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:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
19
gaia/tv_apps/dashboard/bower.json
Executable file
19
gaia/tv_apps/dashboard/bower.json
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "dashboard",
|
||||
"version": "0.0.0",
|
||||
"homepage": "https://github.com/mozilla-b2g/gaia/tree/master/tv_apps/dashboard",
|
||||
"description": "Smart Screen Dashboard App",
|
||||
"main": "index.html",
|
||||
"license": "MPL",
|
||||
"private": true,
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
],
|
||||
"dependencies": {
|
||||
"evt": "fxos-components/evt#~0.4.0"
|
||||
}
|
||||
}
|
||||
19
gaia/tv_apps/dashboard/bower_components/evt/bower.json
vendored
Executable file
19
gaia/tv_apps/dashboard/bower_components/evt/bower.json
vendored
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "evt",
|
||||
"main": "index.js",
|
||||
"version": "0.4.0",
|
||||
"homepage": "https://github.com/wilsonpage/evt",
|
||||
"authors": [
|
||||
"Wilson Page <wilsonpage@me.com>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"package.json",
|
||||
"test",
|
||||
"tests",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
154
gaia/tv_apps/dashboard/bower_components/evt/index.js
vendored
Executable file
154
gaia/tv_apps/dashboard/bower_components/evt/index.js
vendored
Executable file
|
|
@ -0,0 +1,154 @@
|
|||
|
||||
/**
|
||||
* Evt
|
||||
*
|
||||
* A super lightweight
|
||||
* event emitter library.
|
||||
*
|
||||
* @version 0.3.3
|
||||
* @author Wilson Page <wilson.page@me.com>
|
||||
*/
|
||||
|
||||
;(function() {
|
||||
|
||||
/**
|
||||
* Locals
|
||||
*/
|
||||
|
||||
var proto = Events.prototype;
|
||||
var slice = [].slice;
|
||||
|
||||
/**
|
||||
* Creates a new event emitter
|
||||
* instance, or if passed an
|
||||
* object, mixes the event logic
|
||||
* into it.
|
||||
*
|
||||
* @param {Object} obj
|
||||
* @return {Object}
|
||||
*/
|
||||
function Events(obj) {
|
||||
if (!(this instanceof Events)) return new Events(obj);
|
||||
if (obj) return mixin(obj, proto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback
|
||||
* with an event name.
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {Function} cb
|
||||
* @return {Event}
|
||||
*/
|
||||
proto.on = function(name, cb) {
|
||||
this._cbs = this._cbs || {};
|
||||
(this._cbs[name] || (this._cbs[name] = [])).push(cb);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Attach a callback that once
|
||||
* called, detaches itself.
|
||||
*
|
||||
* TODO: Implement `.off()` to work
|
||||
* with `once()` callbacks.
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {Function} cb
|
||||
* @public
|
||||
*/
|
||||
proto.once = function(name, cb) {
|
||||
this.on(name, one);
|
||||
function one() {
|
||||
cb.apply(this, arguments);
|
||||
this.off(name, one);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a single callback,
|
||||
* or all callbacks associated
|
||||
* with the passed event name.
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {Function} cb
|
||||
* @return {Event}
|
||||
*/
|
||||
proto.off = function(name, cb) {
|
||||
this._cbs = this._cbs || {};
|
||||
|
||||
if (!name) { this._cbs = {}; return; }
|
||||
if (!cb) { return delete this._cbs[name]; }
|
||||
|
||||
var cbs = this._cbs[name] || [];
|
||||
var i;
|
||||
|
||||
while (cbs && ~(i = cbs.indexOf(cb))) { cbs.splice(i, 1); }
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fires an event, triggering
|
||||
* all callbacks registered on this
|
||||
* event name.
|
||||
*
|
||||
* @param {String} name
|
||||
* @return {Event}
|
||||
*/
|
||||
proto.fire = proto.emit = function(options) {
|
||||
var cbs = this._cbs = this._cbs || {};
|
||||
var name = options.name || options;
|
||||
var batch = (cbs[name] || []).concat(cbs['*'] || []);
|
||||
var ctx = options.ctx || this;
|
||||
|
||||
if (batch.length) {
|
||||
this._fireArgs = arguments;
|
||||
var args = slice.call(arguments, 1);
|
||||
while (batch.length) {
|
||||
batch.shift().apply(ctx, args);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
proto.firer = function(name) {
|
||||
var self = this;
|
||||
return function() {
|
||||
var args = slice.call(arguments);
|
||||
args.unshift(name);
|
||||
self.fire.apply(self, args);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Util
|
||||
*/
|
||||
|
||||
/**
|
||||
* Mixes in the properties
|
||||
* of the second object into
|
||||
* the first.
|
||||
*
|
||||
* @param {Object} a
|
||||
* @param {Object} b
|
||||
* @return {Object}
|
||||
*/
|
||||
function mixin(a, b) {
|
||||
for (var key in b) a[key] = b[key];
|
||||
return a;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose 'Event' (UMD)
|
||||
*/
|
||||
|
||||
if (typeof exports === 'object') {
|
||||
module.exports = Events;
|
||||
} else if (typeof define === 'function' && define.amd) {
|
||||
define(function(){ return Events; });
|
||||
} else {
|
||||
window.evt = Events;
|
||||
}
|
||||
|
||||
})();
|
||||
63
gaia/tv_apps/dashboard/index.html
Executable file
63
gaia/tv_apps/dashboard/index.html
Executable file
|
|
@ -0,0 +1,63 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
|
||||
<meta charset="utf-8">
|
||||
<title>Dashboard</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="style/dashboard.css">
|
||||
|
||||
<!-- Shared L10n libraries -->
|
||||
<script src="shared/js/l10n.js"></script>
|
||||
<script src="shared/js/l10n_date.js"></script>
|
||||
|
||||
<!-- L10n properties -->
|
||||
<meta name="defaultLanguage" content="en-US">
|
||||
<meta name="availableLanguages" content="en-US">
|
||||
<link rel="localization" href="locales/dashboard.{locale}.properties"/>
|
||||
<link rel="localization" href="shared/locales/date/date.{locale}.properties"/>
|
||||
|
||||
<!-- Helper libraries -->
|
||||
<script defer src="bower_components/evt/index.js"></script>
|
||||
|
||||
<!-- Shared TV library for keyboard-based navigating -->
|
||||
<script defer src="shared/js/smart-screen/key_navigation_adapter.js"></script>
|
||||
|
||||
<!-- Specific code -->
|
||||
<script defer src="js/app_widget.js"></script>
|
||||
<script defer src="js/digital_clock.js"></script>
|
||||
<script defer src="js/dashboard.js"></script>
|
||||
<script defer src="js/bootstrap.js"></script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="background" class="background"></div>
|
||||
<div id="background-mask" class="background-mask"></div>
|
||||
<div role="region" id="main-section" tabIndex="-1">
|
||||
<section id="center-panel">
|
||||
<div id="digital-clock">
|
||||
<p id="time-now" class='time'>
|
||||
<span id="hours-tens-digit" class="hours-tens-digit"></span>
|
||||
<span id="hours-units-digit" class="hours-units-digit"></span>
|
||||
<span class="colons">:</span>
|
||||
<span id="minutes-tens-digit" class="minutes-tens-digit"></span>
|
||||
<span id="minutes-units-digit" class="minutes-units-digit"></span>
|
||||
<span class="colons">:</span>
|
||||
<span id="seconds-tens-digit" class="seconds-tens-digit"></span>
|
||||
<span id="seconds-units-digit" class="seconds-units-digit"></span>
|
||||
</p>
|
||||
<p id="date-now" class='date'>
|
||||
<span id="today-is" data-l10n-id="today-is"></span><br>
|
||||
<span id="greeting"></span>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<section id="top-panel"></section>
|
||||
<section id="right-panel"></section>
|
||||
<section id="bottom-panel"></section>
|
||||
<section id="left-panel"></section>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
44
gaia/tv_apps/dashboard/js/app_widget.js
Executable file
44
gaia/tv_apps/dashboard/js/app_widget.js
Executable file
|
|
@ -0,0 +1,44 @@
|
|||
'use strict';
|
||||
|
||||
(function(exports) {
|
||||
function AppWidget(config) {
|
||||
this.manifestURL = config.manifestURL;
|
||||
this.widget = config.widget;
|
||||
this.launchPath = config.url;
|
||||
this.position = config.position;
|
||||
this.isExpanded = false;
|
||||
|
||||
var browser = document.createElement('iframe');
|
||||
// XXX: Change the following property and dashboard App permission to
|
||||
// widget-specific one after widget path bug is fixed. See bug 1167063.
|
||||
browser.setAttribute('mozbrowser', 'true');
|
||||
browser.setAttribute('remote', 'true');
|
||||
browser.setAttribute('mozapp', config.manifestURL);
|
||||
browser.src = config.url;
|
||||
|
||||
this.iframe = browser;
|
||||
this.container = document.getElementById(this.position + '-panel');
|
||||
this.container.appendChild(this.iframe);
|
||||
}
|
||||
|
||||
AppWidget.prototype = {
|
||||
_setHash: function aw_setHash(hash) {
|
||||
// Note: The '#' should always present to prevent reload
|
||||
this.iframe.src = this.iframe.src.split('#')[0] + '#' + hash;
|
||||
},
|
||||
|
||||
toggleExpand: function aw_setExpand(value) {
|
||||
if(typeof value !== 'undefined') {
|
||||
this.isExpanded = ! this.isExpanded;
|
||||
} else {
|
||||
this.isExpanded = value;
|
||||
}
|
||||
this._setHash(this.isExpanded ? 'expand' : '');
|
||||
},
|
||||
|
||||
focus: function aw_focus() {
|
||||
this.iframe.focus();
|
||||
}
|
||||
};
|
||||
exports.AppWidget = AppWidget;
|
||||
}(window));
|
||||
4
gaia/tv_apps/dashboard/js/bootstrap.js
vendored
Executable file
4
gaia/tv_apps/dashboard/js/bootstrap.js
vendored
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
/* globals Dashboard */
|
||||
'use strict';
|
||||
window.dashboard = new Dashboard();
|
||||
window.dashboard.init();
|
||||
99
gaia/tv_apps/dashboard/js/dashboard.js
Executable file
99
gaia/tv_apps/dashboard/js/dashboard.js
Executable file
|
|
@ -0,0 +1,99 @@
|
|||
'use strict';
|
||||
/* global KeyNavigationAdapter, AppWidget, DigitalClock */
|
||||
|
||||
(function(exports) {
|
||||
|
||||
function Dashboard () {}
|
||||
|
||||
Dashboard.prototype = {
|
||||
init: function d_init() {
|
||||
document.body.dataset.activeDirection = '';
|
||||
|
||||
this.elements = {};
|
||||
this.elements.mainSection = document.getElementById('main-section');
|
||||
|
||||
this.keyNavigationAdapter = new KeyNavigationAdapter();
|
||||
this.keyNavigationAdapter.on('move', this.onMove.bind(this));
|
||||
this.keyNavigationAdapter.on('before-move', this.onMove.bind(this));
|
||||
this.keyNavigationAdapter.init(window, {useMozBrowserKeyEvents: true});
|
||||
document.addEventListener(
|
||||
'visibilitychange', this.onVisibilityChange.bind(this));
|
||||
|
||||
this.widgets = {};
|
||||
this.widgets.down = new AppWidget({
|
||||
manifestURL: 'app://weather-widget.gaiamobile.org/manifest.webapp',
|
||||
widget: 'weather',
|
||||
url: 'app://weather-widget.gaiamobile.org/widget.html',
|
||||
position: 'bottom'
|
||||
});
|
||||
|
||||
this.digitalClock = new DigitalClock();
|
||||
this.digitalClock.init();
|
||||
this.digitalClock.start();
|
||||
},
|
||||
|
||||
onVisibilityChange: function d_onVisibilityChange() {
|
||||
if (document.visibilityState !== 'visible') {
|
||||
this._clearActiveDirection();
|
||||
document.body.classList.remove('active');
|
||||
} else {
|
||||
this.digitalClock.generateGreeting();
|
||||
document.body.classList.add('active');
|
||||
}
|
||||
},
|
||||
|
||||
onMove: function d_onMove(key) {
|
||||
var activeDirection = document.body.dataset.activeDirection;
|
||||
|
||||
switch (activeDirection) {
|
||||
case 'up':
|
||||
if (key === 'down') {
|
||||
this._clearActiveDirection();
|
||||
}
|
||||
break;
|
||||
case 'right':
|
||||
if (key === 'left') {
|
||||
this._clearActiveDirection();
|
||||
}
|
||||
break;
|
||||
case 'down':
|
||||
if (key === 'up') {
|
||||
this._clearActiveDirection();
|
||||
}
|
||||
break;
|
||||
case 'left':
|
||||
if (key === 'right') {
|
||||
this._clearActiveDirection();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
document.body.dataset.activeDirection = key;
|
||||
if (this.widgets[key]) {
|
||||
this.widgets[key].toggleExpand(true);
|
||||
|
||||
this._holdFocus();
|
||||
// Note: Gecko refuses to change focus on a key event callback. we
|
||||
// need to setTimeout to prevent fail.
|
||||
setTimeout(() => this.widgets[key].focus());
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_holdFocus: function() {
|
||||
document.activeElement.blur();
|
||||
this.elements.mainSection.focus();
|
||||
},
|
||||
|
||||
_clearActiveDirection: function() {
|
||||
var direction = document.body.dataset.activeDirection;
|
||||
this.widgets[direction] && this.widgets[direction].toggleExpand(false);
|
||||
document.body.dataset.activeDirection = '';
|
||||
this._holdFocus();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
exports.Dashboard = Dashboard;
|
||||
|
||||
}(window));
|
||||
138
gaia/tv_apps/dashboard/js/digital_clock.js
Executable file
138
gaia/tv_apps/dashboard/js/digital_clock.js
Executable file
|
|
@ -0,0 +1,138 @@
|
|||
'use strict';
|
||||
|
||||
(function (exports) {
|
||||
|
||||
function DigitalClock () {
|
||||
this.date = null;
|
||||
|
||||
this.timeoutId = null;
|
||||
|
||||
this.elements = {
|
||||
'hours-tens-digit': document.getElementById('hours-tens-digit'),
|
||||
'hours-units-digit': document.getElementById('hours-units-digit'),
|
||||
'minutes-tens-digit': document.getElementById('minutes-tens-digit'),
|
||||
'minutes-units-digit': document.getElementById('minutes-units-digit'),
|
||||
'seconds-tens-digit': document.getElementById('seconds-tens-digit'),
|
||||
'seconds-units-digit': document.getElementById('seconds-units-digit'),
|
||||
'today': document.getElementById('today-is'),
|
||||
'greeting': document.getElementById('greeting')
|
||||
};
|
||||
}
|
||||
|
||||
DigitalClock.prototype = {
|
||||
|
||||
init: function () {
|
||||
this._initTime(new Date());
|
||||
},
|
||||
|
||||
start: function () {
|
||||
this._tick(this._update.bind(this));
|
||||
},
|
||||
|
||||
stop: function () {
|
||||
if (this.timeoutId) {
|
||||
window.clearTimeout(this.timeoutId);
|
||||
this.timeoutId = null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Start ticking the clock.
|
||||
* @param {Function} refresh A callback to run on every tick.
|
||||
*/
|
||||
_tick: function (refresh) {
|
||||
var now = new Date();
|
||||
|
||||
refresh(now);
|
||||
|
||||
self.timeoutId = setTimeout(this._tick.bind(this, refresh),
|
||||
1000 - now.getMilliseconds());
|
||||
},
|
||||
|
||||
/**
|
||||
* Init the time of the clock.
|
||||
*/
|
||||
_initTime: function (date) {
|
||||
var hours = date.getHours();
|
||||
var minutes = date.getMinutes();
|
||||
var seconds = date.getSeconds();
|
||||
|
||||
this.elements['hours-tens-digit'].textContent = Math.floor(hours / 10);
|
||||
this.elements['hours-units-digit'].textContent = hours % 10;
|
||||
this.elements['minutes-tens-digit'].textContent =
|
||||
Math.floor(minutes / 10);
|
||||
this.elements['minutes-units-digit'].textContent = minutes % 10;
|
||||
this.elements['seconds-tens-digit'].textContent =
|
||||
Math.floor(seconds / 10);
|
||||
this.elements['seconds-units-digit'].textContent = seconds % 10;
|
||||
|
||||
this._changeDate(date);
|
||||
|
||||
this.date = date;
|
||||
},
|
||||
|
||||
generateGreeting: function () {
|
||||
var hours = this.date ? this.date.getHours() : (new Date()).getHours;
|
||||
this.elements.greeting.dataset.l10nId = 'greeting-at-' + hours;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the display date and time.
|
||||
* @param {Date} date The Date to be displayed.
|
||||
*/
|
||||
_update: function (date) {
|
||||
var hours = date.getHours();
|
||||
var minutes = date.getMinutes();
|
||||
var seconds = date.getSeconds();
|
||||
|
||||
if (this.date.getHours() !== hours) {
|
||||
this._changeTime('hours', this.date.getHours(), hours);
|
||||
}
|
||||
|
||||
if (this.date.getMinutes() !== minutes) {
|
||||
this._changeTime('minutes', this.date.getMinutes(), minutes);
|
||||
}
|
||||
|
||||
this._changeTime('seconds', this.date.getSeconds(), seconds);
|
||||
|
||||
if (this.date.getDate() !== date.getDate()) {
|
||||
this._changeDate(date);
|
||||
}
|
||||
this.date = date;
|
||||
},
|
||||
|
||||
_changeTime: function(unit, oldValue, newValue) {
|
||||
if (Math.floor(oldValue / 10) !== Math.floor(newValue / 10)) {
|
||||
this._changeDigit(unit + '-tens-digit', Math.floor(newValue / 10));
|
||||
}
|
||||
this._changeDigit(unit + '-units-digit', newValue % 10);
|
||||
},
|
||||
|
||||
/**
|
||||
* Change the specified digit and handle the changing animations.
|
||||
* @param {String} position The position of the element.
|
||||
* @param {Number} digit The number to be changed to.
|
||||
*/
|
||||
_changeDigit: function (position, digit) {
|
||||
// TODO: handle animations
|
||||
this.elements[position].textContent = digit;
|
||||
},
|
||||
|
||||
/**
|
||||
* Change the display date using mozL10n.
|
||||
* @param {[Date} date The date to be displayed.
|
||||
*/
|
||||
_changeDate: function (date) {
|
||||
var dateTimeFormat = new navigator.mozL10n.DateTimeFormat();
|
||||
navigator.mozL10n.formatValue('dateFormat').then(function (format) {
|
||||
var formatted = dateTimeFormat.localeFormat(date, format);
|
||||
this.elements.today.dataset.l10nArgs =
|
||||
JSON.stringify({ 'date': formatted });
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
exports.DigitalClock = DigitalClock;
|
||||
|
||||
})(window);
|
||||
18
gaia/tv_apps/dashboard/jsdoc.json
Executable file
18
gaia/tv_apps/dashboard/jsdoc.json
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "tv-dashboard",
|
||||
"tags": {
|
||||
"dictionaries": ["jsdoc"]
|
||||
},
|
||||
"source": {
|
||||
"include": ["tv_apps/dashboard/js/**/*.js"]
|
||||
},
|
||||
"plugins": [
|
||||
"plugins/markdown",
|
||||
"plugins/summarize"
|
||||
],
|
||||
"opts": {
|
||||
"private": true,
|
||||
"recurse": true,
|
||||
"destination": "docs/tv-dashboard"
|
||||
}
|
||||
}
|
||||
26
gaia/tv_apps/dashboard/locales/dashboard.en-US.properties
Executable file
26
gaia/tv_apps/dashboard/locales/dashboard.en-US.properties
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
dateFormat = %A, %b %e
|
||||
today-is=Today is {{date}}
|
||||
greeting-at-0=Good Evening
|
||||
greeting-at-1=Good Evening
|
||||
greeting-at-2=Good Evening
|
||||
greeting-at-3=Good Evening
|
||||
greeting-at-4=Good Morning
|
||||
greeting-at-5=Good Morning
|
||||
greeting-at-6=Good Morning
|
||||
greeting-at-7=Good Morning
|
||||
greeting-at-8=Good Morning
|
||||
greeting-at-9=Good Morning
|
||||
greeting-at-10=Good Morning
|
||||
greeting-at-11=Good Morning
|
||||
greeting-at-12=Good Afternoon
|
||||
greeting-at-13=Good Afternoon
|
||||
greeting-at-14=Good Afternoon
|
||||
greeting-at-15=Good Afternoon
|
||||
greeting-at-16=Good Afternoon
|
||||
greeting-at-17=Good Afternoon
|
||||
greeting-at-18=Good Afternoon
|
||||
greeting-at-19=Good Afternoon
|
||||
greeting-at-20=Good Evening
|
||||
greeting-at-21=Good Evening
|
||||
greeting-at-22=Good Evening
|
||||
greeting-at-23=Good Evening
|
||||
27
gaia/tv_apps/dashboard/manifest.webapp
Executable file
27
gaia/tv_apps/dashboard/manifest.webapp
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"name": "Dashboard",
|
||||
"description": "Smart Screen Dashboard ",
|
||||
"launch_path": "/index.html",
|
||||
"type": "certified",
|
||||
"developer": {
|
||||
"name": "The Gaia Team",
|
||||
"url": "https://github.com/mozilla-b2g/gaia"
|
||||
},
|
||||
"locales": {
|
||||
"en-US": {
|
||||
"name": "Dashboard",
|
||||
"description": "Smart Screen Dashboard"
|
||||
}
|
||||
},
|
||||
"default_locale": "en-US",
|
||||
"icons": {
|
||||
"336": "/style/icons/dashboard_336.png",
|
||||
"672": "/style/icons/dashboard_672.png"
|
||||
},
|
||||
"orientation": "landscape",
|
||||
"permissions": {
|
||||
"browser": {},
|
||||
"embed-apps": {},
|
||||
"webapps-manage": {}
|
||||
}
|
||||
}
|
||||
306
gaia/tv_apps/dashboard/style/dashboard.css
Executable file
306
gaia/tv_apps/dashboard/style/dashboard.css
Executable file
|
|
@ -0,0 +1,306 @@
|
|||
html {
|
||||
font-size: 10px;
|
||||
/* We have Fira Sans as default font in real device. But it isn't default in
|
||||
* browser. That's the main reason this line is here.*/
|
||||
font-family: "Fira Sans";
|
||||
}
|
||||
|
||||
html, body {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
transform-style: preserve-3d;
|
||||
perspective: 25rem;
|
||||
}
|
||||
|
||||
a {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
a:hover, a:active, a:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
#main-section {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: transform 1s cubic-bezier(0.5, 0.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
/* Activate widgets */
|
||||
|
||||
body[data-active-direction="up"] #main-section {
|
||||
transform: translateY(25rem);
|
||||
}
|
||||
|
||||
body[data-active-direction="right"] #main-section {
|
||||
transform: translateX(-32.8rem);
|
||||
}
|
||||
|
||||
body[data-active-direction="down"] #main-section {
|
||||
transform: translateY(-28.2rem);
|
||||
}
|
||||
|
||||
body[data-active-direction="left"] #main-section {
|
||||
transform: translateX(39rem);
|
||||
}
|
||||
|
||||
body[data-active-direction="up"] #center-panel,
|
||||
body[data-active-direction="right"] #center-panel,
|
||||
body[data-active-direction="down"] #center-panel,
|
||||
body[data-active-direction="left"] #center-panel {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
body[data-active-direction="up"] #right-panel,
|
||||
body[data-active-direction="up"] #bottom-panel,
|
||||
body[data-active-direction="up"] #left-panel,
|
||||
body[data-active-direction="right"] #top-panel,
|
||||
body[data-active-direction="right"] #bottom-panel,
|
||||
body[data-active-direction="right"] #left-panel,
|
||||
body[data-active-direction="down"] #top-panel,
|
||||
body[data-active-direction="down"] #right-panel,
|
||||
body[data-active-direction="down"] #left-panel,
|
||||
body[data-active-direction="left"] #top-panel,
|
||||
body[data-active-direction="left"] #right-panel,
|
||||
body[data-active-direction="left"] #bottom-panel {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.background {
|
||||
position: fixed;
|
||||
top: -14.1rem;
|
||||
left: -16.4rem;
|
||||
width: calc(100% + 36.2rem);
|
||||
height: calc(100% + 26.6rem);
|
||||
background-image: url(images/Default_wallpaper_02.jpg);
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
transition: transform 1s cubic-bezier(0.5, 0.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
body[data-active-direction="up"] .background {
|
||||
transform: translateY(-12.5rem);
|
||||
}
|
||||
|
||||
body[data-active-direction="right"] .background {
|
||||
transform: translateX(16.4rem);
|
||||
}
|
||||
|
||||
body[data-active-direction="down"] .background {
|
||||
transform: translateY(14.1rem);
|
||||
}
|
||||
|
||||
body[data-active-direction="left"] .background {
|
||||
transform: translateX(-19.6rem);
|
||||
}
|
||||
|
||||
.background-mask {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(to right, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0) 26%, rgba(0,0,0,0) 74%, rgba(0,0,0,0.3) 100%),
|
||||
linear-gradient(to bottom, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0) 46%, rgba(0,0,0,0) 54%, rgba(0,0,0,0.3) 100%);
|
||||
}
|
||||
|
||||
#center-panel,
|
||||
#top-panel,
|
||||
#right-panel,
|
||||
#bottom-panel,
|
||||
#left-panel {
|
||||
position: absolute;
|
||||
transition: opacity 1s cubic-bezier(0.5, 0.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
/* center-panel */
|
||||
|
||||
#center-panel {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* digital clock */
|
||||
|
||||
#digital-clock {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -20.9rem 0 0 -57.5rem;
|
||||
width: 115rem;
|
||||
height: 41.8rem;
|
||||
color: white;
|
||||
font-weight: 100;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#digital-clock .time {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 0;
|
||||
font-size: 34rem;
|
||||
line-height: 34rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#digital-clock .time span {
|
||||
width: 17rem;
|
||||
}
|
||||
|
||||
#digital-clock .time span.colons {
|
||||
position: relative;
|
||||
top: -3rem;
|
||||
width: 6rem;
|
||||
}
|
||||
|
||||
#digital-clock .date {
|
||||
position: relative;
|
||||
margin-top: 4.4rem;
|
||||
font-size: 3.4rem;
|
||||
line-height: 3.4rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#today-is {
|
||||
position: relative;
|
||||
/*top: -4rem;*/
|
||||
}
|
||||
|
||||
#greeting {
|
||||
position: relative;
|
||||
top: 4rem;
|
||||
}
|
||||
|
||||
/* top-panel */
|
||||
|
||||
#top-panel {
|
||||
width: 100%;
|
||||
height: 34.8rem;
|
||||
top: -25rem;
|
||||
}
|
||||
|
||||
/* right-panel */
|
||||
|
||||
#right-panel {
|
||||
width: 48rem;
|
||||
height: 100%;
|
||||
right: -32.8rem;
|
||||
}
|
||||
|
||||
/* bottom-panel */
|
||||
|
||||
#bottom-panel {
|
||||
width: 100%;
|
||||
height: 44.2rem;
|
||||
bottom: -28.2rem;
|
||||
}
|
||||
|
||||
/* left-panel */
|
||||
|
||||
#left-panel {
|
||||
width: 58.2rem;
|
||||
height: 100%;
|
||||
left: -39rem;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Intro animation */
|
||||
|
||||
/*
|
||||
* Animation for background.
|
||||
*/
|
||||
|
||||
.active .background {
|
||||
animation: shrink 1s cubic-bezier(0.0, 0.0, 0.0, 1.0) 1.2s;
|
||||
}
|
||||
|
||||
@keyframes shrink {
|
||||
from { transform: translateZ(10rem); }
|
||||
to { transform: translateZ(0); }
|
||||
}
|
||||
|
||||
/*
|
||||
* Animations for greetings and date.
|
||||
*/
|
||||
|
||||
/* XXX: 'both' will keep the animation at running state, not sure if it'll affect performance. ; */
|
||||
/* Come back to this if there's a performance issue. */
|
||||
.active #today-is {
|
||||
animation: slidedown 0.3s cubic-bezier(0.0, 0.0, 0.0, 1.0) 4.5s both;
|
||||
}
|
||||
|
||||
.active #greeting {
|
||||
animation: slideupdown 2.6s cubic-bezier(0.0, 0.0, 0.0, 1.0) 2.2s both;
|
||||
}
|
||||
|
||||
@keyframes slidedown {
|
||||
from {
|
||||
top: -4rem;
|
||||
}
|
||||
to {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideupdown {
|
||||
0% {
|
||||
top: 4rem;
|
||||
}
|
||||
11.54% {
|
||||
top: -3.4rem;
|
||||
}
|
||||
88.46% {
|
||||
top: -3.4rem;
|
||||
}
|
||||
100% { top: 4rem;}
|
||||
}
|
||||
|
||||
/*
|
||||
* The following rules set transform origin to central point of the screen.
|
||||
* For the formula calc(XXrem +/- 50vh/vw), they locate to respective screen
|
||||
* edges by the former term XXrem, then componsate 50vh/50vw for centeral point.
|
||||
*/
|
||||
|
||||
.active #bottom-panel {
|
||||
/* 16rem is the height of its preview area. */
|
||||
transform-origin: 50vw calc(16rem - 50vh);
|
||||
animation: zoomin 1s cubic-bezier(0.0, 0.0, 0.0, 1.0) 1.2s both;
|
||||
}
|
||||
|
||||
.active #top-panel {
|
||||
/* 25rem is the height of its hidden area. */
|
||||
transform-origin: 50vw calc(25rem + 50vh);
|
||||
animation: zoomin 1s cubic-bezier(0.0, 0.0, 0.0, 1.0) 1.2s both;
|
||||
}
|
||||
|
||||
.active #left-panel {
|
||||
/* 39rem is the width of its hidden area. */
|
||||
transform-origin: calc(39rem + 50vw) 50vh;
|
||||
animation: zoomin 1s cubic-bezier(0.0, 0.0, 0.0, 1.0) 1.2s both;
|
||||
}
|
||||
|
||||
.active #right-panel {
|
||||
/* 15.2rem is the width of its preview area. */
|
||||
transform-origin: calc(15.2rem - 50vw) 50vh;
|
||||
animation: zoomin 1s cubic-bezier(0.0, 0.0, 0.0, 1.0) 1.2s both;
|
||||
}
|
||||
|
||||
@keyframes zoomin {
|
||||
from {
|
||||
transform: scale(1.7);
|
||||
}
|
||||
to {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
BIN
gaia/tv_apps/dashboard/style/icons/dashboard_336.png
Executable file
BIN
gaia/tv_apps/dashboard/style/icons/dashboard_336.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
gaia/tv_apps/dashboard/style/icons/dashboard_672.png
Executable file
BIN
gaia/tv_apps/dashboard/style/icons/dashboard_672.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
BIN
gaia/tv_apps/dashboard/style/images/Default_wallpaper_02.jpg
Executable file
BIN
gaia/tv_apps/dashboard/style/images/Default_wallpaper_02.jpg
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 194 KiB |
14
gaia/tv_apps/dashboard/test/marionette/.jshintrc
Executable file
14
gaia/tv_apps/dashboard/test/marionette/.jshintrc
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"extends": "../../../../.jshintrc",
|
||||
"node": true,
|
||||
"predef": [
|
||||
"suite",
|
||||
"test",
|
||||
"setup",
|
||||
"teardown",
|
||||
"suiteSetup",
|
||||
"suiteTeardown",
|
||||
"marionette",
|
||||
"marionetteScriptFinished"
|
||||
]
|
||||
}
|
||||
123
gaia/tv_apps/dashboard/test/marionette/dashboard_test.js
Executable file
123
gaia/tv_apps/dashboard/test/marionette/dashboard_test.js
Executable file
|
|
@ -0,0 +1,123 @@
|
|||
'use strict';
|
||||
|
||||
var APP_URL = 'app://dashboard.gaiamobile.org';
|
||||
|
||||
var Keys = {
|
||||
'up': '\ue013',
|
||||
'right': '\ue014',
|
||||
'down': '\ue015',
|
||||
'left': '\ue012'
|
||||
};
|
||||
|
||||
var assert = require('chai').assert;
|
||||
|
||||
// Skip because the dashboard is not needed app now.
|
||||
// Should re-enable the test when we need it.
|
||||
marionette.skip('Test Arrow Key Events', function() {
|
||||
|
||||
var opts = {
|
||||
hostOptions: {
|
||||
screen: {
|
||||
width: 1920,
|
||||
height: 1080
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var client = marionette.client({
|
||||
profile: opts,
|
||||
// XXX: Set this to true once Accessibility is implemented in TV
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
// Launch test app
|
||||
client.apps.launch(APP_URL);
|
||||
client.apps.switchToApp(APP_URL);
|
||||
});
|
||||
|
||||
test('Display and hide top widget after pressing up/down',
|
||||
{ devices: ['tv'] }, function () {
|
||||
var element = client.helper.waitForElement('body');
|
||||
element.sendKeys(Keys.up);
|
||||
assert.equal(element.scriptWith(function(elem) {
|
||||
return elem.dataset.activeDirection;
|
||||
}), 'up');
|
||||
element.sendKeys(Keys.down);
|
||||
assert.equal(element.scriptWith(function(elem) {
|
||||
return elem.dataset.activeDirection;
|
||||
}), '');
|
||||
});
|
||||
|
||||
test('Display and hide right widget after pressing right/left',
|
||||
{ devices: ['tv'] }, function () {
|
||||
var element = client.helper.waitForElement('body');
|
||||
element.sendKeys(Keys.right);
|
||||
assert.equal(element.scriptWith(function(elem) {
|
||||
return elem.dataset.activeDirection;
|
||||
}), 'right');
|
||||
element.sendKeys(Keys.left);
|
||||
assert.equal(element.scriptWith(function(elem) {
|
||||
return elem.dataset.activeDirection;
|
||||
}), '');
|
||||
});
|
||||
|
||||
test('Display and hide bottom widget after pressing down/up',
|
||||
{ devices: ['tv'] }, function () {
|
||||
var element = client.helper.waitForElement('body');
|
||||
element.sendKeys(Keys.down);
|
||||
assert.equal(element.scriptWith(function(elem) {
|
||||
return elem.dataset.activeDirection;
|
||||
}), 'down');
|
||||
element.sendKeys(Keys.up);
|
||||
assert.equal(element.scriptWith(function(elem) {
|
||||
return elem.dataset.activeDirection;
|
||||
}), '');
|
||||
});
|
||||
|
||||
test('Display and hide left widget after pressing left/right',
|
||||
{ devices: ['tv'] }, function () {
|
||||
var element = client.helper.waitForElement('body');
|
||||
element.sendKeys(Keys.left);
|
||||
assert.equal(element.scriptWith(function(elem) {
|
||||
return elem.dataset.activeDirection;
|
||||
}), 'left');
|
||||
element.sendKeys(Keys.right);
|
||||
assert.equal(element.scriptWith(function(elem) {
|
||||
return elem.dataset.activeDirection;
|
||||
}), '');
|
||||
});
|
||||
|
||||
test('Doesn\'t hide top widgets after pressing up when top widget is visible',
|
||||
{ devices: ['tv'] }, function () {
|
||||
var element = client.helper.waitForElement('body');
|
||||
// test top widget
|
||||
element.sendKeys(Keys.up);
|
||||
assert.equal(element.scriptWith(function(elem) {
|
||||
return elem.dataset.activeDirection;
|
||||
}), 'up');
|
||||
element.sendKeys(Keys.up);
|
||||
assert.equal(element.scriptWith(function(elem) {
|
||||
return elem.dataset.activeDirection;
|
||||
}), 'up');
|
||||
});
|
||||
|
||||
test('Doesn\'t hide top widgets after pressing right or left ' +
|
||||
'when top widget is visible', { devices: ['tv'] }, function () {
|
||||
var element = client.helper.waitForElement('body');
|
||||
// test top widget
|
||||
element.sendKeys(Keys.up);
|
||||
assert.equal(element.scriptWith(function(elem) {
|
||||
return elem.dataset.activeDirection;
|
||||
}), 'up');
|
||||
element.sendKeys(Keys.right);
|
||||
assert.equal(element.scriptWith(function(elem) {
|
||||
return elem.dataset.activeDirection;
|
||||
}), 'up');
|
||||
element.sendKeys(Keys.left);
|
||||
assert.equal(element.scriptWith(function(elem) {
|
||||
return elem.dataset.activeDirection;
|
||||
}), 'up');
|
||||
});
|
||||
|
||||
});
|
||||
109
gaia/tv_apps/dashboard/test/marionette/digital_clock_test.js
Executable file
109
gaia/tv_apps/dashboard/test/marionette/digital_clock_test.js
Executable file
|
|
@ -0,0 +1,109 @@
|
|||
'use strict';
|
||||
|
||||
var APP_URL = 'app://dashboard.gaiamobile.org';
|
||||
|
||||
var assert = require('chai').assert;
|
||||
|
||||
// Skip because the dashboard is not needed app now.
|
||||
// Should re-enable the test when we need it.
|
||||
marionette.skip('Test Digital Clock', function() {
|
||||
|
||||
var opts = {
|
||||
hostOptions: {
|
||||
screen: {
|
||||
width: 1920,
|
||||
height: 1080
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var client = marionette.client({ profile: opts });
|
||||
|
||||
setup(function() {
|
||||
// Launch test app
|
||||
client.apps.launch(APP_URL);
|
||||
client.apps.switchToApp(APP_URL);
|
||||
});
|
||||
|
||||
test('Should display the current time',
|
||||
{ devices: ['tv'] }, function () {
|
||||
var element = client.helper.waitForElement('body');
|
||||
|
||||
var hoursTens = element.findElement('#hours-tens-digit');
|
||||
assert.ok(hoursTens.scriptWith(function(elem) {
|
||||
var now = new Date();
|
||||
return parseInt(elem.textContent, 10) ===
|
||||
Math.floor(now.getHours() / 10);
|
||||
}));
|
||||
|
||||
var hoursUnits = element.findElement('#hours-units-digit');
|
||||
assert.ok(hoursUnits.scriptWith(function(elem) {
|
||||
var now = new Date();
|
||||
return parseInt(elem.textContent, 10) === now.getHours() % 10;
|
||||
}));
|
||||
|
||||
var minutesTens = element.findElement('#minutes-tens-digit');
|
||||
assert.ok(minutesTens.scriptWith(function(elem) {
|
||||
var now = new Date();
|
||||
return parseInt(elem.textContent, 10) ===
|
||||
Math.floor(now.getMinutes() / 10);
|
||||
}));
|
||||
|
||||
var minutesUnits = element.findElement('#minutes-units-digit');
|
||||
assert.ok(minutesUnits.scriptWith(function(elem) {
|
||||
var now = new Date();
|
||||
return parseInt(elem.textContent, 10) === now.getMinutes() % 10;
|
||||
}));
|
||||
|
||||
var secondsTens = element.findElement('#seconds-tens-digit');
|
||||
assert.ok(secondsTens.scriptWith(function(elem) {
|
||||
var now = new Date();
|
||||
return parseInt(elem.textContent, 10) ===
|
||||
Math.floor(now.getSeconds() / 10);
|
||||
}));
|
||||
|
||||
var secondsUnits = element.findElement('#seconds-units-digit');
|
||||
assert.ok(secondsUnits.scriptWith(function(elem) {
|
||||
var now = new Date();
|
||||
return parseInt(elem.textContent, 10) === now.getSeconds() % 10;
|
||||
}));
|
||||
});
|
||||
|
||||
test('Should display the month, date and weekday info',
|
||||
{ devices: ['tv'] }, function () {
|
||||
var element = client.helper.waitForElement('body');
|
||||
|
||||
var today = element.findElement('#today-is');
|
||||
assert.ok(today.scriptWith(function(elem) {
|
||||
var weekday = [
|
||||
'Sunday',
|
||||
'Monday',
|
||||
'Tuesday',
|
||||
'Wednesday',
|
||||
'Thursday',
|
||||
'Friday',
|
||||
'Saturday'
|
||||
];
|
||||
|
||||
var month = [
|
||||
'Jan',
|
||||
'Feb',
|
||||
'Mar',
|
||||
'Apr',
|
||||
'May',
|
||||
'Jun',
|
||||
'Jul',
|
||||
'Aug',
|
||||
'Sep',
|
||||
'Oct',
|
||||
'Nov',
|
||||
'Dec'
|
||||
];
|
||||
var now = new Date();
|
||||
var dateString = 'Today is ' + weekday[now.getDay()] + ', ' +
|
||||
month[now.getMonth()] + ' ' + now.getDate();
|
||||
return elem.textContent === dateString;
|
||||
}));
|
||||
});
|
||||
|
||||
});
|
||||
18
gaia/tv_apps/dashboard/test/unit/.jshintrc
Executable file
18
gaia/tv_apps/dashboard/test/unit/.jshintrc
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"extends": "../../../../.jshintrc",
|
||||
"predef": [
|
||||
"assert",
|
||||
"suite",
|
||||
"test",
|
||||
"setup",
|
||||
"teardown",
|
||||
"suiteSetup",
|
||||
"suiteTeardown",
|
||||
"require",
|
||||
"requireApp",
|
||||
"sinon",
|
||||
"mocha",
|
||||
"loadBodyHTML"
|
||||
]
|
||||
}
|
||||
|
||||
70
gaia/tv_apps/dashboard/test/unit/app_widget_test.js
Executable file
70
gaia/tv_apps/dashboard/test/unit/app_widget_test.js
Executable file
|
|
@ -0,0 +1,70 @@
|
|||
/* globals AppWidget */
|
||||
|
||||
require('/js/app_widget.js');
|
||||
|
||||
|
||||
suite('AppWidget', function() {
|
||||
'use strict';
|
||||
|
||||
var subject;
|
||||
var frameData = {
|
||||
manifestURL: 'url1',
|
||||
widget: 'widgetName1',
|
||||
url: 'about:blank',
|
||||
position: 'up'
|
||||
};
|
||||
var upPanel;
|
||||
|
||||
setup(function() {
|
||||
upPanel = document.createElement('div');
|
||||
upPanel.id = 'up-panel';
|
||||
document.body.appendChild(upPanel);
|
||||
|
||||
subject = new AppWidget(frameData);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
document.body.removeChild(upPanel);
|
||||
});
|
||||
|
||||
suite('creation > ', function() {
|
||||
test('should init frame inside up-panel', function() {
|
||||
assert.equal(upPanel.firstChild, subject.iframe);
|
||||
});
|
||||
|
||||
test('should init frame with appropriate mozapp attributes', function() {
|
||||
var iframe = subject.iframe;
|
||||
assert.equal(iframe.getAttribute('mozbrowser'), 'true');
|
||||
assert.equal(iframe.getAttribute('remote'), 'true');
|
||||
assert.equal(iframe.getAttribute('mozapp'), frameData.manifestURL);
|
||||
assert.equal(iframe.src, frameData.url);
|
||||
});
|
||||
});
|
||||
|
||||
suite('toggleExpand() >', function() {
|
||||
setup(function() {
|
||||
subject.toggleExpand(true);
|
||||
});
|
||||
|
||||
test('should add hash "expand" when expanding', function() {
|
||||
var hash = subject.iframe.src.split('#')[1];
|
||||
assert.equal(hash, 'expand');
|
||||
});
|
||||
|
||||
test('should remove hash "expand" when shrinking', function() {
|
||||
subject.toggleExpand(false);
|
||||
// We have to confirm toggleExpand leaves a '#' in url to prevent reload.
|
||||
// (thus split() shoul return an array of two strings).
|
||||
var hash = subject.iframe.src.split('#')[1];
|
||||
assert.equal(hash, '');
|
||||
});
|
||||
});
|
||||
|
||||
suite('focus() > ', function () {
|
||||
test('should focus its iframe', function() {
|
||||
var spy = this.sinon.spy(subject.iframe, 'focus');
|
||||
subject.focus();
|
||||
assert.isTrue(spy.calledOnce);
|
||||
});
|
||||
});
|
||||
});
|
||||
147
gaia/tv_apps/dashboard/test/unit/dashboard_test.js
Executable file
147
gaia/tv_apps/dashboard/test/unit/dashboard_test.js
Executable file
|
|
@ -0,0 +1,147 @@
|
|||
'use strict';
|
||||
|
||||
/* global Dashboard, MocksHelper */
|
||||
|
||||
require('/bower_components/evt/index.js');
|
||||
require('/shared/js/smart-screen/key_navigation_adapter.js');
|
||||
require('mock_app_widget.js');
|
||||
require('mock_digital_clock.js');
|
||||
require('/js/dashboard.js');
|
||||
|
||||
var MocksHelperForUnitTest = new MocksHelper([
|
||||
'AppWidget',
|
||||
'DigitalClock'
|
||||
]).init();
|
||||
|
||||
suite('Dashboard', function() {
|
||||
var dashboard;
|
||||
var mainSection;
|
||||
var clock;
|
||||
|
||||
MocksHelperForUnitTest.attachTestHelpers();
|
||||
|
||||
setup(function() {
|
||||
clock = this.sinon.useFakeTimers();
|
||||
|
||||
mainSection = document.createElement('div');
|
||||
mainSection.id = 'main-section';
|
||||
document.body.appendChild(mainSection);
|
||||
|
||||
dashboard = new Dashboard();
|
||||
dashboard.init();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
document.body.removeChild(mainSection);
|
||||
});
|
||||
|
||||
test('should initialize keyNavigationAdapter and digitalClock', function() {
|
||||
assert.isDefined(dashboard.keyNavigationAdapter);
|
||||
assert.isDefined(dashboard.digitalClock);
|
||||
});
|
||||
|
||||
test('should change body dataset.activeDirection ' +
|
||||
'to up when dashboard.onMove is called with up', function() {
|
||||
dashboard.onMove('up');
|
||||
assert.equal(document.body.dataset.activeDirection, 'up');
|
||||
dashboard.onMove('down');
|
||||
assert.equal(document.body.dataset.activeDirection, '');
|
||||
});
|
||||
|
||||
test('should change body dataset.activeDirection ' +
|
||||
'to right when dashboard.onMove is called with right', function() {
|
||||
dashboard.onMove('right');
|
||||
assert.equal(document.body.dataset.activeDirection, 'right');
|
||||
dashboard.onMove('left');
|
||||
assert.equal(document.body.dataset.activeDirection, '');
|
||||
});
|
||||
|
||||
test('should change body dataset.activeDirection ' +
|
||||
'to down when dashboard.onMove is called with down', function() {
|
||||
dashboard.onMove('down');
|
||||
assert.equal(document.body.dataset.activeDirection, 'down');
|
||||
dashboard.onMove('up');
|
||||
assert.equal(document.body.dataset.activeDirection, '');
|
||||
});
|
||||
|
||||
test('should change body dataset.activeDirection ' +
|
||||
'to left when dashboard.onMove is called with left', function() {
|
||||
dashboard.onMove('left');
|
||||
assert.equal(document.body.dataset.activeDirection, 'left');
|
||||
dashboard.onMove('right');
|
||||
assert.equal(document.body.dataset.activeDirection, '');
|
||||
});
|
||||
|
||||
test('should not change body dataset.activeDirection ' +
|
||||
'when activeDirection is up and dashboard.onMove is ' +
|
||||
'called with up',function () {
|
||||
dashboard.onMove('up');
|
||||
assert.equal(document.body.dataset.activeDirection, 'up');
|
||||
dashboard.onMove('up');
|
||||
assert.equal(document.body.dataset.activeDirection, 'up');
|
||||
});
|
||||
|
||||
test('should not change body dataset.activeDirection ' +
|
||||
'when activeDirection is up and dashboard.onMove is ' +
|
||||
'called with right or left',function () {
|
||||
dashboard.onMove('up');
|
||||
assert.equal(document.body.dataset.activeDirection, 'up');
|
||||
dashboard.onMove('right');
|
||||
assert.equal(document.body.dataset.activeDirection, 'up');
|
||||
dashboard.onMove('left');
|
||||
assert.equal(document.body.dataset.activeDirection, 'up');
|
||||
});
|
||||
|
||||
test('should expand bottom widget when moving down,' +
|
||||
' then shrink when moving back', function() {
|
||||
dashboard.onMove('down');
|
||||
assert.isTrue(
|
||||
dashboard.widgets.down.toggleExpand.withArgs(true).calledOnce);
|
||||
dashboard.onMove('up');
|
||||
assert.isTrue(
|
||||
dashboard.widgets.down.toggleExpand.withArgs(false).calledOnce);
|
||||
});
|
||||
|
||||
test('should focus on bottom widget when moving down', function () {
|
||||
var mainSectionFocusSpy = this.sinon.spy(mainSection, 'focus');
|
||||
dashboard.onMove('down');
|
||||
assert.isTrue(mainSectionFocusSpy.calledOnce);
|
||||
clock.tick();
|
||||
assert.isTrue(dashboard.widgets.down.focus.calledOnce);
|
||||
});
|
||||
|
||||
suite('visibilityChange >', function() {
|
||||
function setFakeVisibility(value) {
|
||||
Object.defineProperty(document, 'visibilityState', {
|
||||
get: function(){
|
||||
return value;
|
||||
},
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
|
||||
teardown(function() {
|
||||
delete document.visibilityState;
|
||||
});
|
||||
|
||||
test('should move to center if visibility becomes hidden', function() {
|
||||
dashboard.onMove('down');
|
||||
setFakeVisibility('hidden');
|
||||
var mainSectionFocusSpy = this.sinon.spy(mainSection, 'focus');
|
||||
dashboard.onVisibilityChange();
|
||||
|
||||
assert.equal(document.body.dataset.activeDirection, '');
|
||||
assert.isTrue(mainSectionFocusSpy.calledOnce);
|
||||
});
|
||||
|
||||
test('should toggle body class "expand" with visibility', function() {
|
||||
setFakeVisibility('hidden');
|
||||
dashboard.onVisibilityChange();
|
||||
assert.isFalse(document.body.classList.contains('active'));
|
||||
|
||||
setFakeVisibility('visible');
|
||||
dashboard.onVisibilityChange();
|
||||
assert.isTrue(document.body.classList.contains('active'));
|
||||
});
|
||||
});
|
||||
});
|
||||
111
gaia/tv_apps/dashboard/test/unit/digital_clock_test.js
Executable file
111
gaia/tv_apps/dashboard/test/unit/digital_clock_test.js
Executable file
|
|
@ -0,0 +1,111 @@
|
|||
'use strict';
|
||||
|
||||
/* global DigitalClock, MockL10n */
|
||||
|
||||
// XXX: The mocked formatValue method in mock_l10n.js returns Promise.resolve
|
||||
// instead of a Promise, making it difficult to use the mock_promise.js
|
||||
// to test. So here the test code is commented out
|
||||
// until the mock_l10n.js is changed.
|
||||
//
|
||||
// require('/shared/test/unit/mocks/mock_promise.js');
|
||||
require('/shared/test/unit/mocks/mock_l10n.js');
|
||||
require('/js/digital_clock.js');
|
||||
|
||||
suite('DigitalClock', function() {
|
||||
var clock;
|
||||
var digitalClock;
|
||||
var startDate;
|
||||
var realL10n;
|
||||
|
||||
function createElement(tagName, id) {
|
||||
var element = document.createElement(tagName);
|
||||
element.id = id;
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
var elements = {
|
||||
hoursTensDigit: createElement('span', 'hours-tens-digit'),
|
||||
hoursUnitsDigit: createElement('span', 'hours-units-digit'),
|
||||
minutesTensDigit: createElement('span', 'minutes-tens-digit'),
|
||||
minutesUnitsDigit: createElement('span', 'minutes-units-digit'),
|
||||
secondsTensDigit: createElement('span', 'seconds-tens-digit'),
|
||||
secondsUnitsDigit: createElement('span', 'seconds-units-digit'),
|
||||
today: createElement('span', 'today-is'),
|
||||
greeting: createElement('span', 'greeting')
|
||||
};
|
||||
|
||||
suiteSetup(function() {
|
||||
realL10n = window.navigator.mozL10n;
|
||||
window.navigator.mozL10n = MockL10n;
|
||||
|
||||
for (var prop in elements) {
|
||||
document.body.appendChild(elements[prop]);
|
||||
}
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
var elements = document.getElementsByTagName('span');
|
||||
for (var i=0; i < elements.length; i++) {
|
||||
document.body.removeChild(elements[i]);
|
||||
}
|
||||
window.navigator.mozL10n = realL10n;
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
// this.sinon.stub(window, 'Promise', MockPromise);
|
||||
// this.sinon.spy(window.Promise, 'resolve');
|
||||
|
||||
startDate = new Date(2015, 4, 31);
|
||||
clock = this.sinon.useFakeTimers(startDate.getTime());
|
||||
digitalClock = new DigitalClock();
|
||||
digitalClock.init();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
// window.Promise.restore();
|
||||
|
||||
digitalClock.stop();
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
test('Should display current date and time once the clock is initialized',
|
||||
function() {
|
||||
clock.tick();
|
||||
assert.equal(digitalClock.date.getTime(), startDate.getTime());
|
||||
assert.equal(elements.hoursTensDigit.textContent, '0');
|
||||
assert.equal(elements.hoursUnitsDigit.textContent, '0');
|
||||
assert.equal(elements.minutesTensDigit.textContent, '0');
|
||||
assert.equal(elements.minutesUnitsDigit.textContent, '0');
|
||||
assert.equal(elements.secondsTensDigit.textContent, '0');
|
||||
assert.equal(elements.secondsUnitsDigit.textContent, '0');
|
||||
// var p0 = window.Promise.resolve.firstCall.args[0];
|
||||
// Using JSON.stringify here to match the result from the mock l10n.
|
||||
// assert.equal(elements.today.dataset.l10nArgs,
|
||||
// JSON.stringify({'date': JSON.stringify(new Date()) + 'dateFormat'}));
|
||||
});
|
||||
|
||||
test('Should clear timers after the clock is stopped', function() {
|
||||
digitalClock.stop();
|
||||
assert.isNull(digitalClock.timeoutId);
|
||||
});
|
||||
|
||||
test('Should update date and time after the clock starts', function() {
|
||||
var now;
|
||||
digitalClock.start();
|
||||
// Set the clock to 1 day, 1 hour, 1 minute and 1 second later
|
||||
// after initial date.
|
||||
clock.tick(86400000);
|
||||
now = new Date();
|
||||
// var p0 = window.Promise.resolve.firstCall.args[0];
|
||||
// assert.equal(elements.today.dataset.l10nArgs,
|
||||
// JSON.stringify({'date': JSON.stringify(now) + 'dateFormat'}));
|
||||
// Set the clock to 1 hour, 1 minute and 1 second later
|
||||
clock.tick(3600000 + 60000 + 1000);
|
||||
now = new Date();
|
||||
assert.equal(digitalClock.date.getTime(), now.getTime());
|
||||
assert.equal(elements.hoursUnitsDigit.textContent, '1');
|
||||
assert.equal(elements.minutesUnitsDigit.textContent, '1');
|
||||
assert.equal(elements.secondsUnitsDigit.textContent, '1');
|
||||
});
|
||||
});
|
||||
10
gaia/tv_apps/dashboard/test/unit/mock_app_widget.js
Executable file
10
gaia/tv_apps/dashboard/test/unit/mock_app_widget.js
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
/* globals sinon */
|
||||
'use strict';
|
||||
|
||||
|
||||
var MockAppWidget = function(config) {
|
||||
this.toggleExpand = sinon.stub();
|
||||
this.focus = sinon.stub();
|
||||
};
|
||||
MockAppWidget.prototype = {
|
||||
};
|
||||
10
gaia/tv_apps/dashboard/test/unit/mock_digital_clock.js
Executable file
10
gaia/tv_apps/dashboard/test/unit/mock_digital_clock.js
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
'use strict';
|
||||
|
||||
var MockDigitalClock = function() {
|
||||
this.init = sinon.stub();
|
||||
this.start = sinon.stub();
|
||||
this.stop = sinon.stub();
|
||||
this.generateGreeting = sinon.stub();
|
||||
};
|
||||
|
||||
MockDigitalClock.prototype = {};
|
||||
3
gaia/tv_apps/dashboard/test/unit/setup.js
Executable file
3
gaia/tv_apps/dashboard/test/unit/setup.js
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
'use strict';
|
||||
|
||||
require('/shared/test/unit/mocks/mocks_helper.js');
|
||||
Loading…
Reference in a new issue