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,73 @@
'use strict';
require('/shared/js/component_utils.js');
require('/shared/elements/gaia_checkbox/script.js');
suite('GaiaCheckbox', function() {
setup(function() {
this.container = document.createElement('div');
});
test('Label is populated', function() {
this.container.innerHTML = '<gaia-checkbox><label>my label</label>' +
'</gaia-checkbox>';
assert.equal(this.container.querySelector('label').textContent,
'my label');
});
test('Not checked by default', function() {
this.container.innerHTML = '<gaia-checkbox></gaia-checkbox>';
var element = this.container.firstElementChild;
assert.equal(element.checked, false);
});
test('ClassName is proxied to shadow dom', function() {
this.container.innerHTML = '';
var element = document.createElement('gaia-checkbox');
element.className = 'inline';
this.container.appendChild(element);
var wrapper = element.shadowRoot.querySelector('#checkbox');
assert.equal(wrapper.classList.contains('inline'), true);
element.className = '';
assert.equal(wrapper.classList.contains('inline'), false);
});
test('Checks the element based on checked attr', function() {
this.container.innerHTML =
'<gaia-checkbox checked></gaia-checkbox>';
var element = this.container.firstElementChild;
var span = element.shadowRoot.querySelector('span');
assert.equal(element.checked, true);
assert.ok(span.classList.contains('checked'));
});
test('Attribute change toggles checked state', function() {
this.container.innerHTML = '<gaia-checkbox></gaia-checkbox>';
var element = this.container.firstElementChild;
var checkbox = element.shadowRoot.querySelector('span');
assert.equal(element.checked, false);
assert.ok(!checkbox.classList.contains('checked'));
element.checked = 'checked';
assert.ok(element.checked);
assert.ok(checkbox.classList.contains('checked'));
});
test('Gets right value after click', function(done) {
this.container.innerHTML = '<gaia-checkbox></gaia-checkbox>';
var element = this.container.firstElementChild;
assert.ok(!element.checked);
element.addEventListener('change', function(e) {
assert.equal(e.target.checked, true);
done();
});
element.handleClick({
preventDefault: function() {},
stopImmediatePropagation: function() {}
});
});
});

View file

@ -0,0 +1,74 @@
'use strict';
require('/shared/js/component_utils.js');
require('/shared/elements/gaia_confirm/script.js');
suite('GaiaSwitch', function() {
setup(function() {
this.container = document.createElement('div');
});
teardown(function() {
this.container.parentNode.removeChild(this.container);
});
var subject;
setup(function() {
this.container.innerHTML = '<gaia-confirm>' +
'<h1>title</h1>' +
'<p>description</p>' +
'<gaia-buttons skin="dark">' +
'<button class="cancel">Cancel</button>' +
'<button class="confirm recommend">Submit</button>' +
'</gaia-buttons>' +
'</gaia-confirm>';
subject = this.container.firstElementChild;
// XXX: Currently we must build the entire dom in one go then insert it.
document.body.appendChild(this.container);
});
test('Event : confirm', function(done) {
this.timeout(100);
subject.addEventListener('confirm', function confirm() {
subject.removeEventListener('confirm', confirm);
done();
});
var confirm = this.container.querySelector('.confirm');
confirm.dispatchEvent(new CustomEvent('click'));
});
test('Event : cancel', function(done) {
this.timeout(100);
subject.addEventListener('confirm', function confirm() {
subject.removeEventListener('confirm', confirm);
done();
});
var confirm = this.container.querySelector('.confirm');
confirm.dispatchEvent(new CustomEvent('click'));
});
test('Content is populated', function() {
var element = this.container.firstElementChild;
// Validate title
var titles = element.querySelectorAll('h1');
assert.equal(titles.length, 1);
assert.equal(titles[0].textContent, 'title');
// Validate description
var descs = element.querySelectorAll('p');
assert.equal(descs.length, 1);
assert.equal(descs[0].textContent, 'description');
// Validate buttons
var buttons = element.querySelectorAll('button');
assert.equal(buttons.length, 2);
assert.equal(buttons[0].textContent, 'Cancel');
assert.equal(buttons[1].textContent, 'Submit');
});
});

View file

