Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,213 @@
/* global GaiaGrid */
/* global MocksHelper */
'use strict';
require('/shared/test/unit/mocks/mock_lazy_loader.js');
require('/shared/elements/gaia_grid/js/grid_dragdrop.js');
require('/shared/elements/gaia_grid/js/grid_icon_renderer.js');
require('/shared/elements/gaia_grid/js/grid_layout.js');
require('/shared/elements/gaia_grid/js/grid_view.js');
require('/shared/elements/gaia_grid/js/grid_zoom.js');
require('/shared/elements/gaia_grid/script.js');
require('/shared/elements/gaia_grid/js/items/grid_item.js');
require('/shared/elements/gaia_grid/js/items/divider.js');
require('/shared/elements/gaia_grid/js/items/placeholder.js');
require('/shared/elements/gaia_grid/js/items/group.js');
var mocksHelperForGrid = new MocksHelper([
'LazyLoader'
]).init();
suite('GaiaGrid', function() {
mocksHelperForGrid.attachTestHelpers();
setup(function() {
this.sinon.useFakeTimers();
this.container = document.createElement('div');
document.body.appendChild(this.container);
});
teardown(function() {
document.body.removeChild(this.container);
});
suite('features', function() {
test('default features', function() {
this.container.innerHTML = '<gaia-grid></gaia-grid>';
var grid = this.container.firstElementChild._grid;
// Some features are loaded after rendering like dragdrop
grid.render();
assert.equal(grid.dragdrop, undefined);
assert.equal(grid.zoom, undefined);
assert.ok(grid.layout);
});
test('/w dragdrop', function() {
this.container.innerHTML = '<gaia-grid dragdrop></gaia-grid>';
var grid = this.container.firstElementChild._grid;
grid.render();
assert.ok(grid.dragdrop);
assert.equal(grid.zoom, undefined);
assert.ok(grid.layout);
});
test('/w zoom', function() {
this.sinon.stub(window, 'GridZoom');
this.container.innerHTML = '<gaia-grid zoom></gaia-grid>';
var grid = this.container.firstElementChild._grid;
grid.render();
assert.ok(grid.zoom);
assert.equal(grid.dragdrop, undefined);
assert.ok(grid.layout);
});
test('/w dragdrop and disable-sections', function() {
this.container.innerHTML =
'<gaia-grid dragdrop disable-sections></gaia-grid>';
var grid = this.container.firstElementChild._grid;
grid.render();
assert.ok(grid.config.features.disableSections);
});
});
suite('items', function() {
var element;
var fakeBookmarkItem = {
identifier: 'http://mozilla.org',
detail: {
type: 'bookmark',
id: 'http://mozilla.org',
url: 'http://mozilla.org'
},
setPosition: function() {},
setCoordinates: function() {},
render: function() {}
};
var fakeBookmarkItem2 = {
identifier: 'http://tid.es',
detail: {
type: 'bookmark',
id: 'http://tid.es',
url: 'http://tid.es'
},
setPosition: function() {},
setCoordinates: function() {},
render: function() {}
};
var fakeBookmarkItem3 = {
identifier: 'http://firefoxos.com',
detail: {
type: 'bookmark',
id: 'http://firefoxos.com',
url: 'http://firefoxos.com'
},
setPosition: function() {},
setCoordinates: function() {},
render: function() {}
};
setup(function() {
this.container.innerHTML = '<gaia-grid></gaia-grid>';
element = this.container.firstElementChild;
});
test('adding an item increments count', function() {
var itemLength = element.getItems().length;
element.add(fakeBookmarkItem);
assert.equal(element.getItems().length, itemLength + 1);
});
test('null items do not change item count', function() {
var itemLength = element.getItems().length;
element.add(null);
assert.equal(element.getItems().length, itemLength);
});
test('removeItemByIndex', function() {
var itemLength = element.getItems().length;
element.add(fakeBookmarkItem2);
element.add(fakeBookmarkItem3);
assert.equal(element.getItems().length, itemLength + 2);
element.removeItemByIndex(0);
assert.equal(element.getItems().length, itemLength + 1);
});
test('appendItem adds before divider', function() {
element.clear();
var divider = new GaiaGrid.Divider();
element.add(fakeBookmarkItem);
element.add(divider);
var itemLength = element.getItems().length;
element.appendItemToExpandedGroup(fakeBookmarkItem2);
var items = element.getItems();
assert.equal(items.length, itemLength + 1);
assert.equal(items[items.length - 1], divider);
});
test('appendItemToExpandedGroup expands collapsed divider', function() {
element.clear();
var divider = new GaiaGrid.Divider();
element.add(fakeBookmarkItem);
element.add(divider);
var itemLength = element.getItems().length;
divider.detail.collapsed = true;
element.appendItemToExpandedGroup(fakeBookmarkItem2);
this.sinon.clock.tick(20);
var items = element.getItems();
assert.ok(items.length > itemLength);
assert.notEqual(divider.detail.collapsed, true);
});
test('clear will dereference item elements', function() {
element.clear();
var domEl = document.createElement('div');
element.appendChild(domEl);
var myObj = fakeBookmarkItem;
myObj.element = domEl;
element.add(myObj);
element.clear();
assert.equal(myObj.element, null);
});
test('items with null identifiers are not added to the grid', function() {
element.clear();
var oldIdentifier = fakeBookmarkItem.identifier;
fakeBookmarkItem.identifier = null;
element.add(fakeBookmarkItem);
assert.equal(element.getItems().length, 0);
fakeBookmarkItem.identifier = oldIdentifier;
});
test('height with text-rows attribute', function() {
this.container.innerHTML = '<gaia-grid></gaia-grid>';
var gridEl = this.container.firstElementChild;
var grid = this.container.firstElementChild._grid;
var defaultItemHeight = grid.layout.gridItemHeight;
assert.ok(defaultItemHeight > 0);
gridEl.setAttribute('text-rows', 3);
var threeRowHeight = grid.layout.gridItemHeight;
assert.ok(threeRowHeight > defaultItemHeight);
gridEl.setAttribute('text-rows', 4);
var fourRowHeight = grid.layout.gridItemHeight;
assert.ok(fourRowHeight > threeRowHeight);
assert.equal(fourRowHeight - threeRowHeight,
threeRowHeight - defaultItemHeight,
'additional text rows match height');
});
});
});

