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,17 @@
{
"extends": "../../../../.jshintrc",
"predef": [
"suite",
"test",
"setup",
"teardown",
"suiteSetup",
"suiteTeardown",
"marionette",
"marionetteScriptFinished",
"require",
"define",
"requireApp",
"sinon"
]
}

View file

@ -0,0 +1,67 @@
'use strict';
var Video = require('./lib/video'),
assert = require('assert'),
VideoOptionsView = require('./lib/video_options_view.js'),
VideoInfoView = require('./lib/video_info_view.js');
marionette('video info view', function() {
var client = marionette.client({
profile: {
prefs: {
'device.storage.enabled': true,
'device.storage.testing': true,
'device.storage.prompt.testing': true
}
},
desiredCapabilities: { raisesAccessibilityExceptions: false }
});
var app, selectors, optionsView, infoView;
setup(function() {
// Remove all files in temp device storage.
client.fileManager.removeAllFiles();
// Add file into the videos directory
client.fileManager.add({
type: 'videos',
filePath: 'test_media/Movies/elephants-dream.webm'
});
selectors = Video.Selector;
app = new Video(client);
optionsView = new VideoOptionsView(client);
infoView = new VideoInfoView(client);
});
test('open video and check video info', function() {
// tap thumbnail to open video
app.launch();
app.waitForThumbnails(1);
app.tapThumbnail(0);
app.waitForVideoToStartPlaying();
// tap screen to show menu, then tap option
app.tapOption();
assert.equal(optionsView.waitForOptionsViewDisplayed(), true);
// tap more info from options menu
optionsView.tapMoreInfo();
assert.equal(infoView.waitForInfoViewDisplayed(), true);
// check displayed values
assert.equal(infoView.displayedName, 'Elephants-dream');
assert.equal(infoView.displayedLength, '01:58');
assert.equal(infoView.displayedSize, '8.2 MB');
assert.equal(infoView.displayedType, 'webm');
var today = new Date();
assert.equal(infoView.displayedDate, today.getMonth() + 1 +
'/' + today.getDate() + '/' + today.getFullYear());
assert.equal(infoView.displayedResolution, '540x360');
// tap close, go back to the player view, check it still shows the file
infoView.tapClose();
assert.equal(app.waitForControls(), true);
assert.equal(app.videoTitle(),'Elephants-dream');
});
});

View file

@ -0,0 +1,25 @@
/* global Components,Services */
'use strict';
Components.utils.import('resource://gre/modules/Services.jsm');
Services.obs.addObserver(function(document) {
if (!document || !document.location) {
return;
}
var window = document.defaultView.wrappedJSObject;
var mozActivityData = {};
window.__defineGetter__('MozActivity', function() {
return function fakeMozActivity(data) {
mozActivityData = data;
};
});
window.__defineGetter__('getMozActivityData', function() {
return function getMozActivityData() {
return mozActivityData;
};
});
}, 'document-element-inserted', false);

View file