@ -0,0 +1,75 @@
/*jshint maxlen:false*/
'use strict';
require('/shared/js/component_utils.js');
require('/shared/elements/gaia_drawer/script.js');
suite('GaiaDrawer', function() {
setup(function() {
this.sandbox = sinon.sandbox.create();
// Sizes are in rems, so we set the base font-size
document.documentElement.style.fontSize = '10px';
this.container = document.createElement('div');
this.container.innerHTML = '<gaia-drawer></gaia-drawer>';
this.el = this.container.firstElementChild;
this.el.style.height = '300px';
sinon.spy(this.el, 'open');
sinon.spy(this.el, 'close');
sinon.spy(this.el, 'toggle');
// Insert into DOM to get styling
document.body.appendChild(this.el);
});
teardown(function() {
this.sandbox.restore();
});
test('Should toggle open when `open` attribute changes', function() {
this.el.setAttribute('open', '');
sinon.assert.called(this.el.open);
this.el.removeAttribute('open');
sinon.assert.called(this.el.close);
});
test('Should close the drawer when the background is clicked', function() {
this.el.open();
sinon.assert.notCalled(this.el.close);
this.el.shadowRoot.getElementById('background').click();
sinon.assert.called(this.el.close);
});
test('Should not close the drawer when the content is clicked', function() {
this.el.open();
sinon.assert.notCalled(this.el.close);
this.el.shadowRoot.getElementById('content').click();
sinon.assert.notCalled(this.el.close);
});
suite('GaiaDrawer#toggle()', function() {
test('Should toggle the `open` state', function() {
this.el.toggle();
sinon.assert.called(this.el.open);
assert.isTrue(this.el.hasAttribute('open'));
this.el.toggle();
sinon.assert.called(this.el.close);
assert.isFalse(this.el.hasAttribute('open'));
});
test('Should derive state from argument if passed', function() {
this.el.toggle(true);
sinon.assert.called(this.el.open);
assert.isTrue(this.el.hasAttribute('open'));
this.el.toggle(true);
assert.isTrue(this.el.hasAttribute('open'));
});
});
});

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;
});
});

View file

@ -0,0 +1,41 @@
'use strict';
require('/shared/js/component_utils.js');
require('/shared/elements/gaia_menu/script.js');
suite('GaiaMenu', function() {
setup(function() {
this.container = document.createElement('div');
this.container.innerHTML =
'<gaia-menu>' +
'<header>title</header>' +
'<button>button1</button>'+
'<button>button2</button>'+
'</gaia-menu>';
this.element = this.container.firstElementChild;
});
test('Content is populated', function() {
// Validate header
var headers = this.element.querySelectorAll('header');
assert.equal(headers.length, 1);
assert.equal(headers[0].textContent, 'title');
// Validate button
var buttons = this.element.querySelectorAll('button');
assert.equal(buttons.length, 2);
// Validate a 'cancel' button was added
var cancelButton = this.element.shadowRoot.querySelector('button');
assert.equal(cancelButton.textContent, 'Cancel');
});
test('Cancel event is dispatched', function(done) {
this.element.addEventListener('gaiamenu-cancel', function(e) {
done();
});
this.element.shadowRoot.querySelector('button')
.dispatchEvent(new CustomEvent('click'));
});
});

View file