View file

@ -0,0 +1,83 @@
'use strict';
/* global GaiaGrid */
require('/shared/elements/gaia_grid/js/grid_icon_renderer.js');
require('/shared/elements/gaia_grid/script.js');
require('/shared/elements/gaia_grid/js/items/grid_item.js');
require('/shared/elements/gaia_grid/js/items/bookmark.js');
suite('GaiaGrid > Bookmark', function() {
var subject = null,
stub = null;
var stubPage1 = {
name: 'first',
id: 1,
icon: 'no',
url: 'http://mozilla.org'
};
setup(function() {
stub = sinon.stub(GaiaGrid.GridItem.prototype, 'render', function() {
this.element = document.createElement('div');
});
subject = new GaiaGrid.Bookmark(stubPage1);
});
teardown(function() {
stub.restore();
});
test('Bookmark created properly', function() {
assert.equal(subject.name, stubPage1.name);
assert.equal(subject.icon, stubPage1.icon);
assert.equal(subject.identifier, stubPage1.id);
assert.equal(subject.detail.url, stubPage1.url);
assert.isUndefined(subject.bookmarking);
assert.isTrue(subject.isRemovable());
assert.isTrue(subject.isEditable());
subject.render();
assert.isTrue(subject.element.classList.contains('editable'));
});
test('Bookmark is not editable', function() {
subject = new GaiaGrid.Bookmark(stubPage1, {
isEditable: false
});
assert.isFalse(subject.isEditable());
subject.render();
assert.isFalse(subject.element.classList.contains('editable'));
});
test('Launch bookmark', function(done) {
var openStub = sinon.stub(window, 'open', function(url, name, features) {
openStub.restore();
assert.equal(url, stubPage1.url);
assert.equal(name, '_samescope');
assert.isTrue(features.contains('name=' + subject.name));
assert.isTrue(features.contains('icon=' + subject.icon));
assert.isTrue(features.contains('remote=true'));
assert.isFalse(features.contains('searchName='));
assert.isFalse(features.contains('searchUrl='));
done();
});
subject.launch();
});
test('Launch bookmark with bookmarking feature', function(done) {
var openStub = sinon.stub(window, 'open', function(url, name, features) {
openStub.restore();
assert.isTrue(features.contains('searchName=' + subject.name));
assert.isTrue(features.contains('searchUrl=' +
encodeURIComponent(subject.detail.url)));
done();
});
subject = new GaiaGrid.Bookmark(stubPage1, {
search: true
});
subject.launch();
});
});

View file

