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,14 @@
{
"extends": "../../../../.jshintrc",
"node": true,
"predef": [
"suite",
"test",
"setup",
"teardown",
"suiteSetup",
"suiteTeardown",
"marionette",
"marionetteScriptFinished"
]
}

View file

@ -0,0 +1,129 @@
'use strict';
var APP_URL = 'app://tv-epg.gaiamobile.org';
var SHARED_PATH = __dirname + '/../../../../shared/test/integration/';
var Keys = {
'up': '\ue013',
'right': '\ue014',
'down': '\ue015',
'left': '\ue012'
};
var assert = require('chai').assert;
// Bug 1207453 - Skip the test due to unknown test enviroment issue for now.
// We should investigate the issue and re-enable the test later.
marionette.skip('Test Program Navigation', function() {
var opts = {
apps: {},
hostOptions: {
screen: {
width: 1480,
height: 960
}
}
};
var client = marionette.client({
profile: opts,
// XXX: Set this to true once Accessibility is implemented in TV
desiredCapabilities: { raisesAccessibilityExceptions: false }
});
var actions;
setup(function() {
actions = client.loader.getActions();
launchModalDialogApp();
});
function launchModalDialogApp() {
// Launch test app
client.contentScript.inject(SHARED_PATH + '/mock_tv_service.js');
client.apps.launch(APP_URL);
client.apps.switchToApp(APP_URL);
}
test('Wait for focus element', {devices: ['tv']},function() {
client.helper.waitForElement('#program-list .focus');
});
test('Key down five times', {devices: ['tv']},function() {
client.helper.waitForElement('#program-list .focus');
var body = client.helper.waitForElement('body');
var i;
for(i = 0; i < 5; i++) {
body.sendKeys(Keys.down);
}
var transform = client.helper.waitForElement('#program-list').scriptWith(
function(element) {
return element.style.transform;
});
var reg = /^translate\([\d\.\-]+(?:rem|px)?,\s*([\d\.\-]+)/i;
assert.notEqual(reg.exec(transform)[1], '0');
});
test('Key down five times and then key up five times', {devices: ['tv']},
function() {
client.helper.waitForElement('#program-list .focus');
var body = client.helper.waitForElement('body');
var i;
for(i = 0; i < 5; i++) {
body.sendKeys(Keys.down);
}
for(i = 0; i < 5; i++) {
body.sendKeys(Keys.up);
}
var transform = client.helper.waitForElement('#program-list').scriptWith(
function(element) {
return element.style.transform;
});
var reg = /^translate\([\d\.\-]+(?:rem|px)?,\s*([\d\.\-]+)/i;
assert.equal(reg.exec(transform)[1], '0');
});
test('Key left', {devices: ['tv']},function() {
client.helper.waitForElement('#program-list .focus');
var body = client.helper.waitForElement('body');
var initialTitle =
client.helper.waitForElement('#program-title').scriptWith(
function(element) {
return element.textContent;
});
body.sendKeys(Keys.left);
var transform = client.helper.waitForElement('#program-list').scriptWith(
function(element) {
return element.style.transform;
});
var reg = /^translate\(([\d\.\-]+)/i;
assert.notEqual(reg.exec(transform)[1], '0');
var newTitle = client.helper.waitForElement('#program-title').scriptWith(
function(element) {
return element.textContent;
});
assert.notEqual(initialTitle, newTitle);
});
test('Key right four times', {devices: ['tv']},function() {
client.helper.waitForElement('#program-list .focus');
var body = client.helper.waitForElement('body');
var i;
for(i = 0; i < 4; i++) {
body.sendKeys(Keys.right);
}
var transform = client.helper.waitForElement('#program-list').scriptWith(
function(element) {
return element.style.transform;
});
var reg = /^translate\(([\d\.\-]+)/i;
assert.notEqual(reg.exec(transform)[1], '0');
});
});

View file

@ -0,0 +1,18 @@
{
"extends": "../../../../.jshintrc",
"predef": [
"assert",
"suite",
"test",
"setup",
"teardown",
"suiteSetup",
"suiteTeardown",
"require",
"requireApp",
"sinon",
"mocha",
"loadBodyHTML"
]
}

View file

@ -0,0 +1,88 @@
'use strict';
/* jshint browser: true */
/* global ContextMenu, MocksHelper, MozActivity */
require('/shared/test/unit/mocks/mock_moz_activity.js');
require('/shared/test/unit/mocks/smart-screen/mock_pin_card.js');
require('/js/context_menu.js');
var mocksHelper = new MocksHelper([
'MozActivity',
'PinCard'
]).init();
suite('tv-epg/contextmenu', function() {
var contextmenu;
var button;
mocksHelper.attachTestHelpers();
setup(function() {
var mockPorts = [{
postMessage: function() {},
}];
button = document.createElement('DIV');
contextmenu = new ContextMenu([{
element: button,
hasText: true
}], {
origin: 'app://contextmenu.test',
manifestURL: 'app://contextmenu.test/manifest.webapp',
connect: function() {
return {
then: function(callback) {
callback(mockPorts);
if (mockPorts[0].onmessage) {
mockPorts[0].onmessage({
data: {
origin: 'app://tvdeck.test',
manifestURL: 'app://tvdeck.test/manifest.webapp'
}
});
}
}
};
}
});
});
suite('Initialization', function() {
test('Initialize members', function() {
assert.equal(contextmenu.tvDeckMeta.origin, 'app://tvdeck.test');
assert.equal(
contextmenu.tvDeckMeta.manifestURL,
'app://tvdeck.test/manifest.webapp'
);
assert.isDefined(contextmenu.pinCard);
});
});
suite('updateMenu', function() {
setup(function() {
MozActivity.mSetup();
window.location.hash = '#1,dvb-0,12';
});
teardown(function() {
window.location.hash = '';
MozActivity.mTeardown();
});
test('Pin card to home', function() {
contextmenu.updateMenu();
button.click();
assert.equal(button.getAttribute('data-l10n-id'), 'pin-to-home');
assert.equal(MozActivity.calls[0].name, 'pin');
});
test('Unpin card to home', function() {
contextmenu.pinCard.pinnedChannels = {
'#1,dvb-0,12': true
};
contextmenu.updateMenu();
button.click();
assert.equal(button.getAttribute('data-l10n-id'), 'unpin-from-home');
});
});
});

View file

@ -0,0 +1,155 @@
'use strict';
/* jshint browser: true */
/* global EPGController, MocksHelper */
require('/bower_components/evt/index.js');
require('/shared/test/unit/mocks/smart-screen/mock_channel_manager.js');
require('/js/epg_controller.js');
var mocksHelper = new MocksHelper([
'ChannelManager'
]).init();
suite('tv-epg/epg_controller', function() {
var epgController;
mocksHelper.attachTestHelpers();
setup(function() {
epgController = new EPGController(4, 30 * 60000);
});
suite('_onScanned', function() {
var fetchPrograms;
var _createChannelList;
setup(function() {
fetchPrograms = this.sinon.stub(epgController, 'fetchPrograms');
_createChannelList = this.sinon.stub(epgController, '_createChannelList');
});
test('_createChannelList should be called', function() {
epgController._onScanned();
assert.isTrue(_createChannelList.called);
});
});
suite('fetchPrograms', function() {
var mockPrograms;
setup(function() {
mockPrograms = [{
startTime: -2 * epgController.timelineUnit,
duration: 4 * epgController.timelineUnit
}, {
startTime: 2 * epgController.timelineUnit,
duration: 4 * epgController.timelineUnit
}, {
startTime: 6 * epgController.timelineUnit,
duration: 4 * epgController.timelineUnit
}];
epgController.timelineOffset = 0;
this.sinon.stub(epgController.channelManager, 'getSource').returns({
channels: [
{
channel: {
getPrograms: function() {
return {
then: function(callback) {
callback(mockPrograms);
}
};
}
}
}
]
});
});
test('Eight timeslot should be created in programTable', function() {
epgController.programTable = [];
epgController.fetchPrograms(-4, 8);
assert.equal(epgController.programTable.length, 8);
assert.equal(epgController.totalTimeslotCount, 8);
});
test('Eight timeslot should be created in programTable', function() {
epgController.programTable = [];
epgController.fetchPrograms(-4, 8);
assert.equal(epgController.programTable.length, 8);
assert.equal(epgController.totalTimeslotCount, 8);
});
test('fetchPrograms from 0 to 8', function() {
epgController.programTable = [];
epgController.fetchPrograms(0, 8);
var answers = [{
program: mockPrograms[0],
duration: 2
}, {
program: mockPrograms[0],
duration: 1
}, {
program: mockPrograms[1],
duration: 4
}, {
program: mockPrograms[1],
duration: 3
}, {
program: mockPrograms[1],
duration: 2
}, {
program: mockPrograms[1],
duration: 1
}, {
program: mockPrograms[2],
duration: 2
}, {
program: mockPrograms[2],
duration: 1
}];
var i;
for(i = 0; i < epgController.programTable.length; i++) {
assert.equal(
epgController.programTable[i][0].program, answers[i].program);
}
});
test('fetchPrograms from 0 to 2 and then -2 to 7', function() {
epgController.programTable = [];
epgController.fetchPrograms(0, 2);
epgController.fetchPrograms(-2, 7);
var answers = [{
program: mockPrograms[0],
duration: 4
}, {
program: mockPrograms[0],
duration: 3
}, {
program: mockPrograms[0],
duration: 2
}, {
program: mockPrograms[0],
duration: 1
}, {
program: mockPrograms[1],
duration: 3
}, {
program: mockPrograms[1],
duration: 2
}, {
program: mockPrograms[1],
duration: 1
}];
assert.equal(epgController.programTable.length, 7);
assert.equal(epgController.totalTimeslotCount, 7);
var i;
for(i = 0; i < epgController.programTable.length; i++) {
assert.equal(
epgController.programTable[i][0].program, answers[i].program);
}
});
});
});

View file

@ -0,0 +1,102 @@
'use strict';
/* jshint browser: true */
/* global EPGProgram */
require('/js/epg_program.js');
suite('tv-epg/epg_program', function() {
var epgProgram;
suiteSetup(function() {
window.EPG = {
COLUMN_WIDTH: 33.8,
COLUMN_MARGIN: 0.6
};
});
setup(function() {
epgProgram = new EPGProgram();
});
suite('createdCallback', function() {
test('Initialization', function() {
assert.equal(epgProgram.duration, 1);
assert.equal(epgProgram.dataset.duration, '1');
assert.isDefined(epgProgram.titleElement);
assert.isDefined(epgProgram.progressElement);
});
});
suite('resetProgressElement', function() {
setup(function() {
epgProgram.startTime = 10;
epgProgram.duration = 3;
});
test('Program has been played', function() {
epgProgram.resetProgressElement(20);
assert.equal(epgProgram.progressElement.style.transform, 'scaleX(1)');
});
test('Program is still playing', function() {
epgProgram.resetProgressElement(12);
assert.equal(epgProgram.progressElement.style.transform, 'scaleX(0)');
assert.isTrue(epgProgram.progressElement.classList.contains('smooth'));
});
});
suite('fillProgress', function() {
test('Fill color of progress element', function() {
epgProgram.fillProgress();
assert.equal(epgProgram.progressElement.style.transform, 'scaleX(1)');
});
});
suite('startTime', function() {
test('Set start time', function() {
epgProgram.startTime = 20;
assert.equal(epgProgram.startTime, 20);
});
});
suite('title', function() {
test('Set title of title element', function() {
epgProgram.title = 'title';
assert.equal(epgProgram.titleElement.textContent, 'title');
});
});
suite('setTitlePadding', function() {
test('Set padding of title element', function() {
epgProgram.titlePadding = '10rem';
assert.equal(epgProgram.titleElement.style.paddingLeft, '10rem');
});
});
suite('setDuration', function() {
test('Set duration of epgProgram', function() {
epgProgram.duration = 10;
assert.equal(epgProgram.duration, 10);
assert.equal(epgProgram.dataset.duration, '10');
});
});
suite('hide', function() {
test('Hide epgProgram', function() {
epgProgram.hide();
assert.isTrue(epgProgram.classList.contains('hidden'));
});
});
suite('show', function() {
test('Show epgProgram', function() {
epgProgram.show();
assert.isFalse(epgProgram.classList.contains('hidden'));
});
});
suiteTeardown(function() {
window.EPG = null;
});
});

View file

@ -0,0 +1,299 @@
'use strict';
/* jshint browser: true */
/* global MocksHelper, MockL10n, EPG */
require('/shared/test/unit/mocks/smart-screen/mock_spatial_navigator.js');
require('/shared/test/unit/mocks/smart-screen/mock_key_navigation_adapter.js');
require('/shared/test/unit/mocks/smart-screen/mock_clock.js');
require('/shared/test/unit/mocks/mock_l10n.js');
require('/test/unit/mock_epg_controller.js');
require('/js/epg_program.js');
require('/js/epg.js');
var mocksHelper = new MocksHelper([
'EPGController',
'Clock',
'SpatialNavigator',
'KeyNavigationAdapter'
]).init();
suite('tv-epg/epg', function() {
var realL10n;
var epg;
mocksHelper.attachTestHelpers();
function createMockElement(id, type) {
var element;
element = document.createElement(type || 'div');
element.id = id;
document.body.appendChild(element);
return element;
}
function createMockUI() {
createMockElement('time');
createMockElement('date');
createMockElement('video-thumbnail', 'video');
createMockElement('timeline');
createMockElement('time-prefix');
createMockElement('channel-list');
createMockElement('progress-bar');
createMockElement('program-list');
createMockElement('program-title');
createMockElement('program-detail');
createMockElement('time-marker-container');
createMockElement('time-marker');
}
suiteSetup(function() {
createMockUI();
});
setup(function() {
realL10n = window.navigator.mozL10n;
window.navigator.mozL10n = MockL10n;
epg = new EPG();
this.sinon.stub(epg, '_updateClock');
});
teardown(function() {
window.navigator.mozL10n = realL10n;
});
suite('_addTimeline', function() {
setup(function() {
epg.programListElement.appendChild(document.createElement('UL'));
epg.programListElement.appendChild(document.createElement('UL'));
});
teardown(function() {
epg.programListElement.innerHTML = '';
epg.timelineElement.innerHTML = '';
});
test('Add one timeslot to timeline', function() {
var time = new Date('Sat May 16 2015 19:24:55 GMT+0800 (CST)');
epg._addTimeline(0, time.getTime());
assert.equal(epg.timelineElement.children.length, 1);
assert.equal(epg.programListElement.children[0].children.length, 1);
assert.equal(epg.programListElement.children[1].children.length, 1);
});
});
suite('_appendChannel', function() {
test('Append two channels', function() {
epg._appendChannel({
name: 'ESPN',
number: '10'
});
epg._appendChannel({
name: 'ESPNHD',
number: '11'
});
assert.equal(epg.channelListElement.children.length, 2);
assert.equal(epg.programListElement.children.length, 2);
});
teardown(function() {
epg.programListElement.innerHTML = '';
epg.channelListElement.innerHTML = '';
});
});
suite('_updateProgramSlot', function() {
var programElement;
setup(function() {
var rowElement = document.createElement('UL');
programElement = document.createElement('epg-program');
rowElement.appendChild(programElement);
epg.programListElement.appendChild(rowElement);
});
teardown(function() {
epg.programListElement.innerHTML = '';
});
test('Hide column', function() {
var configs = {
row: 0,
column: 0,
title: 'program1',
isVisible: false,
duration: 10
};
epg._updateProgramSlot(configs);
assert.equal(programElement.titleElement.textContent, 'program1');
assert.isTrue(programElement.classList.contains('hidden'));
assert.equal(programElement.dataset.duration, '10');
});
test('Visible column with item', function() {
var configs = {
row: 0,
column: 0,
title: 'program1',
isVisible: true,
duration: 10
};
epg._updateProgramSlot(configs);
assert.equal(programElement.titleElement.textContent, 'program1');
assert.isFalse(programElement.classList.contains('hidden'));
assert.equal(programElement.dataset.duration, '10');
});
teardown(function() {
epg.programListElement.innerHTML = '';
});
});
suite('_allChannelFetched', function() {
setup(function() {
epg.epgController.channelOffset = 6;
});
teardown(function() {
epg.channelListElement.style.top = null;
epg.programListElement.style.top = null;
});
test('Adjust top offset', function() {
var answer = (-6 * EPG.ROW_HEIGHT) + 'rem';
epg._allChannelFetched();
assert.equal(epg.channelListElement.style.top, answer);
assert.equal(epg.programListElement.style.top, answer);
});
});
suite('_onUnfocus', function() {
test('Class focus should be removed', function() {
var element = document.createElement('DIV');
element.classList.add('focus');
epg._onUnfocus(element);
assert.isFalse(element.classList.contains('focus'));
});
});
suite('_onFocus', function() {
var fetchPrograms;
var columnElement;
var rowElement;
setup(function() {
this.sinon.stub(epg, '_setTitlePadding');
this.sinon.stub(epg, '_displayProgramInfo');
fetchPrograms = this.sinon.stub(epg.epgController, 'fetchPrograms')
.returns({
then: function() {
return {
catch: function() {}
};
}
});
columnElement = document.createElement('epg-program');
rowElement = document.createElement('DIV');
rowElement.appendChild(columnElement);
epg.visibleChannelOffset = 10;
epg.epgController.channelOffset = 10;
epg.initialTime = 10;
epg.visibleTimeOffset = 10;
});
teardown(function() {
epg.channelListElement.style.transform = null;
epg.programListElement.style.transform = null;
});
test('Move down', function() {
var channelAnswer = 'translateY(' + EPG.ROW_HEIGHT +
'rem) translateZ(0.01rem)';
var programAnswer = 'translate(0rem, ' + EPG.ROW_HEIGHT +
'rem) translateZ(0.01rem)';
rowElement.dataset.row = 9;
epg._onFocus(columnElement);
assert.equal(epg.visibleChannelOffset, 9);
assert.equal(
epg.channelListElement.style.transform, channelAnswer);
assert.equal(
epg.programListElement.style.transform, programAnswer);
});
test('Move up', function() {
var channelAnswer = 'translateY(' + (-EPG.ROW_HEIGHT) +
'rem) translateZ(0.01rem)';
var programAnswer = 'translate(0rem, ' + (-EPG.ROW_HEIGHT) +
'rem) translateZ(0.01rem)';
rowElement.dataset.row = 15;
epg._onFocus(columnElement);
assert.equal(epg.visibleChannelOffset, 11);
assert.equal(
epg.channelListElement.style.transform, channelAnswer);
assert.equal(
epg.programListElement.style.transform, programAnswer);
});
test('Move left', function() {
var timelineAnswer = 'translateX(' + (EPG.COLUMN_WIDTH) +
'rem) translateZ(0.01rem)';
var programAnswer = 'translate(' + (EPG.COLUMN_WIDTH) +
'rem, 0rem) translateZ(0.01rem)';
epg.epgController.timelineOffset = 4;
columnElement.startTime = epg.visibleTimeOffset - 1;
epg._onFocus(columnElement);
assert.equal(
epg.timelineElement.style.transform, timelineAnswer);
assert.equal(
epg.programListElement.style.transform, programAnswer);
assert.isTrue(fetchPrograms.called);
});
test('Move right', function() {
var timelineAnswer = 'translateX(' + (-EPG.COLUMN_WIDTH * 4) +
'rem) translateZ(0.01rem)';
var programAnswer = 'translate(' + (-EPG.COLUMN_WIDTH * 4) +
'rem, 0rem) translateZ(0.01rem)';
epg.epgController.timelineOffset = 14;
columnElement.startTime = epg.visibleTimeOffset + 4;
epg._onFocus(columnElement);
assert.equal(
epg.timelineElement.style.transform, timelineAnswer);
assert.equal(
epg.programListElement.style.transform, programAnswer);
assert.isTrue(fetchPrograms.called);
});
});
suite('_setTitlePadding', function() {
var programElement;
setup(function() {
programElement = document.createElement('epg-program');
programElement.startTime = 0;
epg.visibleTimeOffset = 1;
epg.epgController.timelineOffset = 0;
epg.epgController.programTable = {
1: {
0: {
element: programElement
}
}
};
});
test('Clear padding-left of title element', function() {
epg._setTitlePadding({
setToNull: true
});
assert.equal(programElement.titleElement.style.paddingLeft, '');
});
test('Set padding-left of title element', function() {
epg._setTitlePadding();
assert.equal(programElement.titleElement.style.paddingLeft, '33.8rem');
});
});
});

View file

@ -0,0 +1,34 @@
(function(exports) {
'use strict';
function MockCardManager() {
this.init = function() {
return {
then: function(callback) {}
};
};
var callbacks = {};
this.on = function(name, callback) {
callbacks[name] = callback;
};
this.mTriggerChange = function(name) {
if (callbacks[name]) {
callbacks[name]();
}
};
this.getCardList = function() {
return {
then: function(callback) {
if (this.cards) {
callback(this.cards);
}
}.bind(this)
};
};
}
exports.MockCardManager = MockCardManager;
}(window));

View file

@ -0,0 +1,22 @@
(function(exports) {
'use strict';
function MockEPGController() {
this.on = function() {};
this.fetchPrograms = function() {
var promise = {
then: function(callback) {
callback();
return promise;
},
catch: function() {
return promise;
}
};
return promise;
};
this.switchChannel = function() {};
}
exports.MockEPGController = MockEPGController;
}(window));

View file

@ -0,0 +1,45 @@
'use strict';
/* jshint browser: true */
/* global PinCard, MocksHelper */
require('/bower_components/evt/index.js');
require('/test/unit/mock_card_manager.js');
require('/shared/js/smart-screen/pin_card.js');
var mocksHelper = new MocksHelper([
'CardManager'
]).init();
suite('tv-epg/contextmenu', function() {
var pinCard;
mocksHelper.attachTestHelpers();
setup(function() {
pinCard = new PinCard({
origin: 'app://tvdeck.test',
manifestURL: 'app://tvdeck.test/manifest.webapp'
});
});
suite('Initialization', function() {
test('Initialize member variables', function() {
assert.equal(pinCard.origin, 'app://tvdeck.test');
assert.equal(pinCard.manifestURL, 'app://tvdeck.test/manifest.webapp');
});
});
suite('updatePinnedChannels', function() {
setup(function() {
pinCard.cardManager.cards = [{
launchURL: 'app://tvdeck.test/index.html#1,dvbt,12'
}, {
launchURL: 'app://player.test/index.html'
}];
pinCard.cardManager.mTriggerChange('cardlist-changed');
});
test('Channel #1,dvbt,12 should be pinned', function() {
assert.isTrue(pinCard.pinnedChannels['#1,dvbt,12']);
});
});
});

View file

@ -0,0 +1,3 @@
'use strict';
require('/shared/test/unit/mocks/mocks_helper.js');