@ -0,0 +1,81 @@
'use strict';
require('/shared/js/component_utils.js');
require('/shared/elements/gaia_radio/script.js');
suite('GaiaRadio', function() {
var inputs;
setup(function() {
this.container = document.createElement('div');
this.container.innerHTML = '<gaia-radio name="group"></gaia-radio>' +
'<gaia-radio role="dialog" name="group" checked></gaia-radio>' +
'<gaia-radio name="menu" data-role="menuitemradio" checked></gaia-radio>';
document.body.appendChild(this.container);
inputs = this.container.querySelectorAll('gaia-radio');
});
function simulateClick(element) {
element.handleClick({
preventDefault: function() {},
stopImmediatePropagation: function() {}
});
}
function getRadio(element) {
return element.shadowRoot.querySelector('#radio');
}
test('Checked state', function() {
assert.equal(inputs[0].checked, false);
assert.equal(inputs[1].checked, true);
assert.equal(getRadio(inputs[0]).getAttribute('aria-checked'), 'false');
assert.equal(getRadio(inputs[1]).getAttribute('aria-checked'), 'true');
simulateClick(inputs[0]);
assert.equal(inputs[0].checked, true);
assert.equal(inputs[1].checked, false);
});
test('ClassName is proxied to shadow dom', function() {
this.container.innerHTML = '';
var element = document.createElement('gaia-radio');
element.className = 'inline';
this.container.appendChild(element);
var wrapper = element.shadowRoot.querySelector('#radio');
assert.equal(wrapper.classList.contains('inline'), true);
element.className = '';
assert.equal(wrapper.classList.contains('inline'), false);
});
test('Check accessibility roles', function() {
assert.equal(inputs[0].getAttribute('role'), 'presentation');
assert.equal(inputs[1].getAttribute('role'), 'dialog');
assert.equal(getRadio(inputs[2]).getAttribute('role'), 'menuitemradio');
});
test('Gets right value after click', function(done) {
inputs[0].addEventListener('change', function(e) {
assert.equal(e.target.checked, true);
assert.equal(inputs[0].checked, true);
assert.equal(inputs[1].checked, false);
assert.equal(getRadio(inputs[0]).getAttribute('aria-checked'), 'true');
assert.equal(getRadio(inputs[1]).getAttribute('aria-checked'), 'false');
done();
});
simulateClick(inputs[0]);
});
test('Clicking when checked does not change state', function() {
var input = inputs[0];
input.checked = true;
simulateClick(input);
assert.isTrue(input.checked);
});
});

View file