@ -0,0 +1,360 @@
'use strict';
/* global GaiaGrid, MocksHelper */
require('/shared/test/unit/mocks/mock_lazy_loader.js');
require('/shared/elements/gaia_grid/js/grid_dragdrop.js');
require('/shared/elements/gaia_grid/js/grid_icon_renderer.js');
require('/shared/elements/gaia_grid/js/grid_layout.js');
require('/shared/elements/gaia_grid/js/grid_view.js');
require('/shared/elements/gaia_grid/js/grid_zoom.js');
require('/shared/elements/gaia_grid/script.js');
require('/shared/elements/gaia_grid/js/items/grid_item.js');
require('/shared/elements/gaia_grid/js/items/divider.js');
require('/shared/elements/gaia_grid/js/items/mozapp.js');
require('/shared/elements/gaia_grid/js/items/bookmark.js');
require('/shared/elements/gaia_grid/js/items/placeholder.js');
require('/shared/elements/gaia_grid/js/items/group.js');
var mocksHelperForDragDrop = new MocksHelper([
'LazyLoader'
]).init();
suite('GaiaGrid > DragDrop', function() {
var grid;
var stubPage1 = {
name: 'first',
id: 1,
icon: 'no',
url: 'http://mozilla.org'
};
var stubPage2 = {
name: 'second',
id: 2,
icon: 'no',
url: 'http://mozilla.org/2'
};
var stubPage3 = {
name: 'third',
id: 3,
icon: 'no',
url: 'http://mozilla.org/3'
};
var countDividers = function() {
var nDividers = 0;
for (var i = 0, iLen = grid.items.length; i < iLen; i++) {
if (grid.items[i].detail.type === 'divider') {
nDividers ++;
}
}
return nDividers;
};
mocksHelperForDragDrop.attachTestHelpers();
suiteSetup(function() {
document.body.innerHTML = '<div id="zoom"><div class="arrows">' +
'</div><div class="curtain"></div><div class="indicator"></div></div>';
this.container = document.createElement('div');
this.container.innerHTML = '<gaia-grid dragdrop group></gaia-grid>';
document.body.appendChild(this.container);
grid = this.container.firstElementChild._grid;
grid.add(new GaiaGrid.Bookmark(stubPage1));
grid.add(new GaiaGrid.Bookmark(stubPage2));
grid.render();
});
setup(function() {
this.sinon.useFakeTimers();
});
test('changes position of icons', function() {
var firstBookmark = document.querySelector('.icon');
firstBookmark.dispatchEvent(new CustomEvent('contextmenu',
{bubbles: true}));
assert.ok(grid.dragdrop.inEditMode);
assert.ok(firstBookmark.classList.contains('active'));
assert.equal(grid.items[0].name, 'first');
var moveTo = grid.layout.gridItemWidth + 1;
// XXX: Stub the x/y adjustments, we should probably not depend on this
grid.dragdrop.xAdjust = 0;
grid.dragdrop.yAdjust = 0;
grid.dragdrop.handleEvent({
type: 'touchmove',
touches: [{
pageX: moveTo,
pageY: 0
}]
});
grid.dragdrop.handleEvent({
type: 'touchend',
stopImmediatePropagation: function() {},
preventDefault: function() {}
});
this.sinon.clock.tick(grid.dragdrop.touchEndFinishDelay);
grid.dragdrop.handleEvent({ type: 'transitionend' });
assert.equal(grid.items[0].name, 'second');
assert.equal(grid.items[1].name, 'first');
});
test('attention requested if item doesn\'t move', function() {
var firstBookmark = grid.items[0].element;
var requestAttentionStub = sinon.stub(grid.items[0], 'requestAttention');
firstBookmark.dispatchEvent(new CustomEvent('contextmenu',
{bubbles: true}));
grid.dragdrop.handleEvent({
type: 'touchend',
stopImmediatePropagation: function() {},
preventDefault: function() {}
});
this.sinon.clock.tick(grid.dragdrop.touchEndFinishDelay);
grid.dragdrop.handleEvent({ type: 'transitionend' });
assert.ok(requestAttentionStub.called);
requestAttentionStub.restore();
});
test('cleanup if the touch gesture is canceled', function() {
var firstBookmark = grid.items[0].element;
firstBookmark.dispatchEvent(new CustomEvent('contextmenu',
{bubbles: true}));
assert.ok(grid.dragdrop.inEditMode);
assert.ok(firstBookmark.classList.contains('active'));
grid.dragdrop.handleEvent({
type: 'touchcancel',
stopImmediatePropagation: function() {},
preventDefault: function() {}
});
grid.dragdrop.handleEvent({ type: 'transitionend' });
assert.isFalse(firstBookmark.classList.contains('active'));
});
test('long-press listener not activated during dragging', function() {
grid.dragdrop.icon = grid.items[0];
grid.dragdrop.handleEvent({
type: 'touchstart',
touches: [ { pageX: 0, pageY: 0, screenX: 0, screenY: 0 } ],
target: grid.items[0].element
});
assert.equal(grid.dragdrop.longPressTimeout, null);
});
test('rearrange uses reference of icon for position', function() {
var subject = grid.dragdrop;
subject.icon = grid.items[0];
// The current positions are second -> first -> placeholder
// Simulate a drop onto the second item (index 2).
subject.rearrange(grid.items[2]);
assert.equal(grid.items[0].name, 'first');
assert.equal(grid.items[1].name, 'second');
});
test('create new groups by dropping items at the end', function() {
var requestAttentionStub =
sinon.stub(GaiaGrid.Divider.prototype, 'requestAttention');
var dividers = countDividers();
var subject = grid.dragdrop;
// The current positions are first, second, placeholder, divider,
subject.icon = grid.items[1];
subject.createNewDivider(grid.items[3]);
// After creating the new group, there should be one extra divider.
assert.equal(countDividers(), dividers + 1);
// And attention should have been requested on the new divider.
var newDivider;
for (var i = grid.items.length - 1; i >= 0; i--) {
if (grid.items[i].detail.type === 'divider') {
newDivider = grid.items[i];
break;
}
}
assert.ok(requestAttentionStub.calledOn(newDivider));
requestAttentionStub.restore();
});
test('rearrange collapsed group before expanded group', function() {
var divider = grid.items[7];
assert.equal(divider.detail.type, 'divider');
assert.ok(divider.element.classList.contains('group'));
divider.collapse();
this.sinon.clock.tick(20);
assert.equal(divider.detail.collapsed, true);
var subject = grid.dragdrop;
subject.icon = divider;
subject.rearrange(grid.items[0]);
assert.equal(grid.items[0].name, 'second');
assert.equal(grid.items[2].name, 'first');
});
test('rearrange collapsed group after expanded group', function() {
var divider = grid.items[1];
assert.equal(divider.detail.type, 'divider');
var subject = grid.dragdrop;
subject.icon = divider;
subject.createNewDivider(grid.items[grid.items.length - 1]);
assert.equal(grid.items[0].name, 'first');
assert.equal(grid.items[4].name, 'second');
});
test('rearrange collapsed group before collapsed group', function() {
var divider = grid.items[3];
assert.equal(divider.detail.type, 'divider');
divider.collapse();
this.sinon.clock.tick(20);
divider = grid.items[3];
assert.equal(divider.detail.type, 'divider');
var subject = grid.dragdrop;
subject.icon = divider;
subject.rearrange(grid.items[0]);
assert.equal(grid.items[0].name, 'second');
assert.equal(grid.items[2].name, 'first');
});
test('rearrange collapsed group after collapsed group', function() {
var divider = grid.items[1];
assert.equal(divider.detail.type, 'divider');
var subject = grid.dragdrop;
subject.icon = divider;
subject.createNewDivider(grid.items[grid.items.length - 1]);
assert.equal(grid.items[0].name, 'first');
assert.equal(grid.items[2].name, 'second');
});
test('create new group between groups', function() {
var divider = grid.items[1];
assert.equal(divider.detail.type, 'divider');
divider.expand();
this.sinon.clock.tick(20);
divider = grid.items[3];
assert.equal(divider.detail.type, 'divider');
divider.expand();
this.sinon.clock.tick(20);
// Add a new item to test adding groups between groups
grid.add(new GaiaGrid.Bookmark(stubPage3), 0);
grid.render();
// Now simulate dragging the first icon over the first divider and check
// that a new divider is created, and that the new app is in it
var dividers = countDividers();
divider = grid.items[3];
assert.equal(divider.detail.type, 'divider');
var subject = grid.dragdrop;
subject.icon = grid.items[0];
subject.createNewDivider(divider);
assert.equal(countDividers(), dividers + 1);
assert.equal(grid.items[4].name, 'third');
});
test('check no visual indicator for redundant moves', function() {
// Each of these dividers represents a group with one item, so dragging any
// group or item should mark the previous group and the group itself as
// invalid drop targets
var divider1 = grid.items[3];
var divider2 = grid.items[7];
var divider3 = grid.items[9];
var subject = grid.dragdrop;
subject.icon = divider2;
subject.begin({});
assert.isTrue(divider1.element.classList.contains('invalid-drop'));
assert.isTrue(divider2.element.classList.contains('invalid-drop'));
assert.isFalse(divider3.element.classList.contains('invalid-drop'));
subject.finish();
subject.finalize();
// Check that dragging the first divider over the top of the container
// wouldn't cause a redundant move.
subject.icon = divider1;
subject.begin({});
subject.handleEvent({
type: 'touchmove',
touches: [{
pageX: 0,
pageY: -100
}]
});
assert.isFalse(grid.element.classList.contains('hover-over-top'));
// Tidy up
subject.finish();
subject.finalize();
assert.isFalse(divider1.element.classList.contains('invalid-drop'));
assert.isFalse(divider2.element.classList.contains('invalid-drop'));
});
test('empty groups remain during dragging', function() {
// Each of the three bookmark items is now in a group by itself. Dragging
// any of them outside of that group should leave an empty group, until
// dragging finishes (and then that group should disappear).
// Test this by dragging the first and last items into the middle group,
// testing that the empty group remains until the drag finishes.
var firstIcon = grid.items[0];
var middleIcon = grid.items[4];
var lastIcon = grid.items[8];
// Expand the last divider
grid.items[9].expand();
this.sinon.clock.tick(20);
var nDividers = countDividers();
var subject = grid.dragdrop;
subject.icon = firstIcon;
subject.target = firstIcon.element;
subject.begin({});
subject.rearrange(middleIcon);
assert.equal(nDividers, countDividers());
subject.finish();
subject.finalize();
assert.equal(nDividers - 1, countDividers());
nDividers = countDividers();
subject.icon = lastIcon;
subject.target = lastIcon.element;
subject.begin({});
subject.rearrange(firstIcon);
assert.equal(nDividers, countDividers());
subject.finish();
subject.finalize();
assert.equal(nDividers - 1, countDividers());
});
});