@ -0,0 +1,294 @@
/* global module */
'use strict';
/**
* @constructor
* @param {Marionette.Client} client Marionette client to use.
*/
function Video(client) {
this.client = client.scope({
searchTimeout: 20000
});
}
/**
* @const {string}
*/
Video.ORIGIN = 'app://video.gaiamobile.org';
module.exports = Video;
/**
* @const {Object}
*/
Video.Selector = Object.freeze({
thumbnail: 'li.thumbnail',
fullscreenView: '#player-view',
infoView: '#info-view',
thumbnailsSingleInfoButton: 'a.button.single-info-button',
thumbnailsSingleDeleteButton: 'a.button.single-delete-button',
thumbnailsSingleShareButton: 'a.button.single-share-button',
player: 'video#player',
optionsButton: '#options',
videoTitle: '#video-title',
overlay: '#overlay',
elapsedTime: '#elapsed-text',
durationText: '#duration-text',
thumbnailsSelectButton: '#thumbnails-select-button',
thumbnailSelectTop: '#thumbnail-select-top',
thumbnailsShareButton: '#thumbnails-share-button',
thumbnailsVideoButton: '#thumbnails-video-button',
thumbnailsDeleteButton: '#thumbnails-delete-button',
confirmOkButton: '.modal-dialog-confirm-ok',
playerHeader: '#player-header',
gaiaHeaderActionButton: '.action-button'
});
Video.prototype = {
/**
* Marionette client to use
* @type {Marionette.Client}
*/
client: null,
/**
* @return {Marionette.Element} Container for fullscreen view of an video.
*/
get fullscreenView() {
return this.client.findElement(Video.Selector.fullscreenView);
},
/**
* @return {Marionette.Element} Container for info view of an video.
*/
get infoView() {
return this.client.findElement(Video.Selector.infoView);
},
/**
* @return {Marionette.Element} overlay that displays message load videos
* to get started.
*/
get overlay() {
return this.client.findElement(Video.Selector.overlay);
},
/**
* @return {Marionette.Element} that share videos in selection mode.
*/
get thumbnailsShare() {
return this.client.findElement(Video.Selector.thumbnailsShareButton);
},
/**
* @return {Marionette.Element} that opens camera view.
*/
get thumbnailsVideo() {
return this.client.findElement(Video.Selector.thumbnailsVideoButton);
},
/**
* @return {Marionette.Element} that opens select view.
*/
get thumbnailsSelectButton() {
return this.client.findElement(Video.Selector.thumbnailsSelectButton);
},
/**
* @return {Marionette.Element} that delete videos in selection mode.
*/
get thumbnailsDelete() {
return this.client.findElement(Video.Selector.thumbnailsDeleteButton);
},
/**
* @return {Marionette.Element} to view info about the playing current video.
*/
get thumbnailsSingleInfo() {
return this.client.findElement(Video.Selector.thumbnailsSingleInfoButton);
},
/**
* @return {Marionette.Element} to delete the playing current video.
*/
get thumbnailsSingleDelete() {
return this.client.findElement(Video.Selector.thumbnailsSingleDeleteButton);
},
/**
* @return {Marionette.Element} to share the playing current video.
*/
get thumbnailsSingleShare() {
return this.client.findElement(Video.Selector.thumbnailsSingleShareButton);
},
/**
* @return {Marionette.Element} ok button in confirm delete files dialog.
*/
get confirmOk() {
// selector specific to the out-of-app confirm dialog
// found in System App
return this.client.findElement(Video.Selector.confirmOkButton);
},
/**
* @return {Marionette.Element} First element of all thumbnail images.
*/
get thumbnail() {
return this.client.helper.waitForElement(Video.Selector.thumbnail);
},
/**
* @return {Marionette.Element} video player element.
*/
get player() {
return this.client.helper.waitForElement(Video.Selector.player);
},
/**
* @return {Marionette.Element} video player option button element.
*/
get optionsButton() {
return this.client.helper.waitForElement(Video.Selector.optionsButton);
},
/**
* Called after tapping on a thumbnail to wait for the video to
* begin playing.
*/
waitForVideoToStartPlaying: function() {
var elapsedElem = this.client.findElement(Video.Selector.elapsedTime);
// Ensure when we tap on the player view the video controls are not
// displayed -- they are displayed when the video first plays and then
// they are automatically hidden.
//
// Wait for controls to appear as the video begins to play. Specify
// a timeout to account for the controls having already been displayed
// and auto-hidden.
this.client.waitFor(function() { return elapsedElem.displayed(); },
{ interval: 10, timeout: 5000 }
);
// Either the controls are now displayed or they have already been
// automatically hidden. In the case where they have been displayed,
// wait for them to be automatically hidden.
this.client.waitFor(function() { return !elapsedElem.displayed(); });
// Tap to display the controls and wait for them to be displayed.
this.player.tap();
this.waitForControls();
// Wait for the video to play for at least one second (to allow
// for validation checks).
this.client.waitFor(() => {
return this.getElapsedTimeSeconds() > 0;
});
},
waitForControls: function() {
return this.client.helper.waitForElement(
Video.Selector.elapsedTime).displayed();
},
/**
* Taps 'back' button on gaia-header to return from the video player view
* to the thumbnails view.
*/
back: function() {
var playerHeader = this.client.findElement(Video.Selector.playerHeader);
this.client.switchToShadowRoot(playerHeader);
var actionButton =
this.client.helper.waitForElement(Video.Selector.gaiaHeaderActionButton);
actionButton.tap();
this.client.switchToShadowRoot();
},
getElapsedTime: function() {
var elapsedTimeElem = this.client.findElement(Video.Selector.elapsedTime);
return elapsedTimeElem;
},
getElapsedTimeSeconds: function() {
return parseInt(this.getElapsedTime().text().split(':').pop());
},
videoTitle: function() {
var titleElem =
this.client.findElement(Video.Selector.videoTitle);
return titleElem.text();
},
/**
* @return {Marionette.Element} List of elements of thumbnail videos.
*/
getThumbnails: function() {
return this.client.findElements(Video.Selector.thumbnail);
},
get thumbnails() {
return this.client.findElements(Video.Selector.thumbnail);
},
tapThumbnail: function(n) {
var thumbnail = this.thumbnails[n];
thumbnail.click();
},
waitForThumbnails: function(num) {
var getThumbnails = this.getThumbnails.bind(this);
this.client.helper.waitFor(function() {
return getThumbnails().length === num;
});
},
/**
* Click select button in the video gallery view footer to enter
* selection mode to share or delete multiple videos.
*/
enterSelectionMode: function() {
this.client.helper.waitForElement(Video.Selector.thumbnailsSelectButton)
.click();
this.client.helper.waitForElement(Video.Selector.thumbnailSelectTop);
},
/**
* Click first thumbnail in gallery view to play the video
*/
playVideoFromGalleryView: function() {
this.client.helper.waitForElement(Video.Selector.thumbnail).click();
},
/**
* Select first element of thumbnail videos
* list in selection mode.
*/
selectThumbnail: function() {
var firstItem = this.client.helper.waitForElement(Video.Selector.thumbnail);
firstItem.click();
// Wait for element is shown as selected, otherwise error
this.client.waitFor(function() {
var classList = firstItem.getAttribute('class').split(' ');
return classList.indexOf('selected') > -1;
});
},
/**
* Click option button from player view and wait for button selection
*/
tapOption: function() {
this.optionsButton.click();
},
/**
* Start the Video, save the client for future ops, and wait for the
* Video to finish an initial render.
*/
launch: function() {
this.client.apps.launch(Video.ORIGIN);
this.client.apps.switchToApp(Video.ORIGIN);
// Wait for the document body to know we're really 'launched'.
this.client.helper.waitForElement('body');
}
};

View file

@ -0,0 +1,115 @@
/* global module */
(function(module) {
'use strict';
/**
* @constructor
* @param {Marionette.Client} client Marionette client to use.
*/
function VideoInfoView(client) {
this.client = client.scope({
searchTimeout: 20000
});
}
module.exports = VideoInfoView;
/**
* @const {Object}
*/
VideoInfoView.Selector = Object.freeze({
infoView: '#info-view',
infoName: '#info-name',
infoLength: '#info-length',
infoSize: '#info-size',
infoType: '#info-type',
infoDate: '#info-date',
infoResolution: '#info-resolution',
closeButton: '#info-close-button'
});
VideoInfoView.prototype = {
client: null,
/**
* @return {String} Currently displayed video name text.
*/
get displayedName() {
return this.client.findElement(
VideoInfoView.Selector.infoName).text();
},
/**
* @return {String} Currently displayed video length.
*/
get displayedLength() {
return this.client.findElement(
VideoInfoView.Selector.infoLength).text();
},
/**
* @return {String} Currently displayed video size.
*/
get displayedSize() {
var size = this.client.findElement(
VideoInfoView.Selector.infoSize).text();
return this._trimTextContent(size);
},
/**
* @return {String} Currently displayed video type.
*/
get displayedType() {
return this.client.findElement(
VideoInfoView.Selector.infoType).text();
},
/**
* @return {String} Currently displayed date (taken) for video.
*/
get displayedDate() {
return this.client.findElement(
VideoInfoView.Selector.infoDate).text();
},
/**
* @return {String} Currently displayed video resolution.
*/
get displayedResolution() {
return this.client.findElement(
VideoInfoView.Selector.infoResolution).text();
},
/**
* @return {Marionette.Element} close button.
*/
get closeBtn() {
return this.client.findElement(
VideoInfoView.Selector.closeButton);
},
// strip l10n delimiters
_trimTextContent: function(text) {
// for some reason these unicode delimiters are inserted
// in the text node
return text.trim().replace(/[\u2068\u2069]/g, '');
},
/**
* @return {Boolean} display status of info view.
*/
waitForInfoViewDisplayed: function() {
return this.client.helper.waitForElement(
VideoInfoView.Selector.infoView).displayed();
},
/**
* Close the info view and waits for the player view
*/
tapClose: function() {
this.closeBtn.click();
}
};
})(module);

View file

@ -0,0 +1,89 @@
/* global module */
(function(module) {
'use strict';
/**
* @constructor
* @param {Marionette.Client} client Marionette client to use.
*/
function VideoOptionsView(client) {
this.client = client.scope({
searchTimeout: 20000
});
}
module.exports = VideoOptionsView;
/**
* @const {Object}
*/
VideoOptionsView.Selector = Object.freeze({
optionsView: '#options-view',
shareButton: '.single-share-button',
infoButton: '.single-info-button',
deleteButton: '.single-delete-button',
cancelButton: '.gaia-menu-cancel'
});
VideoOptionsView.prototype = {
client: null,
/**
* @return {Marionette.element} share button object
*/
get shareButton() {
return this.client.findElement(
VideoOptionsView.Selector.shareButton);
},
/**
* @return {Marionette.element} more info button object
*/
get infoButton() {
return this.client.findElement(
VideoOptionsView.Selector.infoButton);
},
/**
* @return {Marionette.element} delete button object
*/
get deleteButton() {
return this.client.findElement(
VideoOptionsView.Selector.deleteButton);
},
/**
* @return {Marionette.element} cancel button object
*/
get cancelButton() {
return this.client.findElement(
VideoOptionsView.Selector.cancelButton);
},
/**
* @return {boolean} the display status of the options view
*/
waitForOptionsViewDisplayed: function() {
return this.client.helper.waitForElement(
VideoOptionsView.Selector.optionsView).displayed();
},
tapShare: function() {
this.shareButton.click();
},
tapMoreInfo: function() {
this.infoButton.click();
},
tapDelete: function() {
this.deleteButton.click();
},
tapCancel: function() {
this.cancelButton.click();
}
};
})(module);

View file

@ -0,0 +1,29 @@
'use strict';
var Video = require('./lib/video');
marionette('video overlay', function() {
var assert = require('assert');
var client = marionette.client({
profile: {
prefs: {
'device.storage.enabled': true,
'device.storage.testing': true,
'device.storage.prompt.testing': true
}
}
});
var app;
setup(function() {
// Remove all files in temp device storage.
client.fileManager.removeAllFiles();
app = new Video(client);
app.launch();
});
test('check for empty overlay', function() {
var overlay = client.helper.waitForElement(Video.Selector.overlay);
assert.ok(overlay.displayed(), 'The list should be empty');
});
});

View file

@ -0,0 +1,68 @@
'use strict';
var assert = require('assert'),
Marionette = require('marionette-client'),
Video = require('./lib/video');
marionette('play various types of videos', function() {
var app, client, actions;
client = marionette.client({
profile: {
prefs: {
'device.storage.enabled': true,
'device.storage.testing': true,
'device.storage.prompt.testing': true
}
},
desiredCapabilities: { raisesAccessibilityExceptions: false }
});
setup(function() {
// Remove all files in temp device storage.
client.fileManager.removeAllFiles();
app = new Video(client);
actions = new Marionette.Actions(client);
});
test ('test various videos', function() {
var types = ['mp4', 'ogg', 'ogv', 'webm'];
types.forEach(function(type) {
var filename = 'test_' + type + '_video.' + type;
client.fileManager.add({
type: 'videos',
filePath: 'apps/video/test/videos/' + filename
});
});
app.launch();
app.waitForThumbnails(types.length);
var thumbnails = app.getThumbnails();
for (var i = 0; i < thumbnails.length; i++) {
app.tapThumbnail(i);
app.waitForVideoToStartPlaying();
// Add some context to the output in case of test failure.
console.log('playing video:', app.videoTitle());
// Verify the video has begun playing and the
// title has been read properly.
assert(app.getElapsedTimeSeconds() > 0);
var expectedTitle = 'Test ' + types[i] + ' video';
assert(app.videoTitle(), expectedTitle);
// Go back to the thumbnail view.
app.back();
// Scroll the thumbnails a bit after each video is played
// to ensure the thumbnails are displayed when they are
// 'tapped'.
actions.flick(thumbnails[i], 0, 0, 0, -30).perform();
}
});
});

View file

@ -0,0 +1,89 @@
/* global __dirname */
'use strict';
var Video = require('./lib/video'),
assert = require('assert');
marionette('video list', function() {
var app, client, selectors;
client = marionette.client({
profile: {
prefs: {
'device.storage.enabled': true,
'device.storage.testing': true,
'device.storage.prompt.testing': true
}
}
});
function getActivityData(client) {
var data;
client.waitFor(function() {
data = client.executeScript(function() {
return window.wrappedJSObject.getMozActivityData();
});
return data;
});
return data;
}
setup(function() {
// inject MozActivity to get activity details
client.contentScript.inject(__dirname +
'/lib/mocks/mock_window_mozactivity.js');
// Remove all files in temp device storage.
client.fileManager.removeAllFiles();
// Add file into the videos directory
client.fileManager.add({
type: 'videos',
filePath: 'test_media/Movies/elephants-dream.webm'
});
selectors = Video.Selector;
app = new Video(client);
app.launch();
client.helper.waitForElement(selectors.thumbnail);
});
test('check video lists', function() {
assert.ok(app.getThumbnails().length > 0, 'the list is not empty');
assert.ok(app.getThumbnails()[0].displayed(),
'first item should be shown');
});
test('open camera app', function() {
app.thumbnailsVideo.click();
var activityObj = getActivityData(client);
assert.equal(
activityObj.name,
'record',
'the activity name should be record.'
);
assert.equal(
activityObj.data.type,
'videos',
'type in data should be videos.'
);
});
/*
test('share video', function() {
app.enterSelectionMode();
app.selectThumbnail();
app.thumbnailsShare.click();
var activityObj = getActivityData(client);
assert.equal(
activityObj.name,
'share',
'the activity name should be share.'
);
assert.equal(activityObj.data.type,
'video/*',
'type data in share activity is video/*.'
);
});
*/
});