@ -0,0 +1,288 @@
/* globals MockGaiaMenu, MocksHelper, MockL10n, MockNavigatorMozIccManager,
MockTelephonyHelper */
'use strict';
require('/shared/test/unit/mocks/mock_navigator_moz_icc_manager.js');
require('/shared/test/unit/mocks/mock_navigator_moz_telephony.js');
require('/shared/test/unit/mocks/mock_l10n.js');
require('/shared/test/unit/mocks/mock_lazy_loader.js');
require('/shared/test/unit/mocks/elements/gaia_menu/mock_gaia_menu.js');
require('/shared/test/unit/mocks/dialer/mock_telephony_helper.js');
require('/shared/js/component_utils.js');
require('/shared/elements/gaia_sim_picker/script.js');
var mocksHelperForGaiaSimPicker = new MocksHelper([
'LazyLoader'
]).init();
suite('GaiaSimPicker', function() {
var subject;
var realMozIccManager;
var realL10n;
var realTelephonyHelper;
var menu;
var header;
mocksHelperForGaiaSimPicker.attachTestHelpers();
suiteSetup(function() {
realMozIccManager = navigator.mozIccManager;
navigator.mozIccManager = MockNavigatorMozIccManager;
navigator.mozIccManager.mTeardown();
realL10n = navigator.mozL10n;
navigator.mozL10n = MockL10n;
realTelephonyHelper = window.TelephonyHelper;
window.TelephonyHelper = null;
});
suiteTeardown(function() {
navigator.mozIccManager = realMozIccManager;
navigator.mozL10n = realL10n;
window.TelephonyHelper = realTelephonyHelper;
});
setup(function() {
this.sinon.spy(MockGaiaMenu, 'show');
this.sinon.stub(MockL10n, 'ready');
this.sinon.stub(MockL10n, 'once');
this.sinon.useFakeTimers();
this.container = document.createElement('div');
this.container.innerHTML =
'<gaia-sim-picker></gaia-sim-picker>';
subject = this.container.firstElementChild;
subject._menu = MockGaiaMenu;
this.sinon.spy(subject, 'focus');
navigator.mozIccManager.addIcc(0, {});
navigator.mozIccManager.addIcc(1, {});
subject.getOrPick(0, '1111');
header = subject.shadowRoot.querySelector('#sim-picker-dial-via');
menu = subject.shadowRoot.querySelector('gaia-menu');
});
teardown(function() {
navigator.mozIccManager.mTeardown();
});
suite('getOrPick/getInUseSim', function() {
test('header should contain phone number when getter provided', function() {
this.sinon.spy(MockL10n, 'setAttributes');
this.sinon.spy(MockL10n, 'translateFragment');
subject.getOrPick(0, '1111');
sinon.assert.calledWith(MockL10n.setAttributes,
header,
'gaia-sim-picker-dial-via-with-number',
{phoneNumber: '1111'});
sinon.assert.calledWith(MockL10n.translateFragment, subject.shadowRoot);
});
test('header should not contain phone number when getter not provided',
function() {
this.sinon.spy(MockL10n, 'translateFragment');
subject.getOrPick(0, null);
assert.equal(header.getAttribute('data-l10n-id'),
'gaia-sim-picker-select-sim');
sinon.assert.calledWith(MockL10n.translateFragment, subject.shadowRoot);
});
test('show the menu twice with different args', function() {
this.sinon.spy(MockL10n, 'setAttributes');
this.sinon.spy(MockL10n, 'translateFragment');
subject.getOrPick(0, '1111');
sinon.assert.calledWith(MockL10n.setAttributes,
header,
'gaia-sim-picker-dial-via-with-number',
{phoneNumber: '1111'});
var buttons = menu.querySelectorAll('button');
assert.equal(buttons.length, 2);
subject.getOrPick(0, '2222');
sinon.assert.calledWith(MockL10n.setAttributes,
header,
'gaia-sim-picker-dial-via-with-number',
{phoneNumber: '2222'});
assert.equal(buttons.length, 2);
sinon.assert.alwaysCalledWith(
MockL10n.translateFragment, subject.shadowRoot
);
sinon.assert.calledTwice(MockL10n.translateFragment);
});
test('should show the menu after l10n is ready', function() {
MockL10n.once.yield();
sinon.assert.calledOnce(MockGaiaMenu.show);
});
test('should focus on self after l10n is ready', function() {
MockL10n.once.yield();
sinon.assert.calledOnce(subject.focus);
});
test('should retranslate after a language change', function() {
this.sinon.stub(MockL10n, 'translateFragment');
MockL10n.ready.yield();
sinon.assert.calledWith(MockL10n.translateFragment, subject.shadowRoot);
});
});
suite('buttons', function() {
test('should have 2 option buttons', function() {
var buttons = menu.querySelectorAll('button');
assert.equal(buttons.length, 2);
});
test('should mark default SIM', function() {
var buttons = menu.querySelectorAll('button');
for (var i = 0; i < buttons.length; i++) {
assert.equal(buttons[i].classList.contains('is-default'), i === 0);
}
});
test('should not mark default SIM when none is set', function() {
subject.getOrPick(undefined, '2222');
var buttons = menu.querySelectorAll('button');
for (var i = 0; i < buttons.length; i++) {
assert.isFalse(buttons[i].classList.contains('is-default'));
}
});
});
suite('callbacks and functions', function() {
var domEventStub;
var callbackStub;
var passedEvent;
var buttons;
// We must do this because sinon doesn't deep-copy the passed event, which
// excludes its `detail` attr in its stub.args array.
var copyPassedEvent = function(e) {
passedEvent = e;
domEventStub(e);
};
setup(function() {
domEventStub = this.sinon.stub();
callbackStub = this.sinon.stub();
subject.addEventListener('gaiasimpicker-simselected', copyPassedEvent);
subject.getOrPick(0, '1111', callbackStub);
// The first child is the header.
buttons = menu.querySelectorAll('button');
});
teardown(function() {
subject.removeEventListener('gaiasimpicker-simselected', domEventStub);
});
[0, 1].forEach(function(cardIndex) {
test('should dispatch DOM event when SIM ' + cardIndex + ' is selected',
function() {
buttons[cardIndex].click();
sinon.assert.calledOnce(domEventStub);
assert.equal(passedEvent.detail.cardIndex, cardIndex);
assert.equal(menu.hidden, true);
});
test('should fire callback when SIM ' + cardIndex + ' is selected',
function() {
buttons[cardIndex].click();
sinon.assert.calledWith(callbackStub, cardIndex);
});
test('should not fire callback if DOM event is preventDefaulted on SIM ' +
cardIndex,
function() {
var callbackStub2 = this.sinon.stub();
subject.addEventListener('gaiasimpicker-simselected', function _(e) {
subject.removeEventListener('gaiasimpicker-simselected', _);
e.preventDefault();
});
subject.getOrPick(0, '1111', callbackStub2);
buttons[cardIndex].click();
sinon.assert.notCalled(callbackStub2);
});
test('should fire all queued up callbacks on SIM ' + cardIndex,
function() {
callbackStub.reset();
var callbackStub2 = this.sinon.stub();
subject.getOrPick(0, '1111', callbackStub);
subject.getOrPick(0, '1111', callbackStub2);
buttons[cardIndex].click();
sinon.assert.calledWith(callbackStub, cardIndex);
sinon.assert.calledWith(callbackStub2, cardIndex);
});
test('should not fire stale callback on SIM ' + cardIndex, function() {
// Should clear callbackStub from queued callbacks.
buttons[cardIndex].click();
callbackStub.reset();
var callbackStub2 = this.sinon.stub();
subject.getOrPick(0, '1111', callbackStub2);
buttons[cardIndex].click();
sinon.assert.notCalled(callbackStub);
sinon.assert.calledWith(callbackStub2, cardIndex);
});
});
});
/**
* Tests for special case code for the dialer. The code that it's testing
* should have no impact anywhere else.
*/
suite('with TelephonyHelper', function() {
var realTelephonyHelper;
suiteSetup(function() {
realTelephonyHelper = window.TelephonyHelper;
window.TelephonyHelper = MockTelephonyHelper;
});
suiteTeardown(function() {
window.TelephonyHelper = realTelephonyHelper;
});
setup(function() {
MockTelephonyHelper.mInUseSim = 1;
});
teardown(function() {
MockTelephonyHelper.mTeardown();
});
test('should callback with in use serviceId', function(done) {
subject.addEventListener('gaiasimpicker-simselected', function _(e) {
subject.removeEventListener('gaiasimpicker-simselected', _);
assert.equal(e.detail.cardIndex, 1);
done();
});
subject.getOrPick(0, '1111');
});
});
});