View file

@ -0,0 +1,88 @@
'use strict';
/* global GaiaGrid */
require('/shared/js/url_helper.js');
require('/shared/elements/gaia_grid/js/grid_icon_renderer.js');
require('/shared/elements/gaia_grid/js/grid_layout.js');
require('/shared/elements/gaia_grid/js/grid_view.js');
require('/shared/elements/gaia_grid/js/items/placeholder.js');
require('/shared/elements/gaia_grid/script.js');
require('/shared/elements/gaia_grid/js/items/grid_item.js');
suite('GridItem', function() {
setup(function() {
this.container = document.createElement('div');
this.container.innerHTML = '<gaia-grid></gaia-grid>';
document.body.appendChild(this.container);
});
test('renderIconFromSrc sets the background size', function(done) {
var subject = new GaiaGrid.GridItem();
subject.element = document.createElement('div');
var original = subject._displayDecoratedIcon;
subject._displayDecoratedIcon = function(blob) {
original.call(subject, blob);
var gridItemHeight = parseInt(this.element.style.height, 10);
var backgroundSize = parseInt(this.element.style.backgroundSize, 10);
assert.ok(backgroundSize > 0);
assert.isTrue(gridItemHeight >= backgroundSize);
done();
};
subject.renderIconFromSrc('/style/icons/Blank.png');
});
test('basic auth in icon urls', function() {
var subject = new GaiaGrid.GridItem();
subject.app = {
origin: 'some:user@mozilla.org'
};
var result = subject.closestIconFromList({
100: '/icon.png'
});
assert.equal(result, 'some:user@mozilla.org/icon.png');
});
test('scale doesn\'t affect style width', function() {
var subject = new GaiaGrid.GridItem();
subject.grid.config.element =
{ appendChild: function() {}, offsetWidth: 333 };
subject.updateTitle = function() {};
subject.renderIcon = function() {};
subject.render();
var width1 = subject.element.style.width;
subject.element = null;
subject.scale = 2;
subject.render();
var width2 = subject.element.style.width;
assert.equal(width1, width2);
});
test('Detect W3C web app manifest icons format', function() {
var icons = [];
window.IconsHelper = {
'getBestIconFromWebManifest': function() {}
};
this.sinon.stub(window.IconsHelper,
'getBestIconFromWebManifest', function() {
return new URL('http://example.com/icon.png');
});
var subject = new GaiaGrid.GridItem();
subject.app = {
'manifestURL': 'http://example.com/manifest.json'
};
var result = subject.closestIconFromList(icons);
assert.equal(result, 'http://example.com/icon.png');
window.WebManifestHelper = undefined;
});
});