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,27 @@
'use strict';
/* jshint node: true, mocha: true */
/* global suiteSetup */
var helper = require('helper');
suite('Gallery tests', function() {
suiteSetup(helper.cleanupWorkspace);
teardown(helper.cleanupWorkspace);
test('APP=gallery make', function(done) {
helper.exec('APP=gallery make', function(error, stdout, stderr) {
helper.checkError(error, stdout, stderr);
var zipPath = process.cwd() +
'/profile/webapps/gallery.gaiamobile.org/application.zip';
var checkList = ['manifest.webapp'];
checkList.forEach(function(filePath) {
helper.checkFilePathInZip(zipPath, filePath);
});
done();
});
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 551 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 687 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View file

@ -0,0 +1,154 @@
This directory contains images for testing the FirefoxOS Gallery app.
It also contains:
README: this file
creator.html: a simple web page that helps create test images
push.sh: A shell script for pushing the test images to your phone
in the right order and with different timestamps so that they
appear in numerical order when scanned by the Gallery app. This
script assumes that the sdcard is at /storage/sdcard0. It works
for the Flame device but may not work for others.
To test the Gallery app manually with these images, follow these steps:
1) Push the images to your phone by typing ./push.sh
2) Launch the gallery app.
3) Ensure that:
- the Gallery app scans for the test photos
- the app does not crash with an OOM
- the scanning process stops
- after the scan, there should be 66 new images in Gallery.
The first 63 and 66 display the numbers 1 to 63, 66 respectively and the
64, 65 are test patterns. For some images, the image number is not visible
in the thumbnail and you have to tap on the thumbnail to verify.
- If any of the 66 images is missing, that is a bug
- If additional images appear that may also be a bug
4) Hold the phone in portrait orientation. Tap on the first thumbnail,
and swipe through the full set of images. Ensure that:
- There is no OOM while swiping
- Images 39 to 58 are all displayed with their text right-side up
- The images are all centered on the screen
5) Turn the phone to landscape mode and swipe back in the other
direction to ensure that all of the images display correctly in this
orientation as well
6) Now swipe again from image 14 through image 33. For each one,
double-tap to zoom in and then swipe (or double tap to zoom back out
and swipe). This should not cause an OOM even if you do it as quickly
as you can.
7) Check a few special-case images:
- Image 6 has a transparent background, but it should be readable
- Images 7 and 8 should be animated (but their thumbnails won't be)
- Image 59 should change when you zoom in to it. (It has an EXIF
preview that does not match the actual image, and we use it to test
that the EXIF preview is used when available. It is a bug if this
image does not change when you zoom in.)
Here are some more details about what the various images are and what
they are intended to test.
The first batch of images tests various image types to be sure we can
display them.
01.jpg: jpeg image
02.png: png image
03.gif: gif image
04.bmp: bmp image
05.jpg: progressive jpeg
06.png: png with transparent bg
07.gif: animated gif
08.png: animated png
Next, we test that we can display PNG images of various sizes
09.png: icon 128x128
10.png: hvga 320x480
11.png: wvga 480x768
12.png: fwvga: 480x854
13.png: dvga: 640x960
The next batch of images are jpegs with sizes between 1 megapixel and
64 megapixels. Gallery should be able to scan, create thumbnails, and
display all of these images without OOMs. Of course, it is not
expected to display the big images at full resolution. It should be
possible to double-tap to zoom in on all of these images without an OOM.
14.jpg - 27.jpg: 1 to 14 mp jpeg files
28.jpg: 16mp
29.jpg: 20mp
30.jpg: 24mp
31.jpg: 32mp
32.jpg: 48mp
33.jpg: 64mp
Now we test that images of various aspect ratios can be displayed correctly
34.png: large portrait mode png
35.png: large landscape mode png
36.png: large square
37.png: wide
38.png: tall
Next we test that we can handle EXIF orientation in JPEG images
correctly. First there is a set of 8 images that should all be
displayed in portrait mode. Then 8 that should all be displayed in
landscape mode. Then four actual photos from the Tarako device that
use EXIF orientation. In all 20 of these images, the text should be
right side up and legible.
39-46.jpg: EXIF orientations 1-8, portrait
47-54.jpg: EXIF orientations 1-8, landscape
55-58.jpg: pictures from Tarako
Next is a series of JPEG images that test our use of EXIF preview
images. Image 59 has a preview image that is different than the real
image. You should see it change if you double-tap to zoom in. This
tests that we are using previews when they are valid. The remaining
four images have invalid or corrupt EXIF previews and should not
display a preview.
59.jpg: image with valid exif preview that changes when zoomed
60.jpg: exif preview is too small, should never see it (thumbnail only?)
61.jpg: exif preview has wrong aspect ratio should never see it
62.jpg: exif preview is corrupt should not crash or hang the scan
63.jpg: exif preview is corrupt and has no metadata
We end with two test patterns. These might be useful for looking at
the quality of our image downsampling.
64.png: test pattern
65.jpg: test pattern
66.jpg: JPEG image with size 90 x 40 smaller than thumbnail container
Finally, there is a set of images that should not display in the
gallery app. These will be rejected during scanning, and error
messages will be printed in the logcat. The first four images don't
display because they are too large. If they appear, it probably means
you're running a high-end device with a different maximum image size
configuration. The other images are actually corrupt and can not
display. They are here to ensure that the metadata scanner does not
choke on them.
x01.png: png that is > 5mp
x02.gif: gif that is > 5mp
x03.jpg: pjpeg that is > 3.33mp
x04.bmp: bmp that has file size > 500kb
x05.png: a zero length file
x06.jpg: a text file that has an image extension
x07.jpg: a corrupt jpeg file (a zero byte change. gecko can't display it)
x08.jpg: truncated jpeg
x09.png: truncated png
x10.gif: truncated gif
x11.bmp: truncated bmp

View file

@ -0,0 +1,60 @@
<style>
body, textarea {
font-size: 24px;
}
</style>
<script>
function create() {
var text = document.getElementById('text').value;
var lines = text.split('\n');
var width = parseInt(document.getElementById('width').value);
var height = parseInt(document.getElementById('height').value);
var background = document.getElementById('background').value
var canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
var context = canvas.getContext('2d');
var fontSize = Math.round(height / (lines.length * 2));
context.font = fontSize + 'px sans-serif';
if (background) {
context.fillStyle = background;
context.fillRect(0, 0, width, height);
}
context.fillStyle = '#000';
// context.strokeStyle = '#fff';
context.lineWidth = 2;
for(var i = 0; i < lines.length; i++) {
var line = lines[i];
var lineWidth = context.measureText(line).width;
context.fillText(line,
(width - lineWidth)/2,
height * .33 + i * 1.1 * fontSize);
/*
context.strokeText(line,
(width - lineWidth)/2,
height * .33 + i * 1.1 * fontSize);
*/
}
canvas.toBlob(function(blob) {
var url = URL.createObjectURL(blob);
document.getElementById('output').src = url;
});
}
</script>
<br><textarea id="text" rows=4 cols=40></textarea>
<br>width: <input id="width">
<br>height: <input id="height">
<br>background: <input id="background">
<br><button onclick="create()">Create</button>
<br><img id="output">

View file

@ -0,0 +1,13 @@
#!/bin/bash
# push the images to the sdcard of the attached device.
# push them in reverse numerical order and give them timestamps that
# are one second apart, so that when Gallery lists them from newest to
# oldest, we'll see image 01 first and image 65 last.
(( t = `date +%s` ))
for i in `ls ??.{jpg,png,gif,bmp} x??.{jpg,png,gif,bmp} | sort -r`; do
adb push $i /storage/sdcard0/TestImages/$i
(( t += 1 ))
adb shell touch -t $t /storage/sdcard0/TestImages/$i
done

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 KiB

View file

View file

@ -0,0 +1,7 @@
This is not actually a jpeg file
It is just a text file that was
saved with a .jpeg file extension.
Oops!
But we want to be sure that it
just gets ignored and does not
cause problems for the Gallery app.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 B

View file

@ -0,0 +1 @@
GIF89a X

After

Width:  |  Height:  |  Size: 9 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 B

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,204 @@
/* global require, marionette, setup, __dirname */
'use strict';
var Gallery = require('./lib/gallery.js'),
Fullscreen_View = require('./lib/fullscreen_view.js'),
GalleryActivityTester = require('./lib/galleryactivitytester.js'),
System = require('./lib/system.js'),
assert = require('assert');
marionette('Gallery Activity Tests', function() {
var fullscreenView, client, apps = {};
apps[GalleryActivityTester.ORIGIN] =
__dirname + '/apps/galleryactivitytester';
client = marionette.client({
profile: {
prefs: {
'device.storage.enabled': true,
'device.storage.testing': true,
'device.storage.prompt.testing': true
},
apps: apps
},
desiredCapabilities: { raisesAccessibilityExceptions: false }
});
var galleryApp, activityTesterApp, system;
var imageInfo = {
name: 'pictures/firefoxOS.png',
type: 'image/png',
size: '52458'
};
setup(function() {
// Remove all files in temp device storage.
client.fileManager.removeAllFiles();
// Add file into the pictures directory
client.fileManager.add([
{type: 'pictures',filePath: 'test_media/Pictures/firefoxOS.png'},
{type: 'pictures',
filePath: 'test_media/Pictures/firefoxOS.png',
filename: 'firefoxOS_2.png'}
]);
galleryApp = new Gallery(client);
fullscreenView = new Fullscreen_View(client);
activityTesterApp = GalleryActivityTester.create(client);
system = new System(client);
});
test('pick and open image using gallery pick and open activity', function() {
activityTesterApp.launch();
// Initiate pick activity by clicking pick image button
activityTesterApp.tapPickImageButton();
// Tap Gallery button from Systm app Action Menu
system.menuOptionButton('Gallery').tap();
// We've switched to the system app, and we're calling
// switchToApp that waits for gallery app and then switch to gallery app
client.apps.switchToApp(Gallery.ORIGIN);
// Check number of images loaded in gallery App
assert.ok(galleryApp.thumbnails.length == '2');
// Get image name of first thumbnail
var selectedImageName = galleryApp.getThumbnailFileName(0);
// Select first image from thumbnail list in gallery app
galleryApp.tapThumbnail(0);
client.waitFor(function(){
return fullscreenView.editCropCanvas.displayed();
});
fullscreenView.cropDoneButton.click();
system.switchToApp(GalleryActivityTester.ORIGIN);
client.waitFor(function(){
return activityTesterApp.pickedImageName.displayed();
});
// Compare returned blob name, type and size with the
// first image loaded in gallery app
var pickedImageName = activityTesterApp.pickedImageName.text();
assert.strictEqual(pickedImageName, selectedImageName);
var pickedImageType = activityTesterApp.pickedImageType.text();
assert.strictEqual(pickedImageType, imageInfo.type);
var pickedImageSize = activityTesterApp.pickedImageSize.text();
assert.strictEqual(pickedImageSize, imageInfo.size);
// Open the picked image using open activity
// with allowSave option as true. Tap picked
// image and invoke gallery open activity
activityTesterApp.tapPickedImage();
system.switchToApp(Gallery.ORIGIN);
// Check if the filename displayed in titlebar matches
// data sent by the initiating app
var title = fullscreenView.openActivityImageTitle.text();
assert.strictEqual(title, 'firefoxOS.png');
// Check if save button is displayed
client.helper.waitForElement(fullscreenView.openActivitySaveButton);
assert.ok(fullscreenView.openActivitySaveButton.displayed());
client.waitFor(function(){
return fullscreenView.openActivityImage.displayed();
});
// Check if displayed image src is set with blob url
assert.ok(fullscreenView.hasSrcImageBlobURL(Gallery.ORIGIN,
fullscreenView.openActivityImage));
});
test('FullScreenView > share image using share activity ', function() {
galleryApp.launch();
// Check number of images loaded in gallery App
assert.ok(galleryApp.thumbnails.length == '2');
// Get image name of first thumbnail
var selectedImageName = galleryApp.getThumbnailFileName(0);
// Select first image from thumbnail list in gallery app
galleryApp.tapThumbnail(0);
fullscreenView.shareButton.tap();
system.menuOptionButton('Gallery Activity Tester').tap();
system.switchToApp(GalleryActivityTester.ORIGIN);
client.waitFor(function(){
return activityTesterApp.sharedImageName.displayed();
});
// Compare shared blob name, type and size with the
// first image loaded in gallery app
var sharedImageName = activityTesterApp.sharedImageName.text();
assert.strictEqual(sharedImageName, selectedImageName);
var sharedImageType = activityTesterApp.sharedImageType.text();
assert.strictEqual(sharedImageType, imageInfo.type);
var sharedImageSize = activityTesterApp.sharedImageSize.text();
assert.strictEqual(sharedImageSize, imageInfo.size);
});
test('open memory backed blob using gallery open activity', function() {
activityTesterApp.launch();
// Initiate open activity by clicking open image button.
// This will generate memory backed blob using canvas
// and trigger open activity to view the blob
activityTesterApp.tapOpenImageButton();
system.switchToApp(Gallery.ORIGIN);
// Check there is no filename in title bar
var title = fullscreenView.openActivityImageTitle.text();
assert.strictEqual(title, '');
// Check save button is not displayed
assert.ok(!fullscreenView.openActivitySaveButton.displayed(),
'Save button is not displayed');
client.waitFor(function(){
return fullscreenView.openActivityImage.displayed();
});
// Check if displayed image src is set with blob url
assert.ok(fullscreenView.hasSrcImageBlobURL(Gallery.ORIGIN,
fullscreenView.openActivityImage));
});
test('SelectView > share image using share activity ', function() {
galleryApp.launch();
galleryApp.switchToSelectView();
// Check number of files that exist
assert.ok(galleryApp.thumbnails.length == '2');
galleryApp.tapThumbnail(0);
// Get image name of selected thumbnail
var selectedImageName = galleryApp.getThumbnailFileName(0);
galleryApp.thumbnailsShareButton.tap();
system.menuOptionButton('Gallery Activity Tester').tap();
system.switchToApp(GalleryActivityTester.ORIGIN);
// Check that image is shared and displayed in
// Gallery Activity Tester App
client.waitFor(function(){
return activityTesterApp.sharedImage.displayed();
});
// Compare shared blob name, type and size with the
// first image loaded in gallery app
var sharedImageName = activityTesterApp.sharedImageName.text();
assert.strictEqual(sharedImageName, selectedImageName);
var sharedImageType = activityTesterApp.sharedImageType.text();
assert.strictEqual(sharedImageType, imageInfo.type);
var sharedImageSize = activityTesterApp.sharedImageSize.text();
assert.strictEqual(sharedImageSize, imageInfo.size);
});
});

View file

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Gallery Activity Tester</title>
<script defer src="/js/galleryactivitytester.js"></script>
</head>
<body>
<div>Gallery Activity Tester</div>
<button id="open-image">Generate Blob and Open Image</button>
<button id="pick-image">Pick Image</button>
<div id="pick-activity-data">
<span class="name"></span>
<span class="type"></span>
<span class="size"></span>
<img id="picked-image"></img>
</div>
<div id="share-activity-data">
<span class="name"></span>
<span class="type"></span>
<span class="size"></span>
<img></img>
</div>
</body>
</html>

View file

@ -0,0 +1,104 @@
/* global MozActivity */
(function(window) {
'use strict';
var image_blob;
function generateBlob(width, height, type) {
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
canvas.width = width;
canvas.height = height;
context.fillStyle = 'orange';
context.fillRect (0, 0, width, height);
return new Promise(function(resolve) {
canvas.toBlob(function(blob) {
canvas.width = canvas.height = 0;
canvas = null;
resolve(blob);
}, type);
});
}
function loadImageData(id, blob) {
var imageUrl = window.URL.createObjectURL(blob);
var img = document.querySelector(id + ' > img');
img.src = imageUrl;
document.querySelector(id + ' > span.name').innerHTML = blob.name;
document.querySelector(id + ' > span.type').innerHTML = blob.type;
document.querySelector(id + ' > span.size').innerHTML = blob.size;
}
window.addEventListener('load', function() {
var pickImageButton = document.getElementById('pick-image');
pickImageButton.addEventListener('click', function() {
var activity = new MozActivity({
name: 'pick',
data: {
type: 'image/*',
}
});
activity.onsuccess = function() {
image_blob = activity.result.blob;
// Populate test app elements with returned activity data
loadImageData('#pick-activity-data', activity.result.blob);
};
activity.onerror = function() {
console.warn('pick activity error:', activity.error.name);
};
});
var pickedImage = document.getElementById('picked-image');
// Add click event handler to received image to
// trigger gallery app open activity
pickedImage.addEventListener('click', function() {
var activity = new MozActivity({
name: 'open',
data: {
type: 'image/png',
filename: 'pictures/firefoxOS.png',
blob: image_blob,
allowSave: true
}
});
activity.onerror = function() {
console.warn('open activity error:', activity.error.name);
};
});
var openImageButton = document.getElementById('open-image');
// Add click event handler to open image button to generate memory
// backed blob and open it using gallery app open activity
openImageButton.addEventListener('click', function() {
generateBlob(100, 100, 'image/jpeg').then(function(blob) {
var activity = new MozActivity({
name: 'open',
data: {
type: blob.type,
blob: blob
}
});
activity.onerror = function() {
console.warn('open activity error while opening memory backed blob:',
activity.error.name);
};
});
});
});
window.navigator.mozSetMessageHandler('activity', function(activity) {
// Populate test app elements with shared activity data
loadImageData('#share-activity-data', activity.source.data.blobs[0]);
});
})(window);

View file

@ -0,0 +1,20 @@
{
"name": "Gallery Activity Tester",
"description": "Gallery Activity Tester app",
"type": "privileged",
"launch_path": "/index.html",
"developer": {
"name": "The Gaia Team",
"url": "https://github.com/mozilla-b2g/gaia"
},
"activities": {
"share": {
"href": "/index.html#activity-share",
"filters": {
"type": ["image/*"]
},
"disposition": "window",
"returnValue": true
}
}
}

View file

@ -0,0 +1,137 @@
'use strict';
var Gallery = require('./lib/gallery.js'),
assert = require('assert');
marionette('using the gallery menu', function() {
var app, client;
client = marionette.client({
profile: {
prefs: {
'device.storage.enabled': true,
'device.storage.testing': true,
'device.storage.prompt.testing': true
}
}
});
setup(function() {
app = new Gallery(client);
});
suite('Gallery app delete single image', function() {
setup(function() {
// Remove all files in temp device storage.
client.fileManager.removeAllFiles();
// Add file into the pictures directory
client.fileManager.add([
{type: 'pictures',filePath: 'apps/gallery/test/images/01.jpg'},
{type: 'pictures',filePath: 'apps/gallery/test/images/02.png'},
{type: 'pictures',filePath: 'apps/gallery/test/images/03.gif'}
]);
});
suite('Select and delete single image', function() {
setup(function() {
app.launch();
});
test('should let you select images', function() {
// Use the 'selection' button and select the test image.
// Selected images should have an border/highlight.
app.switchToSelectView();
app.thumbnail.click();
app.isThumbnailSelected(0);
});
test('should let you delete single image', function() {
app.switchToSelectView();
app.thumbnail.click();
// Check number of selected images
assert.ok(app.thumbnailsNumberSelected.text() == '1 selected');
app.thumbnailsDeleteButton.click();
app.confirmButton.click();
// Check select header text is reset to default text
assert.ok(app.thumbnailsNumberSelected.text() == 'Select');
// Check 2 files still exist
assert.ok(app.thumbnails.length == '2');
});
});
});
suite('Gallery app delete multiple images', function() {
setup(function() {
// Remove all files in temp device storage.
client.fileManager.removeAllFiles();
// Add file into the pictures directory
client.fileManager.add([
{type: 'pictures',filePath: 'apps/gallery/test/images/01.jpg'},
{type: 'pictures',filePath: 'apps/gallery/test/images/02.png'},
{type: 'pictures',filePath: 'apps/gallery/test/images/03.gif'}
]);
});
suite('Select and delete multiple images', function() {
setup(function() {
app.launch();
});
test('should let you select images', function() {
// Use the 'selection' button and select the test images.
// Selected images should have an border/highlight.
app.switchToSelectView();
app.tapThumbnail(0);
app.tapThumbnail(1);
app.tapThumbnail(2);
app.isThumbnailSelected(0);
app.isThumbnailSelected(1);
app.isThumbnailSelected(2);
assert.ok(app.thumbnailsNumberSelected.text() == '3 selected');
});
test('should let you cancel delete images', function() {
// Use the 'selection' button and select the test images.
// Selected images should have an border/highlight.
app.switchToSelectView();
app.tapThumbnail(0);
app.tapThumbnail(1);
app.tapThumbnail(2);
// Check number of selected images
assert.ok(app.thumbnailsNumberSelected.text() == '3 selected');
app.thumbnailsDeleteButton.click();
// Cancel delete of images
app.confirmCancelButton.click();
// Check files still exist
assert.ok(app.thumbnails.length == '3');
assert.ok(app.thumbnailsNumberSelected.text() == '3 selected');
});
test('should let you delete images', function() {
// This test deletes the test image.
app.switchToSelectView();
app.tapThumbnail(0);
app.tapThumbnail(1);
app.tapThumbnail(2);
app.thumbnailsDeleteButton.click();
app.confirmButton.click();
// Wait for the overlay with 'No photos or videos' message.
var overlayView = app.overlayView;
client.helper.waitForElement(overlayView);
assert.ok(overlayView.displayed());
assert.ok(app.thumbnails.length == '0');
});
});
});
});

View file

@ -0,0 +1,124 @@
'use strict';
var Gallery = require('./lib/gallery.js'),
Fullscreen_View = require('./lib/fullscreen_view.js'),
Marionette = require('marionette-client'),
assert = require('assert');
marionette('editing an image', function() {
var app, actions, fullscreen_view, client;
client = marionette.client({
profile: {
prefs: {
'device.storage.enabled': true,
'device.storage.testing': true,
'device.storage.prompt.testing': true,
'webgl.force-enabled': true
}
}
});
setup(function() {
// Remove all files in temp device storage.
client.fileManager.removeAllFiles();
// Add file into the pictures directory
client.fileManager.add({
type: 'pictures',
filePath: 'test_media/Pictures/firefoxOS.png'
});
app = new Gallery(client);
fullscreen_view = new Fullscreen_View(client);
actions = new Marionette.Actions(client);
app.launch();
});
test('should have crop options', function() {
app.tapFirstThumbnail();
fullscreen_view.editButton.click();
fullscreen_view.editCropButton.click();
assert.ok(fullscreen_view.cropOptions.displayed());
});
test('should have exposure options', function() {
app.tapFirstThumbnail();
fullscreen_view.editButton.click();
fullscreen_view.editExposureButton.click();
assert.ok(fullscreen_view.exposureOptions.displayed());
});
test('should have effect options', function() {
app.tapFirstThumbnail();
fullscreen_view.editButton.click();
fullscreen_view.editEffectButton.click();
assert.ok(fullscreen_view.effectOptions.displayed());
});
test('should change exposure', function() {
// Changing the exposure of an image creates a new modified
// version of original.
app.tapFirstThumbnail();
fullscreen_view.editButton.click();
fullscreen_view.editExposureButton.click();
//Change Exposure and get the updated value
actions.flick(fullscreen_view.exposureSlider, 0, 0, 50, 0).perform();
var current = fullscreen_view.getExposureSliderPosition();
assert.ok(
current == 0.8125,
'Exposure slider is at updated position'
);
});
test('should crop image', function() {
// Croping an image creates a new modified version of original.
app.tapFirstThumbnail();
fullscreen_view.editButton.click();
fullscreen_view.editCropButton.click();
//Choose crop aspect portrait crop option
fullscreen_view.editCropAspectPortraitButton.click();
fullscreen_view.applyEditToolOptions();
// Enter Crop edit tool to check selected crop option
fullscreen_view.editCropButton.click();
// Check crop aspect portrait is selected
fullscreen_view.waitForCropAspectPortraitSelected();
});
test('should apply effect', function() {
app.tapFirstThumbnail();
fullscreen_view.editButton.click();
fullscreen_view.editEffectButton.click();
// Choose sepia effect edit option
fullscreen_view.editEffectSepiaButton.click();
fullscreen_view.applyEditToolOptions();
// Enter effect edit tool to check selected effect
fullscreen_view.editEffectButton.click();
// Check Sepia Effect is selected
fullscreen_view.waitForSepiaEffectSelected();
});
test('check default enhance', function() {
app.tapFirstThumbnail();
fullscreen_view.editButton.click();
// Check auto enhance is turned off
fullscreen_view.waitForAutoEnhanceButtonOff();
});
test('Apply auto enhance and cancel to exit main edit screen', function() {
app.tapFirstThumbnail();
fullscreen_view.editButton.click();
fullscreen_view.applyAutoEnhance();
// Click on cancel after applying auto enhance should take user
// out of edit screen and display image in full screen view
fullscreen_view.performEditHeaderCancelAction();
assert.ok(fullscreen_view.displayed);
});
});

View file

@ -0,0 +1,45 @@
'use strict';
var Gallery = require('./lib/gallery.js'),
assert = require('assert');
marionette('run gallery without any files', function() {
var app, client;
client = marionette.client({
profile: {
prefs: {
'device.storage.enabled': true,
'device.storage.testing': true,
'device.storage.prompt.testing': true,
}
}
});
setup(function() {
// Remove all files in temp device storage.
client.fileManager.removeAllFiles();
app = new Gallery(client);
app.launch(true);
});
test('should display empty media message', function() {
// Wait for the overlay with 'Use the Camera app to get started' message.
var overlayView = app.overlayView;
var overlayTitle = app.overlayTitle;
var overlayText = app.overlayText;
var cameraButton = app.cameraButton;
client.helper.waitForElement(overlayView);
assert.ok(overlayView.displayed());
assert.ok(overlayTitle.displayed());
assert.ok(overlayTitle.text() == 'No photos or videos');
assert.ok(overlayText.displayed());
assert.ok(overlayText.text() == 'Use the Camera app to get started.');
assert.ok(cameraButton.displayed());
assert.ok(cameraButton.text() == 'Go to Camera');
});
});

View file

@ -0,0 +1,149 @@
'use strict';
var Gallery = require('./lib/gallery.js'),
Fullscreen_View = require('./lib/fullscreen_view.js'),
Marionette = require('marionette-client'),
assert = require('assert');
marionette('the gallery', function() {
var app, client, actions, fullscreen_view;
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();
// Add file into the pictures directory
client.fileManager.add({
type: 'pictures',
filePath: 'test_media/Pictures/firefoxOS.png'
});
app = new Gallery(client);
fullscreen_view = new Fullscreen_View(client);
actions = new Marionette.Actions(client);
app.launch();
});
test('should only have one image present', function() {
// There should only be a single test image in the gallery
// after running the test suite setup.
// This mainly tests if TestCommon runs smoothly.
assert.strictEqual(app.thumbnails.length, 1);
});
test('should display an image fullscreen and go back', function() {
// You should be able to click on an image to view a fullscreen
// preview and go back by pressing the 'back' button.
app.tapFirstThumbnail();
assert.ok(fullscreen_view.displayed);
fullscreen_view.fullscreenBackButton.click();
assert.ok(app.thumbnailsView.displayed());
});
test('should flick through images in fullscreen mode', function() {
// Acquire a duplicate of an image by launching the editing
// mode and saving it.
app.tapFirstThumbnail();
client.waitFor(function() {
return fullscreen_view.displayed;
});
fullscreen_view.editButton.click();
fullscreen_view.waitForImageEditor();
fullscreen_view.waitForAutoEnhanceButtonOff();
fullscreen_view.editEnhanceButton.click();
fullscreen_view.waitForAutoEnhanceButtonOn();
client.waitFor(function() {
return fullscreen_view.editSaveButton.enabled();
});
fullscreen_view.editSaveButton.click();
client.waitFor(function() {
return app.thumbnails.length == 2;
});
assert.ok(fullscreen_view.displayed);
// go back to the thumbnail view, and click the first thumbnail
fullscreen_view.fullscreenBackButton.click();
client.waitFor(function() {
return app.thumbnailsView.displayed();
});
app.tapFirstThumbnail();
client.waitFor(function() {
return fullscreen_view.displayed;
});
// You should be able to swipe between the two in fullscreen mode.
var translateX = fullscreen_view.getFrameTranslation(
fullscreen_view.fullscreenFrame2);
assert.strictEqual(translateX, 0);
var size = fullscreen_view.fullscreenView.size();
var centerX = size.width / 2;
var centerY = size.height / 2;
actions.flick(fullscreen_view.fullscreenFrame2, centerX + 100,
centerY, centerX - 100, centerY).perform();
//Swiping centers the fullscreen view on the second frame.
translateX = fullscreen_view.getFrameTranslation(
fullscreen_view.fullscreenFrame3);
assert.strictEqual(translateX, 0);
});
test('Change orientation of displayed image in fullscreen', function() {
app.tapFirstThumbnail();
client.waitFor(function() {
return fullscreen_view.displayed;
});
var screen_width = fullscreen_view.screenWidth;
var screen_height = fullscreen_view.screenHeight;
// Checks the image blob is currently being displayed
assert.ok(fullscreen_view.hasSrcImageBlobURL(Gallery.ORIGIN,
fullscreen_view.displayedImage));
// Check that there are 5 options displayed beneath the picture
assert.equal(fullscreen_view.toolBarOptions.length, 5);
// Verify that the screen orientation is in portrait mode
assert.equal(fullscreen_view.screenOrientation, 'portrait');
assert.ok(fullscreen_view.fullScreenToolBar.displayed);
assert.equal(fullscreen_view.toolBarWidth, screen_width);
// Change the screen orientation to landscape mode and verify that
// the screen is in landscape mode
fullscreen_view.changeOrientation('landscape');
// Wait for orientation changes to propagate
client.helper.wait(300);
assert.equal(fullscreen_view.screenOrientation, 'landscape');
assert.ok(fullscreen_view.fullScreenToolBar.displayed);
assert.equal(fullscreen_view.toolBarWidth, screen_height);
// Change the screen orientation back to portrait and verify the screen
// is in portrait mode
fullscreen_view.changeOrientation('portrait');
// Wait for orientation changes to propagate
client.helper.wait(300);
assert.equal(fullscreen_view.screenOrientation, 'portrait');
assert.ok(fullscreen_view.fullScreenToolBar.displayed);
assert.equal(fullscreen_view.toolBarWidth, screen_width);
// Unlock the screen before exiting test
client.executeScript(function () {
window.wrappedJSObject.screen.mozUnlockOrientation();
});
});
});

View file

@ -0,0 +1,441 @@
/* global module */
(function(module) {
'use strict';
/**
* @constructor
* @param {Marionette.Client} client Marionette client to use.
*/
function Fullscreen_View(client) {
this.client = client.scope({
searchTimeout: 20000
});
}
module.exports = Fullscreen_View;
/**
* @const {Object}
*/
Fullscreen_View.Selector = Object.freeze({
currentImage: '#frames .current > .image-view',
fullscreenView: '#fullscreen-view',
fullscreenBackButton: '#fullscreen-back-button-tiny',
editButton: '#fullscreen-edit-button-tiny',
shareButton: '#fullscreen-share-button-tiny',
editView: '#edit-view',
editExposureButton: '#edit-exposure-button',
editCropButton: '#edit-crop-button',
editEffectButton: '#edit-effect-button',
editEnhanceButton: '#edit-enhance-button',
exposureOptions: '#exposure-slider',
cropOptions: '#edit-crop-options',
effectOptions: '#edit-effect-options',
exposureSlider: '#sliderthumb',
editCropAspectPortraitButton: '#edit-crop-aspect-portrait',
editEffectSepiaButton: '#edit-effect-sepia',
editSaveButton: '#edit-save-button',
editToolApplyButton: '#edit-tool-apply-button',
editHeader: '#edit-view gaia-header',
fullscreenFrame2: '#frame2',
fullscreenFrame3: '#frame3',
cropDoneButton: '#crop-done-button',
editCropCanvas: '#edit-crop-canvas',
openTitle: '#filename',
openImage: '#frame > .image-view',
openSaveButton: '#save',
fullScreenToolBar: '#fullscreen-toolbar',
fullScreenToolBarOptions: '#fullscreen-toolbar > a',
});
Fullscreen_View.prototype = {
client: null,
/**
* @return {Marionette.Element} Currently displayed image element.
*/
get displayedImage() {
return this.client.helper.waitForElement(
Fullscreen_View.Selector.currentImage);
},
/**
* @return {numeric} Currently displayed image width.
*/
get width() {
return this.displayedImage.size().width;
},
/**
* @return {numeric} Currently displayed image height.
*/
get height() {
return this.displayedImage.size().height;
},
/**
* @return {Marionette.Element} Container for fullscreen view of an image.
*/
get fullscreenView() {
return this.client.helper.waitForElement(
Fullscreen_View.Selector.fullscreenView);
},
/**
* @return {boolean} display status of Fullscreen View.
*/
get displayed() {
return this.client.helper.waitForElement(
Fullscreen_View.Selector.fullscreenView).displayed();
},
/**
* @return {Marionette.Element} Element to click to get back
* to thumbnail view.
*/
get fullscreenBackButton() {
return this.client.helper.waitForElement(
Fullscreen_View.Selector.fullscreenBackButton);
},
/**
* @return {Marionette.Element} Element to click for image editing mode.
*/
get editButton() {
return this.client.helper.waitForElement(
Fullscreen_View.Selector.editButton);
},
/**
* @return {Marionette.Element} Element to click for effects editing mode.
*/
get editEffectButton() {
return this.client.helper.waitForElement(
Fullscreen_View.Selector.editEffectButton);
},
/**
* @return {Marionette.Element} Element to click for the auto-enhance
* editing mode.
*/
get editEnhanceButton() {
return this.client.helper.waitForElement(
Fullscreen_View.Selector.editEnhanceButton);
},
/**
* @return {Marionette.Element} Element to click for the exposure editing
* mode.
*/
get editExposureButton() {
return this.client.helper.waitForElement(
Fullscreen_View.Selector.editExposureButton);
},
/**
* @return {Marionette.Element} Element to click for crop editing mode.
*/
get editCropButton() {
return this.client.helper.waitForElement(
Fullscreen_View.Selector.editCropButton);
},
/**
* @return {Marionette.Element} Container to host the exposure options.
*/
get exposureOptions() {
return this.client.helper.waitForElement(
Fullscreen_View.Selector.exposureOptions);
},
/**
* @return {Marionette.Element} Container to host the crop options.
*/
get cropOptions() {
return this.client.helper.waitForElement(
Fullscreen_View.Selector.cropOptions);
},
/**
* @return {Marionette.Element} Container to host the effect options.
*/
get effectOptions() {
return this.client.helper.waitForElement(
Fullscreen_View.Selector.effectOptions);
},
/**
* @return {Marionette.Element} Container to host the enhance options.
*/
get enhanceOptions() {
return this.client.helper.waitForElement(
Fullscreen_View.Selector.enhanceOptions);
},
/**
* @return {Marionette.Element}
* Element to swipe to change exposure settings.
*/
get exposureSlider() {
return this.client.helper.waitForElement(
Fullscreen_View.Selector.exposureSlider);
},
/**
* @return {Marionette.Element}
* Element to click to save changes from editing.
*/
get editSaveButton() {
return this.client.helper.waitForElement(
Fullscreen_View.Selector.editSaveButton);
},
/**
* @return {Marionette.Element} Element to apply edits in edit tool.
*/
get editToolApplyButton() {
return this.client.helper.waitForElement(
Fullscreen_View.Selector.editToolApplyButton);
},
/**
* @return {Marionette.Element} Element to click to crop an image.
*/
get editCropAspectPortraitButton() {
return this.client.helper.waitForElement(
Fullscreen_View.Selector.editCropAspectPortraitButton);
},
/**
* @return {Marionette.Element} Element to click to apply a sepia affect.
*/
get editEffectSepiaButton() {
return this.client.helper.waitForElement(
Fullscreen_View.Selector.editEffectSepiaButton);
},
/**
* @return {Marionette.Element} Container element to host fullscreen images.
*/
get fullscreenFrame2() {
return this.client.helper.waitForElement(
Fullscreen_View.Selector.fullscreenFrame2);
},
/**
* @return {Marionette.Element} Container element to host fullscreen images.
*/
get fullscreenFrame3() {
return this.client.helper.waitForElement(
Fullscreen_View.Selector.fullscreenFrame3);
},
/**
* @return {Marionette.Element} Done Button to finish crop and pick image.
*/
get cropDoneButton() {
return this.client.helper.waitForElement(
Fullscreen_View.Selector.cropDoneButton);
},
/**
* @return {Marionette.Element} edit crop canvas showing crop overlay.
*/
get editCropCanvas() {
return this.client.helper.waitForElement(
Fullscreen_View.Selector.editCropCanvas);
},
/**
* @return {Marionette.Element} Element to click for sharing image.
*/
get shareButton() {
return this.client.helper.waitForElement(
Fullscreen_View.Selector.shareButton);
},
/**
* @return {Marionette.Element} Element containing fullScreenView toolbar
*/
get fullScreenToolBar() {
return this.client.helper.waitForElement(
Fullscreen_View.Selector.fullScreenToolBar);
},
/**
* @return {numeric} fullScreenToolBar width.
*/
get toolBarWidth() {
return this.fullScreenToolBar.size().width;
},
/**
* @return {Marionette.Element} List of elements of toolbar options.
*/
get toolBarOptions() {
this.waitFor(Fullscreen_View.Selector.fullScreenToolBarOptions);
return this.client.findElements(
Fullscreen_View.Selector.fullScreenToolBarOptions);
},
/**
* @return {String} screen orientation
*/
get screenOrientation() {
var orientation = this.client.executeScript(function () {
return window.wrappedJSObject.screen.mozOrientation;
});
return orientation;
},
/**
* @return {numeric} screen width
*/
get screenWidth() {
var width = this.client.executeScript(function () {
return window.wrappedJSObject.screen.width;
});
return width;
},
/**
* @return {numeric} screen height
*/
get screenHeight() {
var height = this.client.executeScript(function () {
return window.wrappedJSObject.screen.height;
});
return height;
},
/**
* @return {Marionette.Element} element to display image opened using
* gallery app open activity.
*/
get openActivityImage() {
return this.client.helper.waitForElement(
Fullscreen_View.Selector.openImage);
},
/**
* @return {Marionette.Element} header element
* showing file name opened using gallery app open activity.
*/
get openActivityImageTitle() {
return this.client.helper.waitForElement(
Fullscreen_View.Selector.openTitle);
},
/**
* @return {Marionette.Element} save button that saves image opened using
* gallery app open activity.
*/
get openActivitySaveButton() {
return this.client.findElement(Fullscreen_View.Selector.openSaveButton);
},
/**
* Check referenced image src
* is set with the URL provided
*/
hasSrcImageBlobURL: function(appURL, image) {
var url = 'blob:' + appURL;
return image.getAttribute('src').indexOf(url) > -1;
},
/**
* Read the translateX style and return its integer value.
*/
getFrameTranslation: function(frame) {
var style = frame.getAttribute('style');
return parseInt(style.match(/.*:\s.*\((\d*).*/)[1]);
},
waitFor: function(selector) {
return this.client.helper.waitForElement(selector);
},
/**
* Wait for the image editor view to render before continuing with the tests.
*/
waitForImageEditor: function() {
this.waitFor(Fullscreen_View.Selector.editCropButton);
this.waitFor(Fullscreen_View.Selector.editEffectButton);
this.waitFor(Fullscreen_View.Selector.editExposureButton);
this.waitFor(Fullscreen_View.Selector.editEnhanceButton);
},
getExposureSliderPosition: function() {
return this.exposureSlider.text();
},
applyEditToolOptions: function() {
this.editToolApplyButton.click();
},
saveEditedImage: function() {
this.editSaveButton.click();
},
performEditHeaderCancelAction: function() {
this.waitFor(Fullscreen_View.Selector.editHeader).scriptWith(
function(header) {
var event = document.createEvent('HTMLEvents');
event.initEvent('action', true, true);
header.dispatchEvent(event);
});
},
performEditEnhanceButtonClick: function() {
this.waitFor(Fullscreen_View.Selector.editEnhanceButton).scriptWith(
function(button) {
var event = document.createEvent('HTMLEvents');
event.initEvent('click', true, true);
button.dispatchEvent(event);
});
},
applyAutoEnhance: function() {
this.client.waitFor(function() {
return this.editEnhanceButton.enabled();
}.bind(this));
this.performEditEnhanceButtonClick();
},
waitForCropAspectPortraitSelected: function() {
this.client.waitFor(function() {
return this.editCropAspectPortraitButton
.getAttribute('class').indexOf('selected') > -1;
}.bind(this));
},
waitForSepiaEffectSelected: function() {
this.client.waitFor(function() {
return this.editEffectSepiaButton
.getAttribute('class').indexOf('selected') > -1;
}.bind(this));
},
waitForAutoEnhanceButtonOn: function() {
this.client.waitFor(function() {
return this.editEnhanceButton
.getAttribute('class').split(' ').indexOf('on') > -1;
}.bind(this));
},
waitForAutoEnhanceButtonOff: function() {
this.client.waitFor(function() {
return this.editEnhanceButton
.getAttribute('class').split(' ').indexOf('on') < 0;
}.bind(this));
},
changeOrientation: function(orientation) {
this.client.executeScript(function(orientation) {
window.wrappedJSObject.screen.mozLockOrientation(orientation);
}, [orientation]);
}
};
})(module);

View file

@ -0,0 +1,202 @@
'use strict';
/* jshint node:true */
/**
* @constructor
* @param {Marionette.Client} client Marionette client to use.
*/
function Gallery(client) {
this.client = client.scope({
searchTimeout: 20000
});
}
module.exports = Gallery;
/**
* @const {string}
*/
Gallery.ORIGIN = 'app://gallery.gaiamobile.org';
/**
* @const {Object}
*/
Gallery.Selector = Object.freeze({
thumbnail: '.thumbnail',
thumbnailImage: '.thumbnailImage',
thumbnailsView: '#thumbnail-views > footer.thumbnails-list',
thumbnailsSelectButton: '#thumbnails-select-button',
thumbnailsDeleteButton: '#thumbnails-delete-button',
thumbnailsShareButton: '#thumbnails-share-button',
confirmButton: '#confirm-ok',
confirmCancelButton: '#confirm-cancel',
overlayView: '#overlay',
overlayTitle: '#overlay-title',
overlayText: '#overlay-text',
overlayCameraButton: '#overlay-camera-button',
thumbnailsNumberSelected: '#thumbnails-number-selected'
});
Gallery.prototype = {
/**
* Marionette client to use.
* @type {Marionette.Client}
*/
client: null,
/**
* @return {Marionette.Element} First thumbnail containing element
*/
get thumbnail() {
return this.client.helper.waitForElement(Gallery.Selector.thumbnail);
},
/**
* @return {Marionette.Element} List of thumbnail containing elements
*/
get thumbnails() {
return this.client.findElements(Gallery.Selector.thumbnail);
},
/**
* @return {Marionette.Element} List of thumbnail images
*/
get thumbnailsImage() {
return this.client.findElements(Gallery.Selector.thumbnailImage);
},
/**
* @return {Marionette.Element} Container for thumbnails list view.
*/
get thumbnailsView() {
return this.client.helper.waitForElement(Gallery.Selector.thumbnailsView);
},
/**
* @return {boolean} thumbnails view display status.
*/
get thumbnailsViewDisplayed() {
return this.client.helper.waitForElement(
Gallery.Selector.thumbnailsView).displayed();
},
/**
* @return {Marionette.Element} Container for different overlay messages.
*/
get overlayView() {
return this.client.helper.waitForElement(Gallery.Selector.overlayView);
},
/**
* @return {Marionette.Element} Container for overlay message title.
*/
get overlayTitle() {
return this.client.helper.waitForElement(Gallery.Selector.overlayTitle);
},
/**
* @return {Marionette.Element} Container for overlay message content.
*/
get overlayText() {
return this.client.helper.waitForElement(Gallery.Selector.overlayText);
},
/**
* @return {Marionette.Element} Container for the camera button
* when no media is found.
*/
get cameraButton() {
return this.client.helper.waitForElement(Gallery.Selector.
overlayCameraButton);
},
/**
* @return {Marionette.Element} Element to click to select thumbnails.
*/
get thumbnailsSelectButton() {
return this.client.helper.waitForElement(
Gallery.Selector.thumbnailsSelectButton);
},
/**
* @return {Marionette.Element} Element to click to delete images.
*/
get thumbnailsDeleteButton() {
return this.client.helper.waitForElement(
Gallery.Selector.thumbnailsDeleteButton);
},
/**
* @return {Marionette.Element} Element to click to share multiple images.
*/
get thumbnailsShareButton() {
return this.client.helper.waitForElement(
Gallery.Selector.thumbnailsShareButton);
},
/**
* @return {Marionette.Element} Element to click to confirm the delete dialog.
*/
get confirmButton() {
return this.client.helper.waitForElement(Gallery.Selector.confirmButton);
},
/**
* @return {Marionette.Element} Element to click to cancel the delete dialog.
*/
get confirmCancelButton() {
return this.client.helper.waitForElement(
Gallery.Selector.confirmCancelButton);
},
/**
* @return {Marionette.Element} Element to display number of selected items
*/
get thumbnailsNumberSelected() {
return this.client.helper.waitForElement(
Gallery.Selector.thumbnailsNumberSelected);
},
switchToSelectView: function() {
this.thumbnailsSelectButton.click();
this.client.waitFor(function() {
return this.thumbnailsNumberSelected.displayed();
}.bind(this));
},
tapFirstThumbnail: function() {
this.thumbnail.click();
},
tapThumbnail: function(n) {
this.thumbnails[n].click();
},
isThumbnailSelected: function(n) {
this.client.waitFor(function() {
return this.thumbnails[n].cssProperty('outline') != null;
}.bind(this));
},
getThumbnailFileName: function(n) {
return this.thumbnailsImage[n].getAttribute('data-filename');
},
/**
* Start the Gallery, save the client for future ops, and wait for the
* Gallery to finish an initial render.
*/
launch: function(nomedia) {
this.client.apps.launch(Gallery.ORIGIN);
this.client.apps.switchToApp(Gallery.ORIGIN);
// Wait for the document body to know we're really 'launched'.
this.client.helper.waitForElement('body');
// Make sure the gallery is done scanning for new content.
this.client.setSearchTimeout(1000);
// Check for thumbnail when loaded with images
if (!nomedia) {
this.client.helper.waitForElement(Gallery.Selector.thumbnail);
}
}
};

View file

@ -0,0 +1,77 @@
/* global module */
(function(module) {
'use strict';
var SELECTORS = Object.freeze({
pickImageButton: '#pick-image',
openImageButton: '#open-image',
pickedImage: '#pick-activity-data > img',
pickedImageName: '#pick-activity-data > span.name',
pickedImageType: '#pick-activity-data > span.type',
pickedImageSize: '#pick-activity-data > span.size',
sharedImage: '#share-activity-data > img',
sharedImageName: '#share-activity-data > span.name',
sharedImageType: '#share-activity-data > span.type',
sharedImageSize: '#share-activity-data > span.size'
});
var ORIGIN = 'app://gallery.activitytester.gaiamobile.org';
module.exports = {
ORIGIN: ORIGIN,
create: function(client) {
return {
get pickedImage() {
return client.helper.waitForElement(SELECTORS.pickedImage);
},
get pickedImageName() {
return client.helper.waitForElement(SELECTORS.pickedImageName);
},
get pickedImageType() {
return client.helper.waitForElement(SELECTORS.pickedImageType);
},
get pickedImageSize() {
return client.helper.waitForElement(SELECTORS.pickedImageSize);
},
get sharedImage() {
return client.helper.waitForElement(SELECTORS.sharedImage);
},
get sharedImageName() {
return client.helper.waitForElement(SELECTORS.sharedImageName);
},
get sharedImageType() {
return client.helper.waitForElement(SELECTORS.sharedImageType);
},
get sharedImageSize() {
return client.helper.waitForElement(SELECTORS.sharedImageSize);
},
launch: function() {
client.switchToFrame();
client.apps.launch(ORIGIN);
client.apps.switchToApp(ORIGIN);
},
tapPickImageButton: function() {
client.helper.waitForElement(SELECTORS.pickImageButton).tap();
},
tapOpenImageButton: function() {
client.helper.waitForElement(SELECTORS.openImageButton).tap();
},
tapPickedImage: function() {
client.helper.waitForElement(SELECTORS.pickedImage).tap();
}
};
}
};
})(module);

View file

@ -0,0 +1,49 @@
/* global module */
'use strict';
function System(client) {
this.client = client;
}
module.exports = System;
System.Selector = Object.freeze({
actionMenu: 'form[data-z-index-level="action-menu"]',
button: 'form[data-z-index-level="action-menu"] > menu > button'
});
System.prototype = {
client: null,
get actionMenu() {
// Switch to the system app first.
this.client.switchToFrame();
this.waitForOptionsToDisplay();
return this.client.findElement(System.Selector.actionMenu);
},
switchToApp: function(appOrigin) {
this.client.switchToFrame();
this.client.apps.switchToApp(appOrigin);
},
waitForOptionsToDisplay: function() {
this.client.waitFor(function() {
return this.client.helper.waitForElement(
System.Selector.button).displayed();
}.bind(this));
},
/**
* @return {Marionette.Element} menu option that has same text as appName.
*/
menuOptionButton: function(appName) {
var options = this.actionMenu.findElements(System.Selector.button);
for (var i = 0; i < options.length; i++) {
var menuOption = options[i];
if (menuOption.text() === appName) {
return menuOption;
}
}
}
};

View file

@ -0,0 +1,58 @@
'use strict';
var Gallery = require('./lib/gallery.js'),
assert = require('assert');
marionette('loading images', function() {
var app, client;
client = marionette.client({
profile: {
prefs: {
'device.storage.enabled': true,
'device.storage.testing': true,
'device.storage.prompt.testing': true
}
}
});
setup(function() {
// Remove all files in temp device storage.
client.fileManager.removeAllFiles();
// Add invalid files into the pictures directory
client.fileManager.add([
{type: 'pictures', filePath: 'apps/gallery/test/images/x01.png'},
{type: 'pictures', filePath: 'apps/gallery/test/images/x02.gif'},
{type: 'pictures', filePath: 'apps/gallery/test/images/x03.jpg'},
{type: 'pictures', filePath: 'apps/gallery/test/images/x05.png'},
{type: 'pictures', filePath: 'apps/gallery/test/images/x06.jpg'},
{type: 'pictures', filePath: 'apps/gallery/test/images/x08.jpg'},
{type: 'pictures', filePath: 'apps/gallery/test/images/x09.png'},
{type: 'pictures', filePath: 'apps/gallery/test/images/x10.gif'},
{type: 'pictures', filePath: 'apps/gallery/test/images/x11.bmp'}
]);
app = new Gallery(client);
app.launch(true);
});
test('check invalid images are not shown in thumbnail view', function() {
assert.strictEqual(app.thumbnails.length, 0);
var overlayView = app.overlayView;
client.helper.waitForElement(overlayView);
assert.ok(overlayView.displayed());
var overlayTitle = app.overlayTitle;
assert.ok(overlayTitle.displayed());
assert.ok(overlayTitle.text() == 'No photos or videos');
var overlayText = app.overlayText;
assert.ok(overlayText.displayed());
assert.ok(overlayText.text() == 'Use the Camera app to get started.');
var cameraButton = app.cameraButton;
assert.ok(cameraButton.displayed());
assert.ok(cameraButton.text() == 'Go to Camera');
});
});

View file

@ -0,0 +1,76 @@
'use strict';
var Gallery = require('./lib/gallery.js'),
Fullscreen_View = require('./lib/fullscreen_view.js'),
Marionette = require('marionette-client'),
assert = require('assert');
marionette('loading images', function() {
var app, fullscreen_view, actions, client;
client = marionette.client({
profile: {
prefs: {
'device.storage.enabled': true,
'device.storage.testing': true,
'device.storage.prompt.testing': true
}
}
});
setup(function() {
// Remove all files in temp device storage.
client.fileManager.removeAllFiles();
// Add file into the pictures directory
client.fileManager.add([
{type: 'pictures',filePath: 'apps/gallery/test/images/01.jpg'},
{type: 'pictures',filePath: 'apps/gallery/test/images/02.png'},
{type: 'pictures',filePath: 'apps/gallery/test/images/03.gif'},
{type: 'pictures',filePath: 'apps/gallery/test/images/04.bmp'},
// Progressive JPG
{type: 'pictures',filePath: 'apps/gallery/test/images/05.jpg'},
// Transparent background PNG
{type: 'pictures',filePath: 'apps/gallery/test/images/06.png'},
// Animated GIF
{type: 'pictures',filePath: 'apps/gallery/test/images/07.gif'},
// Animaged PNG
{type: 'pictures',filePath: 'apps/gallery/test/images/08.png'}
]);
app = new Gallery(client);
fullscreen_view = new Fullscreen_View(client);
actions = new Marionette.Actions(client);
app.launch();
});
test('open valid images in fullscreen view', function() {
for (var i = 0; i < 8; i++) {
app.tapThumbnail(i);
assert.ok(fullscreen_view.displayed);
var initial_width = fullscreen_view.width;
var initial_height = fullscreen_view.height;
// Checks the image blob is currently being displayed
assert.ok(fullscreen_view.hasSrcImageBlobURL(Gallery.ORIGIN,
fullscreen_view.displayedImage));
// Initially the image resolutions matches the frame size.
// if the image's original res is
// greater than the frame, it will zoom in when the user double taps.
// double tap, if the image width got increased,
// also check the displayed image height has increased (i.e. zoomed in)
actions.doubleTap(fullscreen_view.displayedImage).perform();
if (initial_width < fullscreen_view.width)
{assert.ok(initial_height < fullscreen_view.height);}
else
{assert.ok(initial_height == fullscreen_view.height);}
// return to the main thumbnail view
fullscreen_view.fullscreenBackButton.click();
assert.ok(app.thumbnailsViewDisplayed);
}
});
});

View file

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

View file

@ -0,0 +1,311 @@
'use strict';
requireApp('/gallery/js/image_processor.js');
requireApp('/gallery/js/image_processor_thread.js');
function defineTestSuite(implementationName) {
suite(implementationName, function() {
var processor;
suiteSetup(function() {
if (implementationName === 'ImageProcessorThread') {
// The path to the worker code is different in this test case
// so tweak the URL just for this test.
window.ImageProcessorThread.WORKER_URL =
'../../' + window.ImageProcessorThread.WORKER_URL;
}
processor = new window[implementationName](1024*1024);
});
var imageProcessingTests = [
{
name: 'implicit noop test',
input: [0,0,0,0, 255,255,255,0, 50, 100, 150, 0],
expected: [0,0,0,0, 255,255,255,0, 50, 100, 150, 0],
},
{
name: 'explicit noop test',
gamma: 1.0,
levels: {
red: {min: 0, max:255},
green: {min: 0, max:255},
blue: {min: 0, max:255}
},
matrix: [1,0,0,0,0,1,0,0,0,0,1,0],
input: [0,0,0,0, 255,255,255,0, 50, 100, 150, 0],
expected: [0,0,0,0, 255,255,255,0, 50, 100, 150, 0],
},
{
name: 'gamma=1.0',
gamma: 1.0,
input: [0,0,0,0, 255,255,255,0, 50, 100, 150, 0],
expected: [0,0,0,0, 255,255,255,0, 50, 100, 150, 0],
},
{
name: 'gamma=0.5',
gamma: 0.5,
input: [0,0,0,0, 255,255,255,0, 50, 100, 150, 0],
expected: [0,0,0,0, 255,255,255,0,
Math.round(255*Math.pow(50/255, 0.5)),
Math.round(255*Math.pow(100/255, 0.5)),
Math.round(255*Math.pow(150/255, 0.5)),
0]
},
{
name: 'gamma=2.0',
gamma: 2.0,
input: [0,0,0,0, 255,255,255,0, 50, 100, 150, 0],
expected: [0,0,0,0, 255,255,255,0,
Math.round(255*Math.pow(50/255, 2.0)),
Math.round(255*Math.pow(100/255, 2.0)),
Math.round(255*Math.pow(150/255, 2.0)),
0],
},
{ // should not modify the image because range too small
name: 'narrow levels',
levels: {
red: {min: 100, max:105},
green: {min: 60, max:65},
blue: {min: 200, max:205}
},
input: [0,0,0,0, 255,255,255,0, 50, 100, 150, 0],
expected: [0,0,0,0, 255,255,255,0, 50, 100, 150, 0],
},
{
name: 'no-op levels',
levels: {
red: {min: 0, max:255},
green: {min: 0, max:255},
blue: {min: 0, max:255}
},
input: [0,0,0,0, 255,255,255,0, 50, 100, 150, 0],
expected: [0,0,0,0, 255,255,255,0, 50, 100, 150, 0],
},
{
name: 'levels',
levels: {
red: {min: 0, max: 255},
green: { min: 50, max: 200},
blue: {min: 100, max: 150 }
},
input: [0,0,0,0, 255,255,255,0, 50, 100, 150, 0],
expected: [0,0,0,0, 255,255,255,0,
50, Math.round(255 * (100-50)/150), 255, 0]
},
{
name: 'levels plus gamma',
levels: {
red: {min: 0, max: 255},
green: { min: 50, max: 200},
blue: {min: 100, max: 150 }
},
gamma: 1.25,
input: [0,0,0,0, 255,255,255,0, 50, 100, 150, 0],
expected: [0,0,0,0, 255,255,255,0,
Math.round(255*Math.pow(50/255, 1.25)),
Math.round(255*Math.pow(Math.round(255*(100-50)/150)/255,
1.25)),
255, 0]
},
{
name: 'identity matrix',
matrix: [1,0,0,0,0,1,0,0,0,0,1,0],
input: [1,2,3,0],
expected: [1,2,3,0]
},
{
name: 'matrix m0',
matrix: [2,0,0,0, 0,0,0,0, 0,0,0,0],
input: [1,2,3,0],
expected: [2,0,0,0]
},
{
name: 'matrix m1',
matrix: [0,2,0,0, 0,0,0,0, 0,0,0,0],
input: [1,2,3,0],
expected: [4,0,0,0]
},
{
name: 'matrix m2',
matrix: [0,0,2,0, 0,0,0,0, 0,0,0,0],
input: [1,2,3,0],
expected: [6,0,0,0]
},
{
name: 'matrix m3',
matrix: [0,0,0,0.5, 0,0,0,0, 0,0,0,0],
input: [1,2,3,0],
expected: [128,0,0,0]
},
{
name: 'matrix m4',
matrix: [0,0,0,0, 2,0,0,0, 0,0,0,0],
input: [1,2,3,0],
expected: [0,2,0,0]
},
{
name: 'matrix m5',
matrix: [0,0,0,0, 0,2,0,0, 0,0,0,0],
input: [1,2,3,0],
expected: [0,4,0,0]
},
{
name: 'matrix m6',
matrix: [0,0,0,0, 0,0,2,0, 0,0,0,0],
input: [1,2,3,0],
expected: [0,6,0,0]
},
{
name: 'matrix m7',
matrix: [0,0,0,0, 0,0,0,0.5, 0,0,0,0],
input: [1,2,3,0],
expected: [0,128,0,0]
},
{
name: 'matrix m8',
matrix: [0,0,0,0, 0,0,0,0, 2,0,0,0],
input: [1,2,3,0],
expected: [0,0,2,0]
},
{
name: 'matrix m9',
matrix: [0,0,0,0, 0,0,0,0, 0,2,0,0],
input: [1,2,3,0],
expected: [0,0,4,0]
},
{
name: 'matrix m10',
matrix: [0,0,0,0, 0,0,0,0, 0,0,2,0],
input: [1,2,3,0],
expected: [0,0,6,0]
},
{
name: 'matrix m11',
matrix: [0,0,0,0, 0,0,0,0, 0,0,0,0.5],
input: [1,2,3,0],
expected: [0,0,128,0]
},
{
name: 'full matrix',
matrix: [1,2,3,0.1, 4,5,6,0.2, 7,8,9,0.4],
input: [1,2,3,0],
expected: [40,83,152,0]
},
{
name: 'matrix overflow',
matrix: [1,2,3,1, 4,5,6,1, 7,8,9,1],
input: [1,2,3,0],
expected: [255,255,255,0]
},
{
name: 'everything',
gamma: 0.75,
levels: {
red: {min: 20, max: 225},
green: { min: 50, max: 200},
blue: {min: 10, max: 180 }
},
matrix: [0.1,0.2,0.3,0, 0.4,0.5,0.6,0, 0.7,0.8,0.9,0],
input: [50,100,150,0],
expected: [94,212,255,0]
}
];
// Register all the tests defined in the array above
imageProcessingTests.forEach(function(t) {
test(t.name, function(done) {
if (t.gamma) {
processor.setGamma(t.gamma);
}
else {
processor.setGamma(1.0);
}
if (t.levels) {
processor.setLevels(t.levels);
}
else {
processor.setLevels(null);
}
if (t.matrix) {
processor.setMatrix(t.matrix);
}
else {
processor.setMatrix(null);
}
processor.processImage(new Uint8Array(t.input).buffer).then(
function(result) {
done(function() {
assert.deepEqual(new Uint8Array(result),
new Uint8Array(t.expected));
});
},
function(error) {
done(error);
});
});
});
test('compute histogram', function(done) {
// Generate some random input and keep track of the histogram
// we expect from that input;
var N = 16*1024;
var input = new Uint8Array(N*4);
var red = new Uint32Array(256);
var green = new Uint32Array(256);
var blue = new Uint32Array(256);
for(var i = 0; i < N; i++) {
var r = Math.floor(Math.random()*256);
var g = Math.floor(Math.random()*256);
var b = Math.floor(Math.random()*256);
input[i*4] = r;
input[i*4+1] = g;
input[i*4+2] = b;
red[r]++;
green[g]++;
blue[b]++;
}
// Now compute a histogram on that input and compare to expected
processor.computeHistogram(input.buffer).then(
function(result) {
done(function() {
assert.deepEqual(new Uint32Array(result, 0, 256), red);
assert.deepEqual(new Uint32Array(result, 256*4, 256), green);
assert.deepEqual(new Uint32Array(result, 256*8, 256), blue);
});
},
function(error) {
done(error);
});
});
});
}
defineTestSuite('ImageProcessor');
defineTestSuite('ImageProcessorThread');

View file

@ -0,0 +1,51 @@
'use strict';
/* exported MockImageEditor */
var MockImageEditor = (function() {
var MockImageEditor = function(imageBlob, container, edits, ready, croponly) {
this.imageBlob = imageBlob;
this.container = container;
this.edits = edits;
this.ready = ready;
this.croponly = croponly;
this.dest = {};
this.cropOverlayRegion = {};
setTimeout(ready);
};
MockImageEditor.prototype.showCropOverlay = sinon.spy();
MockImageEditor.prototype.setCropAspectRatio = sinon.spy();
MockImageEditor.prototype.destroy = sinon.spy();
MockImageEditor.prototype.hasBeenCropped = function() {
return (this.cropOverlayRegion.left !== 0 ||
this.cropOverlayRegion.top !== 0 ||
this.cropOverlayRegion.right !== this.dest.width ||
this.cropOverlayRegion.bottom !== this.dest.height);
};
MockImageEditor.prototype.getCropRegion = function() {
var region = this.cropOverlayRegion;
var previewRect = this.dest;
// Convert the preview crop region to fractions
var left = region.left / previewRect.width;
var right = region.right / previewRect.width;
var top = region.top / previewRect.height;
var bottom = region.bottom / previewRect.height;
return {
left: left,
top: top,
width: right - left,
height: bottom - top
};
};
sinon.spy(MockImageEditor.prototype, 'hasBeenCropped');
sinon.spy(MockImageEditor.prototype, 'getCropRegion');
return MockImageEditor;
})();

View file

@ -0,0 +1,7 @@
'use strict';
/* exported MockSpinner */
var MockSpinner = {
show: sinon.spy(),
hide: sinon.spy()
};

Some files were not shown because too many files have changed in this diff Show more