View file

@ -0,0 +1,98 @@
'use strict';
require('/shared/js/component_utils.js');
require('/shared/elements/gaia_switch/script.js');
suite('GaiaSwitch', function() {
setup(function() {
this.container = document.createElement('div');
});
test('Label is populated', function() {
this.container.innerHTML = '<gaia-switch><label>my label</label>' +
'</gaia-switch>';
assert.equal(this.container.querySelector('label').textContent,
'my label');
});
test('Input is not checked by default', function() {
this.container.innerHTML = '<gaia-switch></gaia-switch>';
var element = this.container.firstElementChild;
var input = element.shadowRoot.querySelector('input');
assert.equal(input.checked, false);
});
test('Checks the input based on checked attr', function() {
this.container.innerHTML =
'<gaia-switch checked></gaia-switch>';
var element = this.container.firstElementChild;
var input = element.shadowRoot.querySelector('input');
assert.equal(input.checked, true);
});
test('Attribute change toggles checked state', function() {
this.container.innerHTML = '<gaia-switch></gaia-switch>';
var element = this.container.firstElementChild;
var input = element.shadowRoot.querySelector('input');
assert.equal(input.checked, false);
element.checked = 'true';
assert.equal(input.checked, true);
});
test('Gets right value after click', function(done) {
this.container.innerHTML = '<gaia-switch></gaia-switch>';
var element = this.container.firstElementChild;
var input = element.shadowRoot.querySelector('input');
assert.equal(input.checked, false);
element.addEventListener('click', function(e) {
setTimeout(function() {
assert.equal(e.target.checked, true);
done();
});
});
element.handleClick({
target: {},
preventDefault: function() {},
stopImmediatePropagation: function() {}
});
});
test('Dispatches change event', function(done) {
this.container.innerHTML = `<div id="wrapper"><gaia-switch>
</gaia-switch></div>`;
var wrapper = this.container.firstElementChild;
var element = wrapper.firstElementChild;
var input = element.shadowRoot.querySelector('input');
assert.equal(input.checked, false);
wrapper.addEventListener('change', function(e) {
assert.equal(e.type, 'change');
assert.equal(e.target, element);
done();
});
element.handleClick({
target: {},
preventDefault: function() {},
stopImmediatePropagation: function() {}
});
});
test('Clicking link element does not toggle switch', function() {
this.container.innerHTML = `<div id="wrapper">
<gaia-switch>
<label></label><details></details><a href="#">Clicky</a>
</gaia-switch>
</div>`;
var element = this.container.querySelector('gaia-switch');
var details = this.container.querySelector('details');
var link = this.container.querySelector('a');
details.click();
assert.equal(element.checked, true, 'Clicking details toggles the switch');
link.click();
assert.equal(element.checked, true, 'Clicking link does not toggle switch');
});
});

View file

@ -0,0 +1,86 @@
'use strict';
require('/shared/js/component_utils.js');
require('/shared/elements/gaia_tabs/script.js');
suite('GaiaTabs', function() {
setup(function() {
this.clock = sinon.useFakeTimers();
this.container = document.createElement('div');
// Sizes are in rems, so we set the base font-size
document.documentElement.style.fontSize = '10px';
this.container.innerHTML = [
'<gaia-tabs selected="1">',
'<div class="js-child"><span></span></div>',
'<button class="js-child"></button>',
'<a class="js-child"></a>',
'</gaia-tabs>'
].join('');
this.el = this.container.firstElementChild;
this.children = this.el.querySelectorAll('.js-child');
this.span = this.el.querySelector('span');
});
teardown(function() {
this.clock.restore();
});
test('Should select the child at the given index on creation', function() {
assert.isTrue(this.children[1].classList.contains('selected'));
});
test('Should update the selected child when the ' +
'`selected` attribute changes', function() {
assert.isTrue(this.children[1].classList.contains('selected'));
this.el.setAttribute('selected', '2');
assert.isTrue(this.children[2].classList.contains('selected'));
assert.isFalse(this.children[1].classList.contains('selected'));
});
test('Should select a tab when it\'s clicked', function() {
this.children[0].click();
assert.isTrue(this.children[0].classList.contains('selected'));
assert.isFalse(this.children[1].classList.contains('selected'));
});
test('Should select a tab when a tab descendant is clicked', function() {
this.span.click();
assert.isTrue(this.children[0].classList.contains('selected'));
assert.isFalse(this.children[1].classList.contains('selected'));
});
suite('style', function() {
setup(function(done) {
this.container.style.width = '400px';
document.body.appendChild(this.el);
this.el.children[3].onload = function() {
done();
};
});
test('Should position tabs horizontally', function() {
this.children[0].pos = this.children[0].getBoundingClientRect();
this.children[1].pos = this.children[1].getBoundingClientRect();
this.children[2].pos = this.children[2].getBoundingClientRect();
assert.isTrue(this.children[0].pos.right < this.children[1].pos.right);
assert.isTrue(this.children[1].pos.right < this.children[2].pos.right);
});
test('Should size each tab to fill horizontal space equally', function() {
this.el.pos = this.el.getBoundingClientRect();
this.children[0].pos = this.children[0].getBoundingClientRect();
this.children[1].pos = this.children[1].getBoundingClientRect();
this.children[2].pos = this.children[2].getBoundingClientRect();
var expected = Math.round(this.el.pos.width / 3);
assert.equal(Math.round(this.children[0].pos.width), expected);
assert.equal(Math.round(this.children[1].pos.width), expected);
assert.equal(Math.round(this.children[2].pos.width), expected);
});
});
});

View file

@ -0,0 +1,37 @@
'use strict';
require('/shared/js/component_utils.js');
require('/shared/elements/gaia_textinput/script.js');
suite('GaiaTextinput', function() {
setup(function() {
this.container = document.createElement('div');
});
test('simple input with placeholder', function() {
this.container.innerHTML = '<gaia-textinput placeholder="my text">' +
'</gaia-textinput>';
var element = this.container.firstElementChild;
var input = element.shadowRoot.querySelector('input');
assert.equal(input.placeholder, 'my text');
// No reset link
assert.equal(element.querySelectorAll('button[type="reset"]').length, 0);
});
test('reset button is injected', function() {
this.container.innerHTML = '<gaia-textinput reset>' +
'</gaia-textinput>';
var element = this.container.firstElementChild;
var buttons = element.shadowRoot.querySelectorAll('button[type="reset"]');
assert.equal(buttons.length, 1);
});
test('value attribute', function() {
this.container.innerHTML = '<gaia-textinput value="foo">' +
'</gaia-textinput>';
var element = this.container.firstElementChild;
var input = element.shadowRoot.querySelector('input');
assert.equal(input.value, 'foo');
});
});