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
9
gaia/apps/sharedtest/index.html
Executable file
9
gaia/apps/sharedtest/index.html
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Shared Test Runner</title>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
24
gaia/apps/sharedtest/manifest.webapp
Executable file
24
gaia/apps/sharedtest/manifest.webapp
Executable file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"name": "Shared Tests",
|
||||
"default_locale": "en-US",
|
||||
"description": "Runs shared tests.",
|
||||
"launch_path": "/index.html",
|
||||
"permissions": {},
|
||||
"role": "system",
|
||||
"locales": {
|
||||
"en-US": {
|
||||
"name": "Shared Tests",
|
||||
"description": "Runs shared tests"
|
||||
}
|
||||
},
|
||||
"developer": {
|
||||
"url": "https://github.com/mozilla-b2g/gaia",
|
||||
"name": "The Gaia Team"
|
||||
},
|
||||
"icons": {
|
||||
"128": "/style/icons/Blank.png"
|
||||
},
|
||||
"orientation": "portrait-primary"
|
||||
}
|
||||
|
||||
|
||||
BIN
gaia/apps/sharedtest/style/icons/Blank.png
Executable file
BIN
gaia/apps/sharedtest/style/icons/Blank.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 2 KiB |
BIN
gaia/apps/sharedtest/test-data/blue.jpg
Executable file
BIN
gaia/apps/sharedtest/test-data/blue.jpg
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
BIN
gaia/apps/sharedtest/test-data/red.jpg
Executable file
BIN
gaia/apps/sharedtest/test-data/red.jpg
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
11
gaia/apps/sharedtest/test/unit/.jshintrc
Executable file
11
gaia/apps/sharedtest/test/unit/.jshintrc
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"extends": "../../../../.jshintrc",
|
||||
"predef": [
|
||||
"assert",
|
||||
"expect",
|
||||
"require",
|
||||
"requireApp",
|
||||
"sinon"
|
||||
],
|
||||
"mocha": true
|
||||
}
|
||||
47
gaia/apps/sharedtest/test/unit/accessibility_helper_test.js
Executable file
47
gaia/apps/sharedtest/test/unit/accessibility_helper_test.js
Executable file
|
|
@ -0,0 +1,47 @@
|
|||
'use strict';
|
||||
|
||||
/* global AccessibilityHelper */
|
||||
|
||||
require('/shared/js/accessibility_helper.js');
|
||||
|
||||
suite('Accessibility Helper', function() {
|
||||
|
||||
var links;
|
||||
|
||||
setup(function() {
|
||||
var element = document.createElement('div');
|
||||
element.innerHTML =
|
||||
'<li><a aria-selected="true" href="#link-0"></a></li>' +
|
||||
'<li><a aria-selected="false" href="#link-1"></a></li>' +
|
||||
'<li><a aria-selected="false" href="#link-2"></a></li>';
|
||||
links = element.querySelectorAll('a');
|
||||
});
|
||||
|
||||
function testAriaSelected(expectedAriaSelectedValues) {
|
||||
expectedAriaSelectedValues.forEach(function(value, index) {
|
||||
assert.equal(links[index].getAttribute('aria-selected'), value);
|
||||
});
|
||||
}
|
||||
|
||||
test('link 2 should be marked as selected', function() {
|
||||
AccessibilityHelper.setAriaSelected(links[1], links);
|
||||
testAriaSelected(['false', 'true', 'false']);
|
||||
});
|
||||
|
||||
test('link 3 should be marked as selected', function() {
|
||||
AccessibilityHelper.setAriaSelected(links[2], links);
|
||||
testAriaSelected(['false', 'false', 'true']);
|
||||
});
|
||||
|
||||
test('link 1 should be marked as selected', function() {
|
||||
AccessibilityHelper.setAriaSelected(links[0], links);
|
||||
testAriaSelected(['true', 'false', 'false']);
|
||||
});
|
||||
|
||||
test('only one link should be marked as selected', function() {
|
||||
AccessibilityHelper.setAriaSelected(links[1], links);
|
||||
AccessibilityHelper.setAriaSelected(links[2], links);
|
||||
testAriaSelected(['false', 'false', 'true']);
|
||||
});
|
||||
|
||||
});
|
||||
64
gaia/apps/sharedtest/test/unit/achievements_service_test.js
Executable file
64
gaia/apps/sharedtest/test/unit/achievements_service_test.js
Executable file
|
|
@ -0,0 +1,64 @@
|
|||
'use strict';
|
||||
|
||||
/* global AchievementsService, MockNavigatorSettings */
|
||||
|
||||
require('/shared/test/unit/mocks/mock_navigator_moz_settings.js');
|
||||
require('/shared/js/achievements-service.js');
|
||||
|
||||
suite('Achievements Service', function() {
|
||||
|
||||
var mockAchievement = {
|
||||
criteria: 'achievements/achievement',
|
||||
evidence: 'urn:achievement:achieved',
|
||||
name: 'Achievement',
|
||||
description: 'Achievement description',
|
||||
image: '/style/icons/Blank.png'
|
||||
};
|
||||
|
||||
var achievementsService, realMozSettings, realNotification;
|
||||
|
||||
setup(function() {
|
||||
achievementsService = new AchievementsService();
|
||||
|
||||
realMozSettings = navigator.mozSettings;
|
||||
navigator.mozSettings = MockNavigatorSettings;
|
||||
|
||||
realNotification = window.Notification;
|
||||
window.Notification = sinon.spy();
|
||||
|
||||
navigator.mozSettings.createLock().set({
|
||||
achievements: [{
|
||||
criteria: 'achievements/achievement',
|
||||
issuer: 'achievements_service_test'
|
||||
}]
|
||||
});
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
navigator.mozSettings = realMozSettings;
|
||||
window.Notification = realNotification;
|
||||
});
|
||||
|
||||
test('reward', function(done) {
|
||||
achievementsService.reward(mockAchievement).then(function() {
|
||||
var notification = window.Notification.args[0][1];
|
||||
var issuesOn = notification.tag;
|
||||
var achievement = navigator.mozSettings.createLock().get(
|
||||
'achievements').result.achievements[0];
|
||||
|
||||
sinon.assert.calledOnce(window.Notification);
|
||||
assert.isTrue(window.Notification.calledWith(mockAchievement.name));
|
||||
assert.equal(notification.body, mockAchievement.description);
|
||||
assert.ok(issuesOn);
|
||||
assert.equal(notification.icon.indexOf('data:image/png'), 0);
|
||||
|
||||
assert.equal(achievement.criteria, mockAchievement.criteria);
|
||||
assert.equal(achievement.evidence, mockAchievement.evidence);
|
||||
assert.equal(achievement.issuedOn, issuesOn);
|
||||
assert.ok(achievement.recipient);
|
||||
assert.equal(achievement.uid.indexOf('achievement'), 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
91
gaia/apps/sharedtest/test/unit/addons/match_pattern_test.js
Executable file
91
gaia/apps/sharedtest/test/unit/addons/match_pattern_test.js
Executable file
|
|
@ -0,0 +1,91 @@
|
|||
/*global MatchPattern */
|
||||
|
||||
'use strict';
|
||||
|
||||
require('/shared/js/addons/match_pattern.js');
|
||||
|
||||
suite('match pattern test > ', function() {
|
||||
|
||||
var tests = {
|
||||
pass: [
|
||||
{ url: 'http://mozilla.org', pattern: 'http://mozilla.org/' },
|
||||
{ url: 'http://mozilla.org/', pattern: 'http://mozilla.org/' },
|
||||
{ url: 'http://mozilla.org/', pattern: '*://mozilla.org/' },
|
||||
{ url: 'https://mozilla.org/', pattern: '*://mozilla.org/' },
|
||||
{ url: 'http://google.com', pattern: 'http://*.google.com/' },
|
||||
{ url: 'http://docs.google.com', pattern: 'http://*.google.com/' },
|
||||
{ url: 'http://mozilla.org:8080', pattern: 'http://mozilla.org/' },
|
||||
{ url: 'http://mozilla.org:8080', pattern: '*://mozilla.org/' },
|
||||
{ url: 'http://mozilla.org', pattern: 'http://mozilla.org/*' },
|
||||
{ url: 'http://mozilla.org/', pattern: 'http://mozilla.org/*' },
|
||||
{ url: 'http://mozilla.org/', pattern: '*://mozilla.org/*' },
|
||||
{ url: 'https://mozilla.org/', pattern: '*://mozilla.org/*' },
|
||||
{ url: 'http://google.com', pattern: 'http://*.google.com/*' },
|
||||
{ url: 'http://docs.google.com', pattern: 'http://*.google.com/*' },
|
||||
{ url: 'http://mozilla.com/abc/def', pattern: 'http://mozilla.com/*' },
|
||||
{ url: 'http://mozilla.com/abc/def', pattern: 'http://mozilla.com/a*f' },
|
||||
{ url: 'http://mozilla.com/abc/def', pattern: 'http://mozilla.com/a*' },
|
||||
{ url: 'http://mozilla.com/abc/def', pattern: 'http://mozilla.com/*f' },
|
||||
{ url: 'app://settings.gaiamobile.org/index.html',
|
||||
pattern: 'app://settings.gaiamobile.org/index.html*' },
|
||||
{ url: 'app://settings.gaiamobile.org/index.html#',
|
||||
pattern: 'app://settings.gaiamobile.org/index.html*' },
|
||||
{ url: 'file:///foo', pattern: 'file:///foo*' },
|
||||
{ url: 'file:///foo/bar.html', pattern: 'file:///foo*' },
|
||||
{ url: 'http://mozilla.org/a', pattern: '<all_urls>' },
|
||||
{ url: 'https://mozilla.org/a', pattern: '<all_urls>' },
|
||||
{ url: 'ftp://mozilla.org/a', pattern: '<all_urls>' },
|
||||
{ url: 'file:///a', pattern: '<all_urls>' },
|
||||
// Multiple patterns.
|
||||
{ url: 'http://mozilla.org', pattern: ['http://mozilla.org/'] },
|
||||
{ url: 'http://mozilla.org',
|
||||
pattern: ['http://mozilla.org/', 'http://mozilla.com/'] },
|
||||
{ url: 'http://mozilla.com',
|
||||
pattern: ['http://mozilla.org/', 'http://mozilla.com/'] }
|
||||
],
|
||||
fail: [
|
||||
// Invalid pattern.
|
||||
{ url: 'http://mozilla.org', pattern: '' },
|
||||
// Pattern must include trailing slash.
|
||||
{ url: 'http://mozilla.org', pattern: 'http://mozilla.org' },
|
||||
// Protocol not allowed.
|
||||
{ url: 'http://mozilla.org', pattern: 'gopher://wuarchive.wustl.edu/' },
|
||||
{ url: 'file://mozilla.org/', pattern: '*://mozilla.org/' },
|
||||
{ url: 'ftp://mozilla.org/', pattern: '*://mozilla.org/' },
|
||||
{ url: 'http://mozilla.com', pattern: 'http://*mozilla.com*/' },
|
||||
{ url: 'http://mozilla.com', pattern: 'http://mozilla.*/' },
|
||||
{ url: 'http://mozilla.com', pattern: 'http:/mozilla.com/' },
|
||||
{ url: 'http://mozilla.org:8080', pattern: 'http://mozilla.org:8080/' },
|
||||
{ url: 'file://mozilla.org/', pattern: '*://mozilla.org/*' },
|
||||
{ url: 'http://mozilla.com', pattern: 'http://mozilla.*/*' },
|
||||
{ url: 'http://mozilla.com/abc/def', pattern: 'http://mozilla.com/' },
|
||||
{ url: 'http://mozilla.com/abc/def', pattern: 'http://mozilla.com/*e' },
|
||||
{ url: 'http://mozilla.com/abc/def', pattern: 'http://mozilla.com/*c' },
|
||||
{ url: 'http:///a.html', pattern: 'http:///a.html' },
|
||||
{ url: 'gopher://wuarchive.wustl.edu/a', pattern: '<all_urls>' },
|
||||
{ url: 'http://mozilla.biz',
|
||||
pattern: ['http://mozilla.org/', 'http://mozilla.com/'] }
|
||||
]
|
||||
};
|
||||
|
||||
function testMatchPattern({url, pattern}) {
|
||||
var urlToMatch = new URL(url);
|
||||
var matchPattern = new MatchPattern(pattern);
|
||||
return matchPattern.matches(urlToMatch);
|
||||
}
|
||||
|
||||
test('passing URL patterns', function() {
|
||||
tests.pass.forEach(test => {
|
||||
assert.isTrue(testMatchPattern(test,
|
||||
`Expected match: ${JSON.stringify(test.pattern)}, ${test.url}`));
|
||||
});
|
||||
});
|
||||
|
||||
test('failing URL patterns', function() {
|
||||
tests.fail.forEach(test => {
|
||||
assert.isFalse(testMatchPattern(test,
|
||||
`Expected no match: ${JSON.stringify(test.pattern)}, ${test.url}`));
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
378
gaia/apps/sharedtest/test/unit/advanced_telemetry_helper_test.js
Executable file
378
gaia/apps/sharedtest/test/unit/advanced_telemetry_helper_test.js
Executable file
|
|
@ -0,0 +1,378 @@
|
|||
'use strict';
|
||||
|
||||
/* global AdvancedTelemetryHelper, MockNavigatorSettings */
|
||||
|
||||
require('/shared/js/advanced_telemetry_helper.js');
|
||||
require('/shared/js/settings_listener.js');
|
||||
requireApp('system/shared/test/unit/mocks/mock_navigator_moz_settings.js');
|
||||
|
||||
/*
|
||||
* This test suite has several sub-suites that verify that:
|
||||
* 1) The metric is formatted correctly for a counter type of histogram
|
||||
* 2) The metric is formatted correctly for a linear type of histogram
|
||||
* 3) The metric is formatted correctly for an exponential type of histogram
|
||||
* 4) The metric is formatted correctly for logging a value for all types
|
||||
* 5) Disabling and Enabling the telemetry flag setting works correctly.
|
||||
*/
|
||||
suite('AdvancedTelemetryHelper:', function() {
|
||||
var realMozSettings;
|
||||
|
||||
suiteSetup(function() {
|
||||
realMozSettings = navigator.mozSettings;
|
||||
navigator.mozSettings = MockNavigatorSettings;
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
navigator.mozSettings = realMozSettings;
|
||||
});
|
||||
|
||||
/*
|
||||
* This suite makes sure that the metrics are formatted properly.
|
||||
*/
|
||||
suite('counter metric formatting:', function() {
|
||||
var ath;
|
||||
|
||||
setup(function(done) {
|
||||
this.sinon.stub(console, 'info');
|
||||
// Suppress the error message
|
||||
this.sinon.stub(console, 'warn');
|
||||
|
||||
MockNavigatorSettings.mTriggerObservers(
|
||||
AdvancedTelemetryHelper.TELEMETRY_LEVEL_KEY,
|
||||
{ settingValue: 'Enhanced' });
|
||||
done();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
});
|
||||
|
||||
test('should call console info once', function(done) {
|
||||
ath = new AdvancedTelemetryHelper(AdvancedTelemetryHelper.HISTOGRAM_COUNT,
|
||||
'mymetric');
|
||||
sinon.assert.calledOnce(console.info);
|
||||
done();
|
||||
});
|
||||
|
||||
test('should format the console message correctly', function(done) {
|
||||
ath = new AdvancedTelemetryHelper(AdvancedTelemetryHelper.HISTOGRAM_COUNT,
|
||||
'mymetric');
|
||||
sinon.assert.calledWith(console.info, 'telemetry|mymetric|4|1|0|0');
|
||||
done();
|
||||
});
|
||||
|
||||
test('should not write format the message if missing param',
|
||||
function(done) {
|
||||
ath = new AdvancedTelemetryHelper(
|
||||
AdvancedTelemetryHelper.HISTOGRAM_COUNT);
|
||||
sinon.assert.notCalled(console.info);
|
||||
// Should call a warning message.
|
||||
sinon.assert.calledOnce(console.warn);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
suite('linear metric formatting:', function() {
|
||||
var ath;
|
||||
|
||||
setup(function(done) {
|
||||
this.sinon.stub(console, 'info');
|
||||
// Suppress the error message
|
||||
this.sinon.stub(console, 'warn');
|
||||
|
||||
MockNavigatorSettings.mTriggerObservers(
|
||||
AdvancedTelemetryHelper.TELEMETRY_LEVEL_KEY,
|
||||
{ settingValue: 'Enhanced' });
|
||||
done();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
});
|
||||
|
||||
test('should call console info once', function(done) {
|
||||
ath = new AdvancedTelemetryHelper(
|
||||
AdvancedTelemetryHelper.HISTOGRAM_LINEAR, 'mymetric', 10, 1, 10000, 5);
|
||||
sinon.assert.calledOnce(console.info);
|
||||
done();
|
||||
});
|
||||
|
||||
test('should format the message correctly with full params',
|
||||
function(done) {
|
||||
ath = new AdvancedTelemetryHelper(
|
||||
AdvancedTelemetryHelper.HISTOGRAM_LINEAR, 'mymetric', 1, 10000, 5);
|
||||
sinon.assert.calledWith(console.info, 'telemetry|mymetric|1|1|10000|5');
|
||||
done();
|
||||
});
|
||||
|
||||
test('should not write format message if missing param', function(done) {
|
||||
ath = new AdvancedTelemetryHelper();
|
||||
sinon.assert.notCalled(console.info);
|
||||
// Should call a warning message.
|
||||
sinon.assert.calledOnce(console.warn);
|
||||
done();
|
||||
});
|
||||
|
||||
test('should not write format message if 3 params sent', function(done) {
|
||||
ath = new AdvancedTelemetryHelper('mymetric', 10, 1);
|
||||
sinon.assert.notCalled(console.info);
|
||||
// Should call a warning message.
|
||||
sinon.assert.calledOnce(console.warn);
|
||||
done();
|
||||
});
|
||||
|
||||
test('should not write format message if 2 params sent', function(done) {
|
||||
ath = new AdvancedTelemetryHelper('mymetric', 10);
|
||||
sinon.assert.notCalled(console.info);
|
||||
// Should call a warning message.
|
||||
sinon.assert.calledOnce(console.warn);
|
||||
done();
|
||||
});
|
||||
|
||||
test('should handle 0 min value and correct to 1', function(done) {
|
||||
ath = new AdvancedTelemetryHelper(
|
||||
AdvancedTelemetryHelper.HISTOGRAM_LINEAR, 'mymetric', 0, 1000, 10);
|
||||
sinon.assert.calledWith(console.info, 'telemetry|mymetric|1|1|1000|10');
|
||||
done();
|
||||
});
|
||||
|
||||
test('should not allow underscores in metricname', function(done) {
|
||||
ath = new AdvancedTelemetryHelper(
|
||||
AdvancedTelemetryHelper.HISTOGRAM_LINEAR, 'my_metric', 0, 1000, 10);
|
||||
sinon.assert.calledWith(console.info, 'telemetry|my-metric|1|1|1000|10');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
suite('exponential metric formatting:', function() {
|
||||
var ath;
|
||||
|
||||
setup(function(done) {
|
||||
this.sinon.stub(console, 'info');
|
||||
// Suppress the error message
|
||||
this.sinon.stub(console, 'warn');
|
||||
|
||||
MockNavigatorSettings.mTriggerObservers(
|
||||
AdvancedTelemetryHelper.TELEMETRY_LEVEL_KEY,
|
||||
{ settingValue: 'Enhanced' });
|
||||
done();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
});
|
||||
|
||||
test('should call console info once', function(done) {
|
||||
ath = new AdvancedTelemetryHelper(
|
||||
AdvancedTelemetryHelper.HISTOGRAM_EXP, 'mymetric', 0, 10000, 5);
|
||||
sinon.assert.calledOnce(console.info);
|
||||
done();
|
||||
});
|
||||
|
||||
test('should format the message correctly with full params',
|
||||
function(done) {
|
||||
ath = new AdvancedTelemetryHelper(
|
||||
AdvancedTelemetryHelper.HISTOGRAM_EXP, 'mymetric', 1, 10000, 5);
|
||||
sinon.assert.calledWith(console.info,
|
||||
'telemetry|mymetric|0|1|10000|5');
|
||||
done();
|
||||
});
|
||||
|
||||
test('should not write format the message if missing param',
|
||||
function(done) {
|
||||
ath = new AdvancedTelemetryHelper();
|
||||
sinon.assert.notCalled(console.info);
|
||||
// Should call a warning message.
|
||||
sinon.assert.calledOnce(console.warn);
|
||||
done();
|
||||
});
|
||||
|
||||
test('should not write format message if 3 params sent', function(done) {
|
||||
ath = new AdvancedTelemetryHelper('mymetric', 10, 1);
|
||||
sinon.assert.notCalled(console.info);
|
||||
// Should call a warning message.
|
||||
sinon.assert.calledOnce(console.warn);
|
||||
done();
|
||||
});
|
||||
|
||||
test('should not write format message if 2 params sent', function(done) {
|
||||
ath = new AdvancedTelemetryHelper('mymetric', 10);
|
||||
sinon.assert.notCalled(console.info);
|
||||
// Should call a warning message.
|
||||
sinon.assert.calledOnce(console.warn);
|
||||
done();
|
||||
});
|
||||
|
||||
test('should handle 0 min value and correct to 1', function(done) {
|
||||
ath = new AdvancedTelemetryHelper(
|
||||
AdvancedTelemetryHelper.HISTOGRAM_EXP, 'mymetric', 0, 1000, 10);
|
||||
sinon.assert.calledWith(console.info, 'telemetry|mymetric|0|1|1000|10');
|
||||
done();
|
||||
});
|
||||
|
||||
test('should not allow underscores in metricname', function(done) {
|
||||
ath = new AdvancedTelemetryHelper(
|
||||
AdvancedTelemetryHelper.HISTOGRAM_EXP, 'my_metric', 0, 1000, 10);
|
||||
sinon.assert.calledWith(console.info, 'telemetry|my-metric|0|1|1000|10');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
suite('log metric formatting:', function() {
|
||||
var ath;
|
||||
|
||||
setup(function(done) {
|
||||
this.sinon.stub(console, 'info');
|
||||
// Suppress the error message
|
||||
this.sinon.stub(console, 'warn');
|
||||
|
||||
MockNavigatorSettings.mTriggerObservers(
|
||||
AdvancedTelemetryHelper.TELEMETRY_LEVEL_KEY,
|
||||
{ settingValue: 'Enhanced' });
|
||||
done();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
});
|
||||
|
||||
test('should format log for counter message properly', function(done) {
|
||||
ath = new AdvancedTelemetryHelper(
|
||||
AdvancedTelemetryHelper.HISTOGRAM_COUNT, 'mymetric');
|
||||
ath.add();
|
||||
sinon.assert.calledWith(console.info, 'telemetry|mymetric|1');
|
||||
done();
|
||||
});
|
||||
|
||||
test('should format log correctly with parameter', function(done) {
|
||||
ath = new AdvancedTelemetryHelper(
|
||||
AdvancedTelemetryHelper.HISTOGRAM_COUNT, 'mymetric');
|
||||
ath.add(2);
|
||||
sinon.assert.calledWith(console.info, 'telemetry|mymetric|1');
|
||||
done();
|
||||
});
|
||||
|
||||
test('should format log correctly for exp', function(done) {
|
||||
ath = new AdvancedTelemetryHelper(
|
||||
AdvancedTelemetryHelper.HISTOGRAM_EXP, 'mymetric', 1, 100, 10);
|
||||
ath.add(15);
|
||||
|
||||
sinon.assert.calledWith(console.info, 'telemetry|mymetric|15');
|
||||
done();
|
||||
});
|
||||
|
||||
test('should format log correctly for linear', function(done) {
|
||||
ath = new AdvancedTelemetryHelper(
|
||||
AdvancedTelemetryHelper.HISTOGRAM_LINEAR, 'mymetric', 1, 100, 10);
|
||||
ath.add(15);
|
||||
|
||||
sinon.assert.calledWith(console.info, 'telemetry|mymetric|15');
|
||||
done();
|
||||
});
|
||||
|
||||
test('should not log if no value for exp', function(done) {
|
||||
ath = new AdvancedTelemetryHelper(
|
||||
AdvancedTelemetryHelper.HISTOGRAM_EXP, 'mymetric', 1, 100, 10);
|
||||
ath.add();
|
||||
// Checking for calledOnce as it will be called once from the call
|
||||
// to .exp(...) but should not be called from the call to .log()
|
||||
sinon.assert.calledOnce(console.info);
|
||||
sinon.assert.calledOnce(console.warn);
|
||||
done();
|
||||
});
|
||||
|
||||
test('should not log if no value for linear', function(done) {
|
||||
ath = new AdvancedTelemetryHelper(
|
||||
AdvancedTelemetryHelper.HISTOGRAM_LINEAR, 'mymetric', 1, 100, 10);
|
||||
ath.add();
|
||||
// Checking for calledOnce as it will be called once from the call
|
||||
// to .linear(...) but should not be called from the call to .log()
|
||||
sinon.assert.calledOnce(console.info);
|
||||
sinon.assert.calledOnce(console.warn);
|
||||
done();
|
||||
});
|
||||
|
||||
test('should not log if null or < 1 value for exp', function(done) {
|
||||
ath = new AdvancedTelemetryHelper(
|
||||
AdvancedTelemetryHelper.HISTOGRAM_EXP, 'mymetric', 1, 100, 10);
|
||||
ath.add(null);
|
||||
ath.add(-1);
|
||||
ath.add(0);
|
||||
// Checking for calledOnce as it will be called once from the call
|
||||
// to .exp(...) but should not be called from the call to .log() with
|
||||
// invalid params
|
||||
sinon.assert.calledOnce(console.info);
|
||||
sinon.assert.calledThrice(console.warn);
|
||||
done();
|
||||
});
|
||||
|
||||
test('should not log if null or < 1 value for linear', function(done) {
|
||||
ath = new AdvancedTelemetryHelper(
|
||||
AdvancedTelemetryHelper.HISTOGRAM_LINEAR, 'mymetric', 1, 100, 10);
|
||||
ath.add(null);
|
||||
ath.add(-1);
|
||||
ath.add(0);
|
||||
// Checking for calledOnce as it will be called once from the call
|
||||
// to .linear(...) but should not be called from the call to .log() with
|
||||
// invalid params
|
||||
sinon.assert.calledOnce(console.info);
|
||||
sinon.assert.calledThrice(console.warn);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
suite('settings suite', function() {
|
||||
var ath, mockSettings, clock;
|
||||
|
||||
setup(function(done) {
|
||||
this.sinon.stub(console, 'info');
|
||||
// Suppress the error message
|
||||
this.sinon.stub(console, 'warn');
|
||||
|
||||
AdvancedTelemetryHelper.telemetryEnabled = false;
|
||||
mockSettings = MockNavigatorSettings.mSettings;
|
||||
clock = this.sinon.useFakeTimers();
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
});
|
||||
|
||||
test('should not create a message if telemetry disabled', function(done) {
|
||||
MockNavigatorSettings.mTriggerObservers(
|
||||
AdvancedTelemetryHelper.TELEMETRY_LEVEL_KEY,
|
||||
{ settingValue: 'None' });
|
||||
|
||||
ath = new AdvancedTelemetryHelper(AdvancedTelemetryHelper.HISTOGRAM_EXP,
|
||||
'mymetricexp', 0, 10000, 5);
|
||||
sinon.assert.notCalled(console.info);
|
||||
done();
|
||||
});
|
||||
|
||||
test('should start writing setting enabled at runtime', function(done) {
|
||||
// Setting should be disabled by default
|
||||
assert(AdvancedTelemetryHelper.TELEMETRY_LEVEL_KEY, 'None');
|
||||
|
||||
// Enable it
|
||||
MockNavigatorSettings.mTriggerObservers(
|
||||
AdvancedTelemetryHelper.TELEMETRY_LEVEL_KEY,
|
||||
{ settingValue: 'Enhanced' });
|
||||
|
||||
ath = new AdvancedTelemetryHelper(AdvancedTelemetryHelper.HISTOGRAM_EXP,
|
||||
'mymetric', 0, 10000, 5);
|
||||
ath.add(1);
|
||||
ath.add(2);
|
||||
|
||||
sinon.assert.calledThrice(console.info);
|
||||
|
||||
// Disable it
|
||||
MockNavigatorSettings.mTriggerObservers(
|
||||
AdvancedTelemetryHelper.TELEMETRY_LEVEL_KEY,
|
||||
{ settingValue: 'None' });
|
||||
|
||||
// Call it once more
|
||||
ath.add(5);
|
||||
|
||||
// Should not be called again as telemetry was disabled.
|
||||
sinon.assert.calledThrice(console.info);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
159
gaia/apps/sharedtest/test/unit/async_semaphore_test.js
Executable file
159
gaia/apps/sharedtest/test/unit/async_semaphore_test.js
Executable file
|
|
@ -0,0 +1,159 @@
|
|||
'use strict';
|
||||
/* global AsyncSemaphore */
|
||||
|
||||
require('/shared/js/async_semaphore.js');
|
||||
|
||||
var async_sem, async_sem_for_ctor;
|
||||
|
||||
suite('AsyncSemaphore', function() {
|
||||
suiteSetup(function() {
|
||||
});
|
||||
suiteTeardown(function() {
|
||||
});
|
||||
|
||||
suite('Constructor', function() {
|
||||
test('returns instantiated object', function() {
|
||||
var asyncSem = AsyncSemaphore();
|
||||
assert.ok(asyncSem instanceof AsyncSemaphore);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Basic methods', function() {
|
||||
var tokenContext = {
|
||||
token: 'to be done'
|
||||
},
|
||||
expectedToken = 'done';
|
||||
var fakeTask = function() {
|
||||
this.token = 'done';
|
||||
};
|
||||
|
||||
setup(function() {
|
||||
tokenContext.token = 'to be done';
|
||||
async_sem = new AsyncSemaphore();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
async_sem = null;
|
||||
});
|
||||
|
||||
test('> ctor', function() {
|
||||
async_sem_for_ctor = new AsyncSemaphore();
|
||||
assert.equal(async_sem_for_ctor.getValue(), 0);
|
||||
assert.equal(async_sem_for_ctor.getTasksLength(), 0);
|
||||
});
|
||||
|
||||
test('> v', function() {
|
||||
var expectedValue = async_sem.getValue() + 1;
|
||||
async_sem.v();
|
||||
assert.equal(async_sem.getValue(), expectedValue);
|
||||
});
|
||||
|
||||
test('> p', function() {
|
||||
var expectedValue =
|
||||
(async_sem.getValue() - 1 > 0) ? async_sem.getValue() - 1 : 0;
|
||||
async_sem.p();
|
||||
assert.equal(async_sem.getValue(), expectedValue);
|
||||
});
|
||||
|
||||
test('> wait', function() {
|
||||
async_sem.v();
|
||||
async_sem.wait(fakeTask, this);
|
||||
assert.equal(async_sem.getTasksLength(), 1);
|
||||
});
|
||||
|
||||
test('> _execute', function() {
|
||||
async_sem.v();
|
||||
async_sem.wait(fakeTask, tokenContext);
|
||||
// We only do this for testing,
|
||||
// Don't modify sempahore directly in usual case
|
||||
async_sem.semaphore = 0;
|
||||
async_sem._execute();
|
||||
assert.equal(tokenContext.token, expectedToken);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Operations', function() {
|
||||
var clock;
|
||||
var tokenContext = {
|
||||
token: 'to be done'
|
||||
},
|
||||
expectedToken = 'done',
|
||||
expectedFalseToken = 'to be done',
|
||||
expectedAnotherToken = 'done again';
|
||||
var fakeTask = function() {
|
||||
this.token = 'done';
|
||||
};
|
||||
var secondFakeTask = function() {
|
||||
this.token = 'done again';
|
||||
};
|
||||
|
||||
suiteSetup(function() {
|
||||
clock = sinon.useFakeTimers();
|
||||
});
|
||||
suiteTeardown(function() {
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
tokenContext.token = 'to be done';
|
||||
async_sem = new AsyncSemaphore();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
tokenContext.token = null;
|
||||
async_sem = null;
|
||||
});
|
||||
|
||||
test('> task should be done after a "v, wait, p" sequence',
|
||||
function() {
|
||||
async_sem.v();
|
||||
async_sem.wait(fakeTask, tokenContext);
|
||||
async_sem.p();
|
||||
assert.equal(tokenContext.token, expectedToken);
|
||||
});
|
||||
|
||||
test('> task should not be done after a "v(2), wait, p" sequence',
|
||||
function() {
|
||||
async_sem.v(2);
|
||||
async_sem.wait(fakeTask, tokenContext);
|
||||
async_sem.p();
|
||||
assert.equal(tokenContext.token, expectedFalseToken);
|
||||
});
|
||||
|
||||
test('> task should be done after a "v(2), wait, p, p" sequence',
|
||||
function() {
|
||||
async_sem.v(2);
|
||||
async_sem.wait(fakeTask, tokenContext);
|
||||
async_sem.p();
|
||||
async_sem.p();
|
||||
assert.equal(tokenContext.token, expectedToken);
|
||||
});
|
||||
|
||||
test('> tasks should be done in order after a "v, wait, wait, p" sequence',
|
||||
function() {
|
||||
async_sem.v();
|
||||
async_sem.wait(fakeTask, tokenContext);
|
||||
async_sem.wait(secondFakeTask, tokenContext);
|
||||
async_sem.p();
|
||||
assert.equal(tokenContext.token, expectedAnotherToken);
|
||||
});
|
||||
|
||||
test('> v, asynchronous p, wait', function() {
|
||||
async_sem.v();
|
||||
window.setTimeout(function() {
|
||||
async_sem.p();
|
||||
assert.equal(tokenContext.token, expectedToken);
|
||||
}, 0);
|
||||
async_sem.wait(fakeTask, tokenContext);
|
||||
assert.equal(tokenContext.token, expectedFalseToken);
|
||||
clock.tick(1);
|
||||
});
|
||||
|
||||
test('> wait() should not throw exception when pass null as arguments',
|
||||
function() {
|
||||
async_sem.v();
|
||||
async_sem.wait();
|
||||
async_sem.p();
|
||||
});
|
||||
});
|
||||
});
|
||||
115
gaia/apps/sharedtest/test/unit/async_storage_test.js
Executable file
115
gaia/apps/sharedtest/test/unit/async_storage_test.js
Executable file
|
|
@ -0,0 +1,115 @@
|
|||
'use strict';
|
||||
/* global asyncStorage */
|
||||
require('/shared/js/async_storage.js');
|
||||
|
||||
suite('asyncStorage', function() {
|
||||
test('API', function() {
|
||||
assert.typeOf(asyncStorage.length, 'function');
|
||||
assert.typeOf(asyncStorage.key, 'function');
|
||||
assert.typeOf(asyncStorage.getItem, 'function');
|
||||
assert.typeOf(asyncStorage.setItem, 'function');
|
||||
assert.typeOf(asyncStorage.removeItem, 'function');
|
||||
assert.typeOf(asyncStorage.clear, 'function');
|
||||
});
|
||||
|
||||
test('set, get, overwrite, get, remove, get', function(done) {
|
||||
// test basic set and get
|
||||
asyncStorage.setItem('foo', 'bar', function() {
|
||||
asyncStorage.getItem('foo', function(value) {
|
||||
assert.equal(value, 'bar');
|
||||
|
||||
// now test overwrite and get
|
||||
asyncStorage.setItem('foo', 'overwritten', function() {
|
||||
asyncStorage.getItem('foo', function(value) {
|
||||
assert.equal(value, 'overwritten');
|
||||
|
||||
// now test remove and get
|
||||
asyncStorage.removeItem('foo', function() {
|
||||
asyncStorage.getItem('foo', function(value) {
|
||||
assert.equal(value, null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('set and get object values', function(done) {
|
||||
var object = {
|
||||
x: 1,
|
||||
y: 'foo',
|
||||
z: true
|
||||
};
|
||||
|
||||
function next() {
|
||||
generator.next();
|
||||
}
|
||||
|
||||
var generator = (function*() {
|
||||
yield asyncStorage.setItem('myobj', object, next);
|
||||
|
||||
yield asyncStorage.getItem('myobj', function(value) {
|
||||
assert.equal(object.x, value.x);
|
||||
assert.equal(object.y, value.y);
|
||||
assert.equal(object.z, value.z);
|
||||
next();
|
||||
});
|
||||
|
||||
yield asyncStorage.removeItem('myobj', next);
|
||||
|
||||
yield asyncStorage.getItem('myobj', function(value) {
|
||||
assert.equal(value, null);
|
||||
done();
|
||||
});
|
||||
}());
|
||||
next();
|
||||
});
|
||||
|
||||
test('clear, length, key', function(done) {
|
||||
asyncStorage.clear(function() {
|
||||
asyncStorage.length(function(len) {
|
||||
// length should be 0 after clearing
|
||||
assert.equal(len, 0);
|
||||
asyncStorage.setItem('key1', 'value1', function() {
|
||||
asyncStorage.length(function(len) {
|
||||
assert.equal(len, 1);
|
||||
asyncStorage.setItem('key2', 'value2', function() {
|
||||
asyncStorage.length(function(len) {
|
||||
assert.equal(len, 2);
|
||||
asyncStorage.setItem('key3', 'value3', function() {
|
||||
asyncStorage.length(function(len) {
|
||||
assert.equal(len, 3);
|
||||
asyncStorage.key(0, function(key) {
|
||||
assert.equal(key, 'key1');
|
||||
asyncStorage.key(1, function(key) {
|
||||
assert.equal(key, 'key2');
|
||||
asyncStorage.key(2, function(key) {
|
||||
assert.equal(key, 'key3');
|
||||
asyncStorage.key(3, function(key) {
|
||||
assert.equal(key, null);
|
||||
asyncStorage.clear(function() {
|
||||
asyncStorage.key(0, function(key) {
|
||||
assert.equal(key, null);
|
||||
asyncStorage.length(function(len) {
|
||||
assert.equal(len, 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
234
gaia/apps/sharedtest/test/unit/bluetooth_helper_test.js
Executable file
234
gaia/apps/sharedtest/test/unit/bluetooth_helper_test.js
Executable file
|
|
@ -0,0 +1,234 @@
|
|||
/* global BluetoothHelper, MockMozBluetooth, MockBTAdapter */
|
||||
|
||||
'use strict';
|
||||
|
||||
require('/shared/test/unit/mocks/mock_navigator_moz_bluetooth.js');
|
||||
require('/shared/js/bluetooth_helper.js');
|
||||
|
||||
function switchReadOnlyProperty(originObject, propName, targetObj) {
|
||||
Object.defineProperty(originObject, propName, {
|
||||
configurable: true,
|
||||
get: function() { return targetObj; }
|
||||
});
|
||||
}
|
||||
|
||||
suite('bluetooth helper', function() {
|
||||
var subject;
|
||||
var realMozBluetooth;
|
||||
var sandbox = sinon.sandbox.create();
|
||||
|
||||
setup(function() {
|
||||
realMozBluetooth = navigator.mozBluetooth;
|
||||
switchReadOnlyProperty(navigator, 'mozBluetooth', MockMozBluetooth);
|
||||
|
||||
subject = new BluetoothHelper();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
switchReadOnlyProperty(navigator, 'mozBluetooth', realMozBluetooth);
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
test('queued callbacks should be called after initializing', function() {
|
||||
subject.toggleCalls();
|
||||
subject.toggleCalls();
|
||||
sandbox.spy(MockBTAdapter, 'toggleCalls');
|
||||
MockMozBluetooth.triggerOnGetAdapterSuccess();
|
||||
|
||||
assert.isTrue(MockBTAdapter.toggleCalls.calledTwice);
|
||||
});
|
||||
|
||||
['enabled', 'adapteradded'].forEach(function(evtName) {
|
||||
test('should get adapter once ' + evtName, function() {
|
||||
sandbox.spy(MockMozBluetooth, 'getDefaultAdapter');
|
||||
MockMozBluetooth.triggerEventListeners(evtName);
|
||||
|
||||
assert.isTrue(MockMozBluetooth.getDefaultAdapter.called);
|
||||
});
|
||||
});
|
||||
|
||||
suite('public functions and setters', function() {
|
||||
setup(function() {
|
||||
MockMozBluetooth.triggerOnGetAdapterSuccess();
|
||||
});
|
||||
|
||||
test('should answer waiting call', function() {
|
||||
sandbox.spy(MockBTAdapter, 'answerWaitingCall');
|
||||
subject.answerWaitingCall();
|
||||
|
||||
assert.isTrue(MockBTAdapter.answerWaitingCall.calledOnce);
|
||||
});
|
||||
|
||||
test('should ignore waiting call', function() {
|
||||
sandbox.spy(MockBTAdapter, 'ignoreWaitingCall');
|
||||
subject.ignoreWaitingCall();
|
||||
|
||||
assert.isTrue(MockBTAdapter.ignoreWaitingCall.calledOnce);
|
||||
});
|
||||
|
||||
test('should toggle calls', function() {
|
||||
sandbox.spy(MockBTAdapter, 'toggleCalls');
|
||||
subject.toggleCalls();
|
||||
|
||||
assert.isTrue(MockBTAdapter.toggleCalls.calledOnce);
|
||||
});
|
||||
|
||||
test('should get connected devices by profile', function() {
|
||||
var stubDOMReq = {result: ['profiles']};
|
||||
sandbox.stub(MockBTAdapter, 'getConnectedDevices').returns(stubDOMReq);
|
||||
var cb = sinon.stub();
|
||||
subject.getConnectedDevicesByProfile('stubProfileId', cb);
|
||||
|
||||
stubDOMReq.onsuccess();
|
||||
sinon.assert.calledOnce(cb);
|
||||
sinon.assert.calledWithExactly(cb, stubDOMReq.result);
|
||||
});
|
||||
|
||||
test('should connect to sco', function() {
|
||||
var stubDOMReq = {};
|
||||
sandbox.stub(MockBTAdapter, 'connectSco').returns(stubDOMReq);
|
||||
var cb = sinon.stub();
|
||||
subject.connectSco(cb);
|
||||
|
||||
stubDOMReq.onsuccess();
|
||||
assert.isTrue(cb.calledOnce);
|
||||
});
|
||||
|
||||
test('should disconnect from sco', function() {
|
||||
var stubDOMReq = {};
|
||||
sandbox.stub(MockBTAdapter, 'disconnectSco').returns(stubDOMReq);
|
||||
var cb = sinon.stub();
|
||||
subject.disconnectSco(cb);
|
||||
|
||||
stubDOMReq.onsuccess();
|
||||
assert.isTrue(cb.calledOnce);
|
||||
});
|
||||
|
||||
test('should set callback on onhfpstatuschanged', function() {
|
||||
var stubFunc = this.sinon.stub();
|
||||
subject.onhfpstatuschanged = stubFunc;
|
||||
|
||||
assert.equal(MockBTAdapter.onhfpstatuschanged, stubFunc);
|
||||
});
|
||||
|
||||
test('should set callback on onscostatuschanged', function() {
|
||||
var stubFunc = this.sinon.stub();
|
||||
subject.onscostatuschanged = stubFunc;
|
||||
|
||||
assert.equal(MockBTAdapter.onscostatuschanged, stubFunc);
|
||||
});
|
||||
|
||||
test('should set callback on ona2dpstatuschanged', function() {
|
||||
var stubFunc = this.sinon.stub();
|
||||
subject.ona2dpstatuschanged = stubFunc;
|
||||
|
||||
assert.equal(MockBTAdapter.ona2dpstatuschanged, stubFunc);
|
||||
});
|
||||
|
||||
test('should set callback on onrequestmediaplaystatus', function() {
|
||||
var stubFunc = this.sinon.stub();
|
||||
subject.onrequestmediaplaystatus = stubFunc;
|
||||
|
||||
assert.equal(MockBTAdapter.onrequestmediaplaystatus, stubFunc);
|
||||
});
|
||||
|
||||
test('should set callback on onpairedstatuschanged', function() {
|
||||
var stubFunc = this.sinon.stub();
|
||||
subject.onpairedstatuschanged = stubFunc;
|
||||
|
||||
assert.equal(MockBTAdapter.onpairedstatuschanged, stubFunc);
|
||||
});
|
||||
|
||||
// pairing methods test
|
||||
test('should set pairing confirmation', function() {
|
||||
var address = '00:11:22:AA:BB:CC';
|
||||
var confirmed = true;
|
||||
sandbox.spy(MockBTAdapter, 'setPairingConfirmation');
|
||||
subject.setPairingConfirmation(address, confirmed);
|
||||
|
||||
assert.isTrue(MockBTAdapter.setPairingConfirmation.calledWith(address,
|
||||
confirmed));
|
||||
});
|
||||
|
||||
test('should set pin code', function() {
|
||||
var address = '00:11:22:AA:BB:CC';
|
||||
var pincode = 'SixteenTxtLength';
|
||||
sandbox.spy(MockBTAdapter, 'setPinCode');
|
||||
subject.setPinCode(address, pincode);
|
||||
|
||||
assert.isTrue(MockBTAdapter.setPinCode.calledWith(address, pincode));
|
||||
});
|
||||
|
||||
test('should set pass key', function() {
|
||||
var address = '00:11:22:AA:BB:CC';
|
||||
var passkey = 123456;
|
||||
sandbox.spy(MockBTAdapter, 'setPasskey');
|
||||
subject.setPasskey(address, passkey);
|
||||
|
||||
assert.isTrue(MockBTAdapter.setPasskey.calledWith(address, passkey));
|
||||
});
|
||||
|
||||
test('should check sco connected state', function() {
|
||||
var stubDOMReq = {};
|
||||
sandbox.stub(MockBTAdapter, 'isScoConnected').returns(stubDOMReq);
|
||||
var cb = sinon.stub();
|
||||
var errorcb = sinon.stub();
|
||||
subject.isScoConnected(cb, errorcb);
|
||||
|
||||
stubDOMReq.onsuccess();
|
||||
assert.ok(cb.called);
|
||||
stubDOMReq.onerror();
|
||||
assert.ok(errorcb.called);
|
||||
});
|
||||
|
||||
test('should send media meta-data', function() {
|
||||
var stubDOMReq = {};
|
||||
sandbox.stub(MockBTAdapter, 'sendMediaMetaData').returns(stubDOMReq);
|
||||
var cb = sinon.stub();
|
||||
var errorcb = sinon.stub();
|
||||
subject.sendMediaMetaData({}, cb, errorcb);
|
||||
|
||||
stubDOMReq.onsuccess();
|
||||
assert.ok(cb.called);
|
||||
stubDOMReq.onerror();
|
||||
assert.ok(errorcb.called);
|
||||
});
|
||||
|
||||
test('should send media play status', function() {
|
||||
var stubDOMReq = {};
|
||||
sandbox.stub(MockBTAdapter, 'sendMediaPlayStatus').returns(stubDOMReq);
|
||||
var cb = sinon.stub();
|
||||
var errorcb = sinon.stub();
|
||||
subject.sendMediaPlayStatus({}, cb, errorcb);
|
||||
|
||||
stubDOMReq.onsuccess();
|
||||
assert.ok(cb.called);
|
||||
stubDOMReq.onerror();
|
||||
assert.ok(errorcb.called);
|
||||
});
|
||||
|
||||
// get device information test
|
||||
test('should get paired devices', function() {
|
||||
var stubDOMReq =
|
||||
{result: [{name: 'device-01', address: '00:11:22:AA:BB:CC'},
|
||||
{name: 'device-02', address: '00:11:22:AA:BB:DD'}]};
|
||||
sandbox.stub(MockBTAdapter, 'getPairedDevices').returns(stubDOMReq);
|
||||
var cb = sinon.stub();
|
||||
subject.getPairedDevices(cb);
|
||||
|
||||
stubDOMReq.onsuccess();
|
||||
sinon.assert.calledOnce(cb);
|
||||
sinon.assert.calledWithExactly(cb, stubDOMReq.result);
|
||||
});
|
||||
|
||||
test('should get address', function() {
|
||||
var mockAddress = '00:11:22:AA:BB:CC';
|
||||
switchReadOnlyProperty(MockBTAdapter, 'address', mockAddress);
|
||||
var cb = sinon.stub();
|
||||
subject.getAddress(cb);
|
||||
|
||||
sinon.assert.calledOnce(cb);
|
||||
sinon.assert.calledWithExactly(cb, mockAddress);
|
||||
});
|
||||
});
|
||||
});
|
||||
174
gaia/apps/sharedtest/test/unit/bluetooth_helper_v2_test.js
Executable file
174
gaia/apps/sharedtest/test/unit/bluetooth_helper_v2_test.js
Executable file
|
|
@ -0,0 +1,174 @@
|
|||
/* global BluetoothHelper, MockMozBluetooth, MockBTAdapter */
|
||||
|
||||
'use strict';
|
||||
|
||||
require('/shared/test/unit/mocks/mock_navigator_moz_bluetooth_v2.js');
|
||||
require('/shared/js/bluetooth_helper.js');
|
||||
|
||||
function switchReadOnlyProperty(originObject, propName, targetObj) {
|
||||
Object.defineProperty(originObject, propName, {
|
||||
configurable: true,
|
||||
get: function() { return targetObj; }
|
||||
});
|
||||
}
|
||||
|
||||
suite('bluetooth helper', function() {
|
||||
var subject;
|
||||
var realMozBluetooth;
|
||||
var sandbox = sinon.sandbox.create();
|
||||
|
||||
setup(function() {
|
||||
realMozBluetooth = navigator.mozBluetooth;
|
||||
switchReadOnlyProperty(navigator, 'mozBluetooth', MockMozBluetooth);
|
||||
|
||||
subject = new BluetoothHelper();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
switchReadOnlyProperty(navigator, 'mozBluetooth', realMozBluetooth);
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
test('queued callbacks should be called after initializing', function() {
|
||||
sandbox.spy(MockBTAdapter, 'toggleCalls');
|
||||
subject.toggleCalls();
|
||||
subject.toggleCalls();
|
||||
|
||||
assert.isTrue(MockBTAdapter.toggleCalls.calledTwice);
|
||||
});
|
||||
|
||||
suite('public functions and setters', function() {
|
||||
test('should able to enable bluetooth', function() {
|
||||
sandbox.spy(MockBTAdapter, 'enable');
|
||||
subject.enable();
|
||||
|
||||
assert.isTrue(MockBTAdapter.enable.calledOnce);
|
||||
});
|
||||
|
||||
test('should able to disable bluetooth', function() {
|
||||
sandbox.spy(MockBTAdapter, 'disable');
|
||||
subject.disable();
|
||||
|
||||
assert.isTrue(MockBTAdapter.disable.calledOnce);
|
||||
});
|
||||
|
||||
test('should answer waiting call', function() {
|
||||
sandbox.spy(MockBTAdapter, 'answerWaitingCall');
|
||||
subject.answerWaitingCall();
|
||||
|
||||
assert.isTrue(MockBTAdapter.answerWaitingCall.calledOnce);
|
||||
});
|
||||
|
||||
test('should ignore waiting call', function() {
|
||||
sandbox.spy(MockBTAdapter, 'ignoreWaitingCall');
|
||||
subject.ignoreWaitingCall();
|
||||
|
||||
assert.isTrue(MockBTAdapter.ignoreWaitingCall.calledOnce);
|
||||
});
|
||||
|
||||
test('should toggle calls', function() {
|
||||
sandbox.spy(MockBTAdapter, 'toggleCalls');
|
||||
subject.toggleCalls();
|
||||
|
||||
assert.isTrue(MockBTAdapter.toggleCalls.calledOnce);
|
||||
});
|
||||
|
||||
test('should get connected devices by profile', function() {
|
||||
var stubDOMReq = {result: ['profiles']};
|
||||
sandbox.stub(MockBTAdapter, 'getConnectedDevices').returns(stubDOMReq);
|
||||
var cb = sinon.stub();
|
||||
subject.getConnectedDevicesByProfile('stubProfileId', cb);
|
||||
|
||||
stubDOMReq.onsuccess();
|
||||
sinon.assert.calledOnce(cb);
|
||||
sinon.assert.calledWithExactly(cb, stubDOMReq.result);
|
||||
});
|
||||
|
||||
test('should connect to sco', function() {
|
||||
var stubDOMReq = {};
|
||||
sandbox.stub(MockBTAdapter, 'connectSco').returns(stubDOMReq);
|
||||
var cb = sinon.stub();
|
||||
subject.connectSco(cb);
|
||||
|
||||
stubDOMReq.onsuccess();
|
||||
assert.isTrue(cb.calledOnce);
|
||||
});
|
||||
|
||||
test('should disconnect from sco', function() {
|
||||
var stubDOMReq = {};
|
||||
sandbox.stub(MockBTAdapter, 'disconnectSco').returns(stubDOMReq);
|
||||
var cb = sinon.stub();
|
||||
subject.disconnectSco(cb);
|
||||
|
||||
stubDOMReq.onsuccess();
|
||||
assert.isTrue(cb.calledOnce);
|
||||
});
|
||||
|
||||
test('should set callback on onhfpstatuschanged', function() {
|
||||
var stubFunc = this.sinon.stub();
|
||||
subject.onhfpstatuschanged = stubFunc;
|
||||
|
||||
assert.equal(MockBTAdapter.onhfpstatuschanged, stubFunc);
|
||||
});
|
||||
|
||||
test('should set callback on onscostatuschanged', function() {
|
||||
var stubFunc = this.sinon.stub();
|
||||
subject.onscostatuschanged = stubFunc;
|
||||
|
||||
assert.equal(MockBTAdapter.onscostatuschanged, stubFunc);
|
||||
});
|
||||
|
||||
test('should set callback on ona2dpstatuschanged', function() {
|
||||
var stubFunc = this.sinon.stub();
|
||||
subject.ona2dpstatuschanged = stubFunc;
|
||||
|
||||
assert.equal(MockBTAdapter.ona2dpstatuschanged, stubFunc);
|
||||
});
|
||||
|
||||
test('should set callback on onrequestmediaplaystatus', function() {
|
||||
var stubFunc = this.sinon.stub();
|
||||
subject.onrequestmediaplaystatus = stubFunc;
|
||||
|
||||
assert.equal(MockBTAdapter.onrequestmediaplaystatus, stubFunc);
|
||||
});
|
||||
|
||||
test('should check sco connected state', function() {
|
||||
var stubDOMReq = {};
|
||||
sandbox.stub(MockBTAdapter, 'isScoConnected').returns(stubDOMReq);
|
||||
var cb = sinon.stub();
|
||||
var errorcb = sinon.stub();
|
||||
subject.isScoConnected(cb, errorcb);
|
||||
|
||||
stubDOMReq.onsuccess();
|
||||
assert.ok(cb.called);
|
||||
stubDOMReq.onerror();
|
||||
assert.ok(errorcb.called);
|
||||
});
|
||||
|
||||
test('should send media meta-data', function() {
|
||||
var stubDOMReq = {};
|
||||
sandbox.stub(MockBTAdapter, 'sendMediaMetaData').returns(stubDOMReq);
|
||||
var cb = sinon.stub();
|
||||
var errorcb = sinon.stub();
|
||||
subject.sendMediaMetaData({}, cb, errorcb);
|
||||
|
||||
stubDOMReq.onsuccess();
|
||||
assert.ok(cb.called);
|
||||
stubDOMReq.onerror();
|
||||
assert.ok(errorcb.called);
|
||||
});
|
||||
|
||||
test('should send media play status', function() {
|
||||
var stubDOMReq = {};
|
||||
sandbox.stub(MockBTAdapter, 'sendMediaPlayStatus').returns(stubDOMReq);
|
||||
var cb = sinon.stub();
|
||||
var errorcb = sinon.stub();
|
||||
subject.sendMediaPlayStatus({}, cb, errorcb);
|
||||
|
||||
stubDOMReq.onsuccess();
|
||||
assert.ok(cb.called);
|
||||
stubDOMReq.onerror();
|
||||
assert.ok(errorcb.called);
|
||||
});
|
||||
});
|
||||
});
|
||||
231
gaia/apps/sharedtest/test/unit/contact2vcard_test.js
Executable file
231
gaia/apps/sharedtest/test/unit/contact2vcard_test.js
Executable file
|
|
@ -0,0 +1,231 @@
|
|||
/* global ContactToVcard, ContactToVcardBlob, MockContactAllFields,
|
||||
MocksHelper, mozContact */
|
||||
|
||||
'use strict';
|
||||
|
||||
require('/shared/test/unit/mocks/mock_moz_contact.js');
|
||||
require('/shared/test/unit/mocks/mock_contact_all_fields.js');
|
||||
require('/shared/js/contact2vcard.js');
|
||||
require('/shared/js/setImmediate.js');
|
||||
|
||||
var mocksHelperForContact2vcard = new MocksHelper([
|
||||
'mozContact'
|
||||
]).init();
|
||||
|
||||
suite('mozContact to vCard', function() {
|
||||
function contains(vcard) {
|
||||
return function(str) {
|
||||
return vcard.toLowerCase().indexOf(str.toLowerCase()) !== -1;
|
||||
};
|
||||
}
|
||||
|
||||
var b64 = 'R0lGODlhEAAQAMQfAKxoR8VkLxFw1feVPITSWv+eQv7Qo0Cc6OyIN/v7+3PLTSCZ' +
|
||||
'EFy17Wa6XuT1x2bGQ3nNUU6vRXPAa9mLXMTkwJZEHJt7LL5aJ/z8/O2KONx3L/ubP/r6+rtV' +
|
||||
'I////////yH5BAEAAB8ALAAAAAAQABAAAAWD4CeOZDlimOitnvlhXefFiyCs3NkZMe9QDMGi' +
|
||||
'k3t1BgZDIcZgHCCxHAyxKRQmnYOkoYgaNYMNr3JoEB6dDBGmyWxihwNBgVZz2Js3YB+JWNpr' +
|
||||
'HW15YgA2FxkaRB8JgoQxHQEbdiKNg4R5iYuVgpcZmkUjHDEapYqbJRyjkKouoqqhIyEAOw==';
|
||||
|
||||
mocksHelperForContact2vcard.attachTestHelpers();
|
||||
|
||||
suite('mozContact to vCard', function() {
|
||||
test('Convert a single contact to a vcard', function(done) {
|
||||
var mc = new MockContactAllFields();
|
||||
mc.anniversary = new Date(0);
|
||||
var contacts = [mc];
|
||||
var str = '';
|
||||
var count = 0;
|
||||
|
||||
ContactToVcard([mc], function append(vcards, nCards) {
|
||||
str += vcards;
|
||||
count += nCards;
|
||||
}, function success() {
|
||||
done(function() {
|
||||
var _contains = contains(str);
|
||||
|
||||
assert.equal(count, contacts.length);
|
||||
assert.ok(_contains('version:3.0'));
|
||||
assert.ok(_contains('fn:pepito grillo'));
|
||||
assert.ok(_contains('n:grillo;pepito;green;mr.;;'));
|
||||
assert.ok(_contains('nickname:pg'));
|
||||
assert.ok(_contains('category:favorite'));
|
||||
assert.ok(_contains('org:test org'));
|
||||
assert.ok(_contains('title:sr. software architect'));
|
||||
assert.ok(_contains('note:note 1'));
|
||||
assert.ok(_contains('bday:1970-01-01T00:00:00Z'));
|
||||
assert.ok(_contains('anniversary:1970-01-01T00:00:00Z'));
|
||||
assert.ok(_contains('photo;encoding=b;type=image/gif:' + b64));
|
||||
assert.ok(_contains('email;type=home:test@test.com'));
|
||||
assert.ok(_contains('email;type=work,pref:test@work.com'));
|
||||
assert.ok(_contains('tel;type=cell,pref:+346578888888'));
|
||||
assert.ok(_contains('tel;type=home:+3120777777'));
|
||||
assert.ok(_contains('adr;type=home,pref:;;gotthardstrasse 22;' +
|
||||
'chemnitz;chemnitz;09034;germany'));
|
||||
assert.ok(!_contains('url;type=fb_profile_photo:https://abcd1.jpg'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('Convert a single contact to a vcard without photo', function(done) {
|
||||
var mc = new MockContactAllFields();
|
||||
mc.anniversary = new Date(0);
|
||||
var contacts = [mc];
|
||||
var str = '';
|
||||
var count = 0;
|
||||
|
||||
ContactToVcard([mc], function append(vcards, nCards) {
|
||||
str += vcards;
|
||||
count += nCards;
|
||||
}, function success() {
|
||||
done(function() {
|
||||
var _contains = contains(str);
|
||||
|
||||
assert.equal(count, contacts.length);
|
||||
assert.ok(_contains('version:3.0'));
|
||||
assert.ok(_contains('fn:pepito grillo'));
|
||||
assert.ok(_contains('n:grillo;pepito;green;mr.;;'));
|
||||
assert.ok(_contains('nickname:pg'));
|
||||
assert.ok(_contains('category:favorite'));
|
||||
assert.ok(_contains('org:test org'));
|
||||
assert.ok(_contains('title:sr. software architect'));
|
||||
assert.ok(_contains('note:note 1'));
|
||||
assert.ok(_contains('bday:1970-01-01T00:00:00Z'));
|
||||
assert.ok(_contains('anniversary:1970-01-01T00:00:00Z'));
|
||||
assert.ok(!_contains('photo;encoding=b;type=image/gif:' + b64));
|
||||
assert.ok(_contains('email;type=home:test@test.com'));
|
||||
assert.ok(_contains('email;type=work,pref:test@work.com'));
|
||||
assert.ok(_contains('tel;type=cell,pref:+346578888888'));
|
||||
assert.ok(_contains('tel;type=home:+3120777777'));
|
||||
assert.ok(_contains('adr;type=home,pref:;;gotthardstrasse 22;' +
|
||||
'chemnitz;chemnitz;09034;germany'));
|
||||
assert.ok(!_contains('url;type=fb_profile_photo:https://abcd1.jpg'));
|
||||
});
|
||||
},
|
||||
null,
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
test('Convert multiple contacts to a vcard', function(done) {
|
||||
var mc = new MockContactAllFields();
|
||||
mc.anniversary = new Date(0);
|
||||
var contacts = [mc, mc];
|
||||
var str = '';
|
||||
var count = 0;
|
||||
|
||||
ContactToVcard(contacts, function(vcards, nCards) {
|
||||
str += vcards;
|
||||
count += nCards;
|
||||
}, function success() {
|
||||
done(function() {
|
||||
var _contains = contains(str);
|
||||
|
||||
assert.equal(count, contacts.length);
|
||||
assert.ok(_contains('fn:pepito grillo'));
|
||||
assert.ok(_contains('n:grillo;pepito;green;mr.;;'));
|
||||
assert.ok(_contains('nickname:pg'));
|
||||
assert.ok(_contains('category:favorite'));
|
||||
assert.ok(_contains('org:test org'));
|
||||
assert.ok(_contains('title:sr. software architect'));
|
||||
assert.ok(_contains('note:note 1'));
|
||||
assert.ok(_contains('bday:1970-01-01T00:00:00Z'));
|
||||
assert.ok(_contains('anniversary:1970-01-01T00:00:00Z'));
|
||||
assert.ok(_contains('photo;encoding=b;type=image/gif:' + b64));
|
||||
assert.ok(_contains('email;type=home:test@test.com'));
|
||||
assert.ok(_contains('tel;type=cell,pref:+346578888888'));
|
||||
assert.ok(_contains('tel;type=home:+3120777777'));
|
||||
assert.ok(_contains('adr;type=home,pref:;;gotthardstrasse 22;' +
|
||||
'chemnitz;chemnitz;09034;germany'));
|
||||
assert.ok(!_contains('url;type=fb_profile_photo:https://abcd1.jpg'));
|
||||
});
|
||||
}, b64.length / 2); // This ensures at least two batches
|
||||
});
|
||||
|
||||
test('Convert an empty contact to a vcard', function(done) {
|
||||
var contacts = [new mozContact()];
|
||||
var str = '';
|
||||
var count = 0;
|
||||
|
||||
ContactToVcard(contacts, function append(vcards, nCards) {
|
||||
str += vcards;
|
||||
count += nCards;
|
||||
}, function success() {
|
||||
done(function() {
|
||||
assert.equal(count, contacts.length);
|
||||
assert.strictEqual(str, '');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('Convert contact to vcard blob', function(done) {
|
||||
var mc = new MockContactAllFields();
|
||||
var contacts = [mc];
|
||||
var str = '';
|
||||
var count = 0;
|
||||
|
||||
ContactToVcardBlob(contacts, function(blob) {
|
||||
assert.isNotNull(blob);
|
||||
|
||||
// Fetch the same content as a normal vcard
|
||||
ContactToVcard(contacts, function append(vcards, nCards) {
|
||||
str += vcards;
|
||||
count += nCards;
|
||||
}, function success() {
|
||||
// Read the content and verify that is what we generate
|
||||
var reader = new FileReader();
|
||||
|
||||
reader.addEventListener('loadend', function() {
|
||||
var blobContent = reader.result;
|
||||
|
||||
done(function() {
|
||||
// Size and content of the blob should match that of the string
|
||||
assert.equal(str.length, blob.size);
|
||||
assert.equal(str, blobContent);
|
||||
});
|
||||
});
|
||||
reader.readAsText(blob);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('MIME types >', function() {
|
||||
var contact;
|
||||
|
||||
suiteSetup(function() {
|
||||
contact = new MockContactAllFields(true);
|
||||
contact.photo = null;
|
||||
});
|
||||
|
||||
test('> Contact transformed to vcard. No options', function(done) {
|
||||
ContactToVcardBlob([contact], function blobReady(vcardBlob) {
|
||||
assert.isTrue(vcardBlob.size > 0);
|
||||
|
||||
assert.equal(vcardBlob.type, 'text/vcard; charset=utf-8');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('> Contact transformed to vcard. Only MIME type', function(done) {
|
||||
var targetType = 'text/x-vcard';
|
||||
ContactToVcardBlob([contact], function blobReady(vcardBlob) {
|
||||
assert.isTrue(vcardBlob.size > 0);
|
||||
|
||||
assert.equal(vcardBlob.type, targetType + '; charset=utf-8');
|
||||
done();
|
||||
}, { type: targetType });
|
||||
});
|
||||
|
||||
test('> Contact transformed to vcard. MIME type & charset',
|
||||
function(done) {
|
||||
var targetType = 'text/x-vcard; charset=iso-8859-1';
|
||||
|
||||
ContactToVcardBlob([contact], function blobReady(vcardBlob) {
|
||||
assert.isTrue(vcardBlob.size > 0);
|
||||
|
||||
assert.equal(vcardBlob.type, targetType);
|
||||
done();
|
||||
}, { type: targetType });
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
165
gaia/apps/sharedtest/test/unit/contact_photo_helper_test.js
Executable file
165
gaia/apps/sharedtest/test/unit/contact_photo_helper_test.js
Executable file
|
|
@ -0,0 +1,165 @@
|
|||
'use strict';
|
||||
|
||||
/* global ContactPhotoHelper */
|
||||
|
||||
require('/shared/js/contact_photo_helper.js');
|
||||
|
||||
suite('shared/ContactPhotoHelper >', function() {
|
||||
var contactWithoutPhoto, contactWithString, contactWithoutThumbnail,
|
||||
contactWithThumbnail, cntactWithExtra, cntactFbLinkedOnePhoto,
|
||||
cntactFbLinkedTwoPhotos, cntactFbLinkedAllPhotos;
|
||||
|
||||
var dataURL, fullResolution, thumbnail, extra,
|
||||
thumbnailFb, fullResolutionFb;
|
||||
var subject;
|
||||
|
||||
setup(function() {
|
||||
dataURL = 'data:image/png;base64,';
|
||||
fullResolution = new Blob(['1234567890'], {type: 'image/png'});
|
||||
thumbnail = new Blob(['123'], {type: 'image/png'});
|
||||
extra = new Blob(['12345'], {type: 'image/png'});
|
||||
|
||||
fullResolutionFb = new Blob(['9876543210'], {type: 'image/png'});
|
||||
thumbnailFb = new Blob(['987'], {type: 'image/png'});
|
||||
|
||||
contactWithoutPhoto = {
|
||||
photo: []
|
||||
};
|
||||
|
||||
contactWithString = {
|
||||
photo: [dataURL]
|
||||
};
|
||||
|
||||
contactWithoutThumbnail = {
|
||||
photo: [fullResolution]
|
||||
};
|
||||
|
||||
contactWithThumbnail = {
|
||||
photo: [fullResolution, thumbnail]
|
||||
};
|
||||
|
||||
cntactWithExtra = {
|
||||
photo: [thumbnail, extra, fullResolution]
|
||||
};
|
||||
|
||||
// Legacy contact (linked / imported by a previous OS version)
|
||||
// with one photo
|
||||
cntactFbLinkedOnePhoto = {
|
||||
photo: [fullResolutionFb],
|
||||
category: ['fb_linked']
|
||||
};
|
||||
|
||||
// Legacy contact (linked by a previous OS version)
|
||||
// with two photos (local and Facebook)
|
||||
cntactFbLinkedTwoPhotos = {
|
||||
photo: [fullResolution, fullResolutionFb],
|
||||
category: ['fb_linked']
|
||||
};
|
||||
|
||||
// New contact
|
||||
cntactFbLinkedAllPhotos = {
|
||||
category: ['fb_linked'],
|
||||
photo: [fullResolution, thumbnail, fullResolutionFb, thumbnailFb]
|
||||
};
|
||||
|
||||
subject = ContactPhotoHelper;
|
||||
});
|
||||
|
||||
suite('getThumbnail >', function() {
|
||||
test('should return null with the contact has no photo', function() {
|
||||
assert.isNull(subject.getThumbnail(contactWithoutPhoto));
|
||||
});
|
||||
|
||||
test('should not choke on old dataURL photos', function() {
|
||||
assert.equal(subject.getThumbnail(contactWithString),
|
||||
dataURL);
|
||||
});
|
||||
|
||||
test('should return the only photo if the contact has no thumbnail',
|
||||
function() {
|
||||
assert.equal(subject.getThumbnail(contactWithoutThumbnail),
|
||||
fullResolution);
|
||||
});
|
||||
|
||||
test('should return the smallest blob if the contact has a thumbnail',
|
||||
function() {
|
||||
assert.equal(subject.getThumbnail(contactWithThumbnail),
|
||||
thumbnail);
|
||||
});
|
||||
|
||||
test('should return the smallest blob if the contact multiple photos',
|
||||
function() {
|
||||
assert.equal(subject.getThumbnail(cntactWithExtra),
|
||||
thumbnail);
|
||||
});
|
||||
});
|
||||
|
||||
suite('getThumbnail > FB Linked >', function() {
|
||||
test('should return photo[0] for a legacy linked contact with one photo',
|
||||
function() {
|
||||
assert.equal(subject.getThumbnail(cntactFbLinkedOnePhoto),
|
||||
fullResolutionFb);
|
||||
});
|
||||
|
||||
test('should return photo[0] for a legacy linked contact with two photos',
|
||||
function() {
|
||||
assert.equal(subject.getThumbnail(cntactFbLinkedTwoPhotos),
|
||||
fullResolution);
|
||||
});
|
||||
|
||||
test('should return local photo when the linked contact has both photos',
|
||||
function() {
|
||||
assert.equal(subject.getThumbnail(cntactFbLinkedAllPhotos),
|
||||
thumbnail);
|
||||
});
|
||||
});
|
||||
|
||||
suite('getFullResolution >', function() {
|
||||
test('should return null with the contact has no photo', function() {
|
||||
assert.isNull(subject.getFullResolution(contactWithoutPhoto));
|
||||
});
|
||||
|
||||
test('should not choke on old dataURL photos', function() {
|
||||
assert.equal(subject.getFullResolution(contactWithString),
|
||||
dataURL);
|
||||
});
|
||||
|
||||
test('should return the only photo if the contact has no thumbnail',
|
||||
function() {
|
||||
assert.equal(subject.getFullResolution(contactWithoutThumbnail),
|
||||
fullResolution);
|
||||
});
|
||||
|
||||
test('should return the biggest blob if the contact has a thumbnail',
|
||||
function() {
|
||||
assert.equal(subject.getFullResolution(contactWithThumbnail),
|
||||
fullResolution);
|
||||
});
|
||||
|
||||
test('should return the biggest blob if the contact multiple photos',
|
||||
function() {
|
||||
assert.equal(subject.getFullResolution(cntactWithExtra),
|
||||
fullResolution);
|
||||
});
|
||||
});
|
||||
|
||||
suite('getFullResolution > FB Linked >', function() {
|
||||
test('should return photo[0] for a legacy linked contact with one photo',
|
||||
function() {
|
||||
assert.equal(subject.getFullResolution(cntactFbLinkedOnePhoto),
|
||||
fullResolutionFb);
|
||||
});
|
||||
|
||||
test('should return photo[0] for a legacy linked contact with two photos',
|
||||
function() {
|
||||
assert.equal(subject.getFullResolution(cntactFbLinkedTwoPhotos),
|
||||
fullResolution);
|
||||
});
|
||||
|
||||
test('should return the local photo if the contact has both photos',
|
||||
function() {
|
||||
assert.equal(subject.getFullResolution(cntactFbLinkedAllPhotos),
|
||||
fullResolution);
|
||||
});
|
||||
});
|
||||
});
|
||||
651
gaia/apps/sharedtest/test/unit/crop_resize_rotate_test.js
Executable file
651
gaia/apps/sharedtest/test/unit/crop_resize_rotate_test.js
Executable file
|
|
@ -0,0 +1,651 @@
|
|||
'use strict';
|
||||
|
||||
/* global BlobView */
|
||||
/* global parseJPEGMetadata */
|
||||
/* global getImageSize */
|
||||
/* global Downsample */
|
||||
/* global cropResizeRotate, JPEGParser */
|
||||
|
||||
require('/shared/test/unit/mocks/mock_lazy_loader.js');
|
||||
require('/shared/js/blobview.js');
|
||||
require('/shared/js/media/jpeg_metadata_parser.js');
|
||||
require('/shared/js/media/image_size.js');
|
||||
require('/shared/js/media/downsample.js');
|
||||
require('/shared/js/media/crop_resize_rotate.js');
|
||||
require('/shared/js/media/jpeg-exif.js');
|
||||
|
||||
function fetchBlob(url) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url);
|
||||
xhr.onload = function() {
|
||||
if (xhr.status !== 200) {
|
||||
reject(new Error('Failed with status: ' + xhr.status));
|
||||
} else {
|
||||
resolve(this.response);
|
||||
}
|
||||
};
|
||||
xhr.onerror = xhr.ontimeout = function() {
|
||||
reject(new Error('Failed' + xhr.status));
|
||||
};
|
||||
xhr.responseType = 'blob';
|
||||
xhr.send();
|
||||
});
|
||||
}
|
||||
|
||||
suite('Metadata copy test', function() {
|
||||
suiteSetup(function() {
|
||||
|
||||
// We need the mock lazy loader for jpeg-exif.
|
||||
window.LazyLoader = window.MockLazyLoader;
|
||||
});
|
||||
|
||||
|
||||
[
|
||||
// this one is horizontal
|
||||
{ fileName: '/test-data/blue.jpg', rotated: false,
|
||||
targetSize: { width: 640, height: 480 },
|
||||
date: '2015:07:16 09:42:26', make: 'QCOM-AA' },
|
||||
{ fileName: '/test-data/blue.jpg', rotated: false,
|
||||
targetSize: null,
|
||||
date: '2015:07:16 09:42:26', make: 'QCOM-AA' },
|
||||
{ fileName: '/test-data/blue.jpg', rotated: false,
|
||||
targetCrop: { left: 10, top: 10, width: 150, height: 100 },
|
||||
targetSize: null,
|
||||
date: '2015:07:16 09:42:26', make: 'QCOM-AA' },
|
||||
// this one is vertical (Exif orientation)
|
||||
{ fileName: '/test-data/red.jpg', rotated: true,
|
||||
targetSize: { width: 480, height: 640 },
|
||||
date: '2015:07:13 21:16:38', make: 'Sony' },
|
||||
{ fileName: '/test-data/red.jpg', rotated: true,
|
||||
targetSize: null,
|
||||
date: '2015:07:13 21:16:38', make: 'Sony' },
|
||||
{ fileName: '/test-data/red.jpg', rotated: true,
|
||||
targetCrop: { left: 10, top: 10, width: 100, height: 150 },
|
||||
targetSize: null,
|
||||
date: '2015:07:13 21:16:38', make: 'Sony' }
|
||||
].
|
||||
forEach(function (testFile) {
|
||||
var testName = 'Testing metadata copy for ' + testFile.fileName;
|
||||
if (testFile.targetSize) {
|
||||
testName += ' resized to ' + testFile.targetSize.width +
|
||||
'x' + testFile.targetSize.height;
|
||||
}
|
||||
test(testName, function(done) {
|
||||
|
||||
fetchBlob(testFile.fileName).then(function (blob) {
|
||||
assert.ok(blob);
|
||||
|
||||
var targetSize = testFile.targetSize;
|
||||
var targetCrop = testFile.targetCrop === undefined ? null :
|
||||
testFile.targetCrop;
|
||||
cropResizeRotate(
|
||||
blob, targetCrop, targetSize, 'image/jpeg+exif',
|
||||
function (error, outBlob) {
|
||||
assert.equal(error, null);
|
||||
assert.ok(outBlob);
|
||||
|
||||
JPEGParser.readExifMetaData(
|
||||
outBlob, function (error, metaData) {
|
||||
assert.ok(metaData);
|
||||
// check metaData
|
||||
assert.equal(metaData.Orientation, 1);
|
||||
assert.equal(metaData.DateTimeOriginal, testFile.date);
|
||||
assert.equal(metaData.Make, testFile.make);
|
||||
|
||||
var expectedX, expectedY;
|
||||
if (targetSize) {
|
||||
expectedX = targetSize.width;
|
||||
expectedY = targetSize.height;
|
||||
} else if (targetCrop) {
|
||||
expectedX = targetCrop.width;
|
||||
expectedY = targetCrop.height;
|
||||
} else {
|
||||
if (!testFile.rotated) {
|
||||
expectedX = 1280;
|
||||
expectedY = 960;
|
||||
} else {
|
||||
expectedX = 960;
|
||||
expectedY = 1280;
|
||||
}
|
||||
}
|
||||
|
||||
assert.equal(metaData.PixelXDimension, expectedX);
|
||||
assert.equal(metaData.PixelYDimension, expectedY);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
}).catch(function (e) {
|
||||
assert(false, 'Caught error ' + e);
|
||||
done(e);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function runCropResizeRotateTests(imageWidth, imageHeight) {
|
||||
var suitename = 'cropResizeRotate tests ' + imageWidth + 'x' + imageHeight;
|
||||
suite(suitename, function() {
|
||||
const W = imageWidth, H = imageHeight; // The size of the test image
|
||||
|
||||
suiteSetup(function(done) {
|
||||
|
||||
// We need the mock lazy loader for jpeg-exif.
|
||||
window.LazyLoader = window.MockLazyLoader;
|
||||
|
||||
// We begin by creating a special image where each pixel value
|
||||
// encodes the coordinates of that pixel. This allows us to inspect
|
||||
// the pixels in the output image to verify that cropping and rotation
|
||||
// was done correctly.
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = W;
|
||||
canvas.height = H;
|
||||
var context = canvas.getContext('2d');
|
||||
var imageData = context.createImageData(W, H);
|
||||
var pixels = imageData.data;
|
||||
|
||||
for (var x = 0; x < canvas.width; x++) {
|
||||
for (var y = 0; y < canvas.height; y++) {
|
||||
var offset = (x + y * canvas.width) * 4;
|
||||
pixels[offset] = x; // Encode x coordinate as red value
|
||||
pixels[offset + 1] = y; // Encode y coodrdinate as green value
|
||||
imageData.data[offset + 3] = 255; // Make it opaque
|
||||
}
|
||||
}
|
||||
|
||||
context.putImageData(imageData, 0, 0);
|
||||
|
||||
var self = this;
|
||||
canvas.toBlob(function(blob) {
|
||||
self.jpegBlob = blob;
|
||||
done();
|
||||
}, 'image/jpeg');
|
||||
});
|
||||
|
||||
// Decode the image blob and verify that its size is as expected and
|
||||
// that its upper-left pixel has red and green values near r0 and g0
|
||||
// and that its lower-right pixel has values near r1 and g1. We do a
|
||||
// fuzzy match because jpeg is a lossy format and because sometimes
|
||||
// we're working with downsampled images.
|
||||
function verifyImage(blob, expectedWidth, expectedHeight,
|
||||
r0, g0, r1, g1, tolerance, callback) {
|
||||
var image = new Image();
|
||||
var url = URL.createObjectURL(blob);
|
||||
image.src = url;
|
||||
image.onerror = function() {
|
||||
callback(Error('failed to load image from blob'));
|
||||
};
|
||||
|
||||
image.onload = function() {
|
||||
try {
|
||||
assert.equal(image.width, expectedWidth, 'image width');
|
||||
assert.equal(image.height, expectedHeight, 'image height');
|
||||
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = image.width;
|
||||
canvas.height = image.height;
|
||||
var context = canvas.getContext('2d');
|
||||
context.drawImage(image, 0, 0);
|
||||
var imageData = context.getImageData(0, 0,
|
||||
canvas.width, canvas.height);
|
||||
var pixels = imageData.data;
|
||||
|
||||
isNear(pixels[0], r0, 'red component of upper-left');
|
||||
isNear(pixels[1], g0, 'green component of upper-left');
|
||||
|
||||
var offset = 4 * (image.width * image.height - 1);
|
||||
isNear(pixels[offset], r1,
|
||||
'red component of lower right');
|
||||
isNear(pixels[offset + 1], g1,
|
||||
'green component of lower right');
|
||||
|
||||
image.src = '';
|
||||
URL.revokeObjectURL(url);
|
||||
canvas.width = 0;
|
||||
callback();
|
||||
}
|
||||
catch (e) {
|
||||
callback(e);
|
||||
}
|
||||
};
|
||||
|
||||
// Return true if x is close enough to the target
|
||||
function isNear(x, target, message) {
|
||||
if (target - tolerance <= x && x <= target + tolerance) {
|
||||
return;
|
||||
}
|
||||
throw Error(message + ' ' + x + ' is not near ' + target);
|
||||
}
|
||||
}
|
||||
|
||||
test('prerequisite modules loaded', function() {
|
||||
assert.isDefined(BlobView);
|
||||
assert.typeOf(parseJPEGMetadata, 'function');
|
||||
assert.typeOf(getImageSize, 'function');
|
||||
assert.typeOf(cropResizeRotate, 'function');
|
||||
assert.ok(window.MockLazyLoader);
|
||||
assert.ok(window.LazyLoader);
|
||||
assert.isDefined(JPEGParser);
|
||||
});
|
||||
|
||||
test('test image created successfully', function(done) {
|
||||
assert.instanceOf(this.jpegBlob, Blob, 'got jpegBlob');
|
||||
verifyImage(this.jpegBlob, W, H, 0, 0, W - 1, H - 1, 2, done);
|
||||
});
|
||||
|
||||
function testInvalidCropRegion(left, top, width, height) {
|
||||
var invalidRegion = {
|
||||
left: left, top: top, width: width, height: height
|
||||
};
|
||||
var testTitle = 'get error for invalid crop region ' +
|
||||
left + ' ' + top + ' ' + width + ' ' + height;
|
||||
test(testTitle, function(done) {
|
||||
cropResizeRotate(this.jpegBlob, invalidRegion,
|
||||
function(error, blob) {
|
||||
if (error) {
|
||||
done();
|
||||
}
|
||||
else {
|
||||
done(Error('did not get expected error'));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
testInvalidCropRegion(-1, 0, 100, 100);
|
||||
testInvalidCropRegion(0, -1, 100, 100);
|
||||
testInvalidCropRegion(0, 0, W + 10, 100);
|
||||
testInvalidCropRegion(0, 0, 100, H + 1);
|
||||
testInvalidCropRegion(100, 100, W - 90, H - 110);
|
||||
testInvalidCropRegion(100, 100, W - 140, H - 90);
|
||||
|
||||
function testInvalidOutputSize(size) {
|
||||
var testTitle =
|
||||
'get error for invalid outputSize ' + JSON.stringify(size);
|
||||
test(testTitle, function(done) {
|
||||
cropResizeRotate(this.jpegBlob, null, size,
|
||||
function(error, blob) {
|
||||
if (error) {
|
||||
done();
|
||||
}
|
||||
else {
|
||||
done(Error('did not get expected error'));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
testInvalidOutputSize(0);
|
||||
testInvalidOutputSize(-2);
|
||||
testInvalidOutputSize({width: -1, height: 10});
|
||||
testInvalidOutputSize({width: 0, height: 10});
|
||||
testInvalidOutputSize({width: 10, height: -1});
|
||||
testInvalidOutputSize({width: 10, height: 0});
|
||||
testInvalidOutputSize({width: 10});
|
||||
testInvalidOutputSize({});
|
||||
|
||||
function testInvalidOutputType(type) {
|
||||
var testTitle = 'get error for invalid outputType ' + type;
|
||||
test(testTitle, function(done) {
|
||||
cropResizeRotate(this.jpegBlob, null, null, type,
|
||||
function(error, blob) {
|
||||
if (error) {
|
||||
done();
|
||||
}
|
||||
else {
|
||||
done(Error('did not get expected error'));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
testInvalidOutputType('image/*');
|
||||
testInvalidOutputType('image/gif');
|
||||
|
||||
//
|
||||
// Test that valid output types work
|
||||
//
|
||||
|
||||
function testValidOutputType(type) {
|
||||
var testTitle = 'outputType ' + type;
|
||||
test(testTitle, function(done) {
|
||||
cropResizeRotate(this.jpegBlob, null, null, type, gotBlob);
|
||||
function gotBlob(error, blob) {
|
||||
try {
|
||||
assert.isNull(error);
|
||||
assert.instanceOf(blob, Blob);
|
||||
assert.equal(blob.type, type || 'image/jpeg');
|
||||
done();
|
||||
}
|
||||
catch (e) {
|
||||
done(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
testValidOutputType(null);
|
||||
testValidOutputType('image/jpeg');
|
||||
testValidOutputType('image/png');
|
||||
|
||||
//
|
||||
// Test that with no arguments the input blob is used unmodified
|
||||
//
|
||||
test('blob unmodified for no-op', function(done) {
|
||||
var original = this.jpegBlob;
|
||||
cropResizeRotate(original, function(error, blob) {
|
||||
try {
|
||||
assert.isNull(error);
|
||||
assert.instanceOf(blob, Blob);
|
||||
assert.equal(blob, original);
|
||||
done();
|
||||
}
|
||||
catch (e) {
|
||||
done(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//
|
||||
// Test cropping alone
|
||||
//
|
||||
|
||||
function testCrop(left, top, width, height) {
|
||||
var cropRegion = {
|
||||
left: left, top: top, width: width, height: height
|
||||
};
|
||||
var testTitle = 'Crop region: ' +
|
||||
left + ' ' + top + ' ' + width + ' ' + height;
|
||||
test(testTitle, function(done) {
|
||||
cropResizeRotate(this.jpegBlob, cropRegion, function(error, blob) {
|
||||
try {
|
||||
assert.isNull(error);
|
||||
assert.instanceOf(blob, Blob);
|
||||
verifyImage(blob, width, height, left, top,
|
||||
left + width - 1, top + height - 1, 3, done);
|
||||
}
|
||||
catch (e) {
|
||||
done(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
testCrop(0, 0, W, H);
|
||||
testCrop(0, 0, 50, 50);
|
||||
testCrop(0, 50, 50, 50);
|
||||
testCrop(50, 0, 50, 50);
|
||||
testCrop(50, 50, 50, 50);
|
||||
testCrop(100, 100, W - 100, H - 100);
|
||||
testCrop(1, 1, 1, 1);
|
||||
|
||||
//
|
||||
// Test resizing alone
|
||||
//
|
||||
|
||||
function testResize(width, height) {
|
||||
var testTitle = 'Resize image to: ' + width + 'x' + height;
|
||||
test(testTitle, function(done) {
|
||||
var outputSize = { width: width, height: height };
|
||||
var tolerance = Math.max(W / width, H / height) * 3;
|
||||
|
||||
// If the aspect ratio of the output size doesn't match the image
|
||||
// then we get an implict crop region, and we have to figure that out
|
||||
var cropX = 0, cropY = 0, cropW = W, cropH = H;
|
||||
var scaleX = width / W;
|
||||
var scaleY = height / H;
|
||||
if (scaleX < scaleY) {
|
||||
cropW = width / scaleY;
|
||||
cropX = (W - cropW) / 2;
|
||||
}
|
||||
else {
|
||||
cropH = height / scaleX;
|
||||
cropY = (H - cropH) / 2;
|
||||
}
|
||||
|
||||
cropResizeRotate(this.jpegBlob, null, outputSize,
|
||||
function(error, blob) {
|
||||
try {
|
||||
assert.isNull(error);
|
||||
assert.instanceOf(blob, Blob);
|
||||
verifyImage(blob, width, height,
|
||||
cropX, cropY, cropX + cropW - 1, cropY + cropH - 1,
|
||||
tolerance, done);
|
||||
}
|
||||
catch (e) {
|
||||
done(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// resize to the same aspect ratio
|
||||
testResize(Math.round(W / 2), Math.round(H / 2));
|
||||
testResize(Math.round(W / 3), Math.round(H / 3));
|
||||
testResize(Math.round(W / 4), Math.round(H / 4));
|
||||
testResize(Math.round(W / 5), Math.round(H / 5));
|
||||
testResize(Math.round(W / 6), Math.round(H / 6));
|
||||
testResize(Math.round(2 * W / 3), Math.round(2 * H / 3));
|
||||
testResize(Math.round(5 * W / 6), Math.round(5 * H / 6));
|
||||
|
||||
// resize to different aspect ratios
|
||||
testResize(Math.min(W, H), Math.min(W, H));
|
||||
testResize(Math.min(W, H) >> 1, Math.min(W, H) >> 1);
|
||||
testResize(Math.min(W, H), Math.min(W, H) >> 1);
|
||||
testResize(Math.min(W, H) >> 1, Math.min(W, H));
|
||||
|
||||
//
|
||||
// Test resizing to a specified maximum number of pixels
|
||||
//
|
||||
function testResizeMaxPixels(areaScale) {
|
||||
var testTitle = 'Resize image area by: ' + areaScale;
|
||||
test(testTitle, function(done) {
|
||||
var maxPixels = Math.round(W * H * areaScale);
|
||||
var downsample = Downsample.areaAtLeast(areaScale);
|
||||
var expectedWidth = downsample.scale(W);
|
||||
var expectedHeight = downsample.scale(H);
|
||||
var tolerance = Math.ceil(2 / downsample.dimensionScale);
|
||||
|
||||
cropResizeRotate(this.jpegBlob, null, maxPixels, function(error, blob) {
|
||||
try {
|
||||
assert.isNull(error);
|
||||
assert.instanceOf(blob, Blob);
|
||||
verifyImage(blob, expectedWidth, expectedHeight,
|
||||
0, 0, W - 1, H - 1,
|
||||
tolerance, done);
|
||||
}
|
||||
catch (e) {
|
||||
done(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
testResizeMaxPixels(2); // no resize
|
||||
testResizeMaxPixels(1); // no resize
|
||||
testResizeMaxPixels(0.9);
|
||||
testResizeMaxPixels(0.8);
|
||||
testResizeMaxPixels(0.75);
|
||||
testResizeMaxPixels(0.6);
|
||||
testResizeMaxPixels(0.5);
|
||||
testResizeMaxPixels(0.4);
|
||||
testResizeMaxPixels(0.3);
|
||||
testResizeMaxPixels(0.25);
|
||||
testResizeMaxPixels(0.2);
|
||||
testResizeMaxPixels(0.140625); // 3/8ths squared
|
||||
testResizeMaxPixels(0.1);
|
||||
testResizeMaxPixels(0.0625); // 1/4 squared
|
||||
testResizeMaxPixels(0.05);
|
||||
testResizeMaxPixels(0.04);
|
||||
testResizeMaxPixels(0.02);
|
||||
testResizeMaxPixels(0.015625); // 1/8th squared
|
||||
testResizeMaxPixels(0.01);
|
||||
|
||||
//
|
||||
// Test rotation and mirroring alone
|
||||
// Our JPEG blob does not have real EXIF orientation data, so we
|
||||
// pass a fake metadata object to claim that it does and expect
|
||||
// it to be rotated appropriately
|
||||
//
|
||||
|
||||
function testRotate(rotation, mirrored, width, height, r0, g0, r1, g1) {
|
||||
var testTitle = 'Rotation: ' + rotation;
|
||||
if (mirrored) {
|
||||
testTitle += ', mirrored';
|
||||
}
|
||||
|
||||
var tolerance = 3;
|
||||
if (mirrored) {
|
||||
// Images with width 220 need a tolerance > 3 for the
|
||||
// rotation=0, mirrored=true test.
|
||||
tolerance += 1;
|
||||
}
|
||||
|
||||
test(testTitle, function(done) {
|
||||
var metadata = {
|
||||
width: W,
|
||||
height: H,
|
||||
rotation: rotation,
|
||||
mirrored: mirrored
|
||||
};
|
||||
|
||||
cropResizeRotate(this.jpegBlob, null, null, null, metadata, gotBlob);
|
||||
|
||||
function gotBlob(error, blob) {
|
||||
try {
|
||||
assert.isNull(error);
|
||||
assert.instanceOf(blob, Blob);
|
||||
verifyImage(blob, width, height, r0, g0, r1, g1, tolerance, done);
|
||||
}
|
||||
catch (e) {
|
||||
done(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testRotate(0, false, W, H, 0, 0, W - 1, H - 1);
|
||||
testRotate(0, true, W, H, W - 1, 0, 0, H - 1);
|
||||
testRotate(90, false, H, W, 0, H - 1, W - 1, 0);
|
||||
testRotate(90, true, H, W, 0, 0, W - 1, H - 1);
|
||||
testRotate(180, false, W, H, W - 1, H - 1, 0, 0);
|
||||
testRotate(180, true, W, H, 0, H - 1, W - 1, 0);
|
||||
testRotate(270, false, H, W, W - 1, 0, 0, H - 1);
|
||||
testRotate(270, true, H, W, W - 1, H - 1, 0, 0);
|
||||
|
||||
|
||||
//
|
||||
// Test rotation and mirroring along with cropping and a simple resize
|
||||
//
|
||||
|
||||
function testCropResizeRotate(left, top, width, height,
|
||||
rotation, mirrored, resize,
|
||||
r0, g0, r1, g1) {
|
||||
var testTitle = 'Crop, resize, rotate: ' + left + ' ' + top + ' ' +
|
||||
width + ' ' + height + ' ' + resize + ' ' + rotation + ' ' + mirrored;
|
||||
|
||||
test(testTitle, function(done) {
|
||||
var cropRegion = {
|
||||
left: left,
|
||||
top: top,
|
||||
width: width,
|
||||
height: height
|
||||
};
|
||||
var metadata = {
|
||||
width: W,
|
||||
height: H,
|
||||
rotation: rotation,
|
||||
mirrored: mirrored
|
||||
};
|
||||
|
||||
var tolerance = 3;
|
||||
var outputSize = null;
|
||||
|
||||
if (resize) {
|
||||
width = Math.round(width / resize);
|
||||
height = Math.round(height / resize);
|
||||
outputSize = {
|
||||
width: width,
|
||||
height: height
|
||||
};
|
||||
tolerance = 1 + resize * 2;
|
||||
}
|
||||
|
||||
cropResizeRotate(this.jpegBlob, cropRegion, outputSize, null, metadata,
|
||||
gotBlob);
|
||||
|
||||
function gotBlob(error, blob) {
|
||||
try {
|
||||
assert.isNull(error);
|
||||
assert.instanceOf(blob, Blob);
|
||||
verifyImage(blob, width, height, r0, g0, r1, g1, tolerance, done);
|
||||
}
|
||||
catch (e) {
|
||||
done(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// We use a 50x100 crop region with left corner at (25,25) for these.
|
||||
// This crop region is in the rotated coordinate system.
|
||||
// The resize factor is varied each time through the loop.
|
||||
for (var i = 0; i < 4; i++) {
|
||||
testCropResizeRotate(25, 25, 50, 100, 0, false, i,
|
||||
25, 25, 75 - 1, 125 - 1);
|
||||
testCropResizeRotate(25, 25, 50, 100, 0, true, i,
|
||||
W - 25, 25, W - 75 - 1, 125 - 1);
|
||||
|
||||
testCropResizeRotate(25, 25, 50, 100, 90, false, i,
|
||||
25, H - 25, 125, H - 75 - 1);
|
||||
testCropResizeRotate(25, 25, 50, 100, 90, true, i,
|
||||
25, 25, 125 - 1, 75 - 1);
|
||||
|
||||
testCropResizeRotate(25, 25, 50, 100, 180, false, i,
|
||||
W - 25 - 1, H - 25 - 1, W - 75 - 1, H - 125 - 1);
|
||||
testCropResizeRotate(25, 25, 50, 100, 180, true, i,
|
||||
25, H - 25 - 1, 75 - 1, H - 125 - 1);
|
||||
|
||||
testCropResizeRotate(25, 25, 50, 100, 270, false, i,
|
||||
W - 25 - 1, 25, W - 125 - 1, 75 - 1);
|
||||
testCropResizeRotate(25, 25, 50, 100, 270, true, i,
|
||||
W - 25 - 1, H - 25, W - 125 - 1, H - 75 - 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Commenting out the less common image sizes for the sake of performance.
|
||||
// The tests pass with these image sizes, so they can be uncommented and run
|
||||
// manually.
|
||||
//
|
||||
([
|
||||
[240, 180], // 4x3
|
||||
[180, 240], // 3x4
|
||||
//[252, 189], // 4x3, with an odd dimension
|
||||
[250, 250], // square
|
||||
[250, 125] // 2x1
|
||||
//[199, 254] // odd portrait size
|
||||
]).forEach(function(size) {
|
||||
runCropResizeRotateTests(size[0], size[1]);
|
||||
});
|
||||
|
||||
/*
|
||||
* These tests take too long to run automatically
|
||||
* but they do pass when run manually
|
||||
*
|
||||
for(var w = 175; w <= 255; w++) {
|
||||
runCropResizeRotateTests(w, 255);
|
||||
}
|
||||
|
||||
for(var w = 175; w <= 255; w++) {
|
||||
runCropResizeRotateTests(255, w);
|
||||
}
|
||||
|
||||
for(var w = 175; w <= 255; w += 5) {
|
||||
for(var h = 175; h <= 255; h += 5) {
|
||||
runCropResizeRotateTests(w, h);
|
||||
}
|
||||
}
|
||||
*/
|
||||
61
gaia/apps/sharedtest/test/unit/default_activity_helper_test.js
Executable file
61
gaia/apps/sharedtest/test/unit/default_activity_helper_test.js
Executable file
|
|
@ -0,0 +1,61 @@
|
|||
'use strict';
|
||||
|
||||
/* global DefaultActivityHelper, MockSettingsHelper, SettingsHelper */
|
||||
|
||||
require('/shared/js/default_activity_helper.js');
|
||||
require('/shared/test/unit/mocks/mock_settings_helper.js');
|
||||
|
||||
suite('Default Activity Helper', function() {
|
||||
var realSettingsHelper;
|
||||
|
||||
suiteSetup(function() {
|
||||
realSettingsHelper = window.SettingsHelper;
|
||||
window.SettingsHelper = MockSettingsHelper;
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
window.SettingsHelper = realSettingsHelper;
|
||||
});
|
||||
|
||||
suite('getDefaultConfig', function() {
|
||||
test('returns config if supported', function() {
|
||||
var config = DefaultActivityHelper.getDefaultConfig('view', 'url');
|
||||
assert.ok(config);
|
||||
assert.equal(config.settingsId, 'activity.default.viewurl');
|
||||
});
|
||||
|
||||
test('returns undefined if not supported', function() {
|
||||
var config = DefaultActivityHelper.getDefaultConfig('aaa', 'bbbb');
|
||||
assert.equal(config, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
suite('getDefaultAction', function(done) {
|
||||
test('setting is recovered for supported activity', function(done) {
|
||||
var settingsHelper = SettingsHelper('activity.default.viewurl', null);
|
||||
settingsHelper.set('manifest');
|
||||
DefaultActivityHelper.getDefaultAction('view', 'url').then((action) => {
|
||||
assert.equal(action, 'manifest');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('cb is called with null if not supported', function(done) {
|
||||
DefaultActivityHelper.getDefaultAction('aaaa', 'bbb').then((action) => {
|
||||
assert.equal(action, null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('setDefaultAction', function(done) {
|
||||
test('setting is set for supported action', function(done) {
|
||||
var settingsHelper = SettingsHelper('activity.default.viewurl', null);
|
||||
DefaultActivityHelper.setDefaultAction('view', 'url', 'testmanifest');
|
||||
settingsHelper.get(function(value) {
|
||||
assert.equal(value, 'testmanifest');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
107
gaia/apps/sharedtest/test/unit/device_storage_test.js
Executable file
107
gaia/apps/sharedtest/test/unit/device_storage_test.js
Executable file
|
|
@ -0,0 +1,107 @@
|
|||
'use strict';
|
||||
|
||||
suite('DeviceStorage', function() {
|
||||
// If the API hasn't landed yet, then don't test it.
|
||||
if (!navigator.getDeviceStorage) {
|
||||
test('navigator.getDeviceStorage() doesn\'t exist; not running tests',
|
||||
function() {});
|
||||
return;
|
||||
}
|
||||
|
||||
var storageAreas = navigator.getDeviceStorage('pictures');
|
||||
|
||||
if (storageAreas == null) {
|
||||
test('navigator.getDeviceStorage() returns null on this platform; ' +
|
||||
'not running tests',
|
||||
function() {});
|
||||
return;
|
||||
}
|
||||
else {
|
||||
/*
|
||||
// These tests are currently failing and have been temporarily disabled as per
|
||||
// Bug 838993. They should be fixed and re-enabled as soon as possible as per
|
||||
// Bug 840493.
|
||||
test('getDeviceStorage() returns an array', function() {
|
||||
assert.ok(storageAreas);
|
||||
assert.isTrue(Array.isArray(storageAreas));
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
// These tests are currently failing and have been temporarily disabled as per
|
||||
// Bug 838993. They should be fixed and re-enabled as soon as possible as per
|
||||
// Bug 840493.
|
||||
var storage = storageAreas[0];
|
||||
test('write, read, enumerate and delete a file', function(done) {
|
||||
var directory = 'test';
|
||||
var filename = Math.random().toString();
|
||||
var pathname = directory + '/' + filename;
|
||||
var blob = new Blob(['hello', ' world'], {type: 'text/plain'});
|
||||
var addrequest = storage.addNamed(blob, pathname);
|
||||
addrequest.onerror = function() {
|
||||
done(new Error('DeviceStorage.addNamed() onerror ' +
|
||||
addrequest.error.name));
|
||||
};
|
||||
addrequest.onsuccess = function() {
|
||||
var readrequest = storage.get(pathname);
|
||||
readrequest.onerror = function() {
|
||||
done(new Error('DeviceStorage.get() error ' +
|
||||
readrequest.error.name));
|
||||
};
|
||||
readrequest.onsuccess = function() {
|
||||
var file = readrequest.result;
|
||||
assert.ok(file, 'file is okay');
|
||||
var reader = new FileReader();
|
||||
reader.readAsText(file);
|
||||
reader.onerror = function() {
|
||||
done(new Error('FileReader.readAsText() error ' +
|
||||
reader.error.name));
|
||||
};
|
||||
reader.onload = function() {
|
||||
assert.equal(reader.result, 'hello world');
|
||||
// XXX
|
||||
// If I just enumerate the directory and look for the
|
||||
// filename itself, then the delete() call later on
|
||||
// doesn't actually remove the file.
|
||||
// Doing it this way still leaves the empty directory around
|
||||
// of course.
|
||||
var cursor = storage.enumerate();
|
||||
var foundfile = false;
|
||||
cursor.onerror = function() {
|
||||
done(new Error('DeviceStorage.enumerate() error ' +
|
||||
cursor.error.name));
|
||||
|
||||
};
|
||||
cursor.onsuccess = function() {
|
||||
if (cursor.result !== null) {
|
||||
if (cursor.result.name === pathname)
|
||||
foundfile = true;
|
||||
cursor.continue();
|
||||
}
|
||||
else { // we're done
|
||||
assert.ok(foundfile === true, 'enumerated the new file');
|
||||
|
||||
var deleterequest = storage.delete(pathname);
|
||||
deleterequest.onerror = function() {
|
||||
done(new Error('DeviceStorage.delete() error ' +
|
||||
deleterequest.error.name));
|
||||
};
|
||||
deleterequest.onsuccess = function() {
|
||||
var readrequest2 = storage.get(pathname);
|
||||
// We've just deleted, so this should fail
|
||||
readrequest2.onsuccess = function() {
|
||||
done(new ERROR("DeviceStorage.delete() didn't delete"));
|
||||
};
|
||||
readrequest2.onerror = function() {
|
||||
done();
|
||||
};
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
});
|
||||
*/
|
||||
});
|
||||
259
gaia/apps/sharedtest/test/unit/dialer/contacts_test.js
Executable file
259
gaia/apps/sharedtest/test/unit/dialer/contacts_test.js
Executable file
|
|
@ -0,0 +1,259 @@
|
|||
/*global MocksHelper, Contacts, MockMozContactsObj,
|
||||
MockasyncStorage, Mockfb
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
require('/shared/test/unit/mocks/mock_mozContacts.js');
|
||||
require('/shared/test/unit/mocks/mock_contact_all_fields.js');
|
||||
require('/shared/test/unit/mocks/mock_lazy_loader.js');
|
||||
require('/shared/test/unit/mocks/mock_async_storage.js');
|
||||
require('/shared//test/unit/mocks/mock_simple_phone_matcher.js');
|
||||
require('/apps/communications/contacts/test/unit/mock_fb.js');
|
||||
|
||||
var mocksHelperForContacts = new MocksHelper([
|
||||
'asyncStorage',
|
||||
'LazyLoader',
|
||||
'SimplePhoneMatcher',
|
||||
'fb'
|
||||
]);
|
||||
|
||||
mocksHelperForContacts.init();
|
||||
|
||||
require('/shared/js/dialer/contacts.js');
|
||||
|
||||
suite('dialer/contacts', function() {
|
||||
var realMozContacts;
|
||||
var searchNumber = '3334445555';
|
||||
var mozContactNum = '638883';
|
||||
var aMozTestContact = {
|
||||
id: 'abcdef',
|
||||
givenName: ['Carlos'],
|
||||
familyName: ['Fernández'],
|
||||
tel: [
|
||||
{
|
||||
type: ['home'],
|
||||
value: mozContactNum
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var aFacebookContact = {
|
||||
id:'567',
|
||||
updated: 'date',
|
||||
additionalName:['Green'],
|
||||
adr:[{type:['home'],locality:'Palencia', region:'Castilla y León',
|
||||
countryName:'España'},{type:['current'], locality:'Greater London',
|
||||
region:'London', countryName:'United Kingdom'}],
|
||||
bday:'Thu, 01 Jan 1970 00:00:00 GMT',
|
||||
email:[{type:['personal'],value:'test@test.com'},
|
||||
{type:['work'],value:'test@work.com',pref:true}],
|
||||
honorificPrefix:['Mr.'],familyName:['Taylor'],givenName:['Bret'],
|
||||
nickname:['PG'],jobTitle:['Sr. Software Architect'],name:['Bret Taylor'],
|
||||
org:['FB'],tel:[{value:'+346578888888',type:['mobile'],carrier:'TEF',
|
||||
pref:true},{value:'+3120777777',type:['Home'],carrier:'KPN'}],
|
||||
url:[{type:['fb_profile_photo'],value:'https://abcd1.jpg'}],
|
||||
category:['favorite','facebook','not_linked',220439],note:['Note 1'],
|
||||
photo:[{}]
|
||||
};
|
||||
aFacebookContact.bday = new Date(0);
|
||||
|
||||
mocksHelperForContacts.attachTestHelpers();
|
||||
|
||||
function checkContacts (test, expected) {
|
||||
for (var key in expected) {
|
||||
if (key !== 'photo') {
|
||||
assert.deepEqual(test[key], expected[key], 'Contact.' + key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suiteSetup(function() {
|
||||
realMozContacts = navigator.mozContacts;
|
||||
navigator.mozContacts = new MockMozContactsObj([aMozTestContact]);
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
navigator.mozContacts = realMozContacts;
|
||||
});
|
||||
|
||||
suite('> Contacts Revision', function() {
|
||||
test('> Should return revision', function(done) {
|
||||
Contacts.getRevision(function(status) {
|
||||
done(function() {
|
||||
assert.deepEqual(status, 'fakeRevision');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('> FindByNumber', function() {
|
||||
var expectedMatchingTel = {type: ['home'], value: mozContactNum };
|
||||
var expectedMatchingContact = {id: 'abcdef', givenName: ['Carlos'],
|
||||
familyName: ['Fernández'],
|
||||
tel: [expectedMatchingTel]};
|
||||
|
||||
setup(function() {
|
||||
this.sinon.spy(navigator.mozContacts, 'find');
|
||||
this.sinon.spy(Mockfb, 'getContactByNumber');
|
||||
this.sinon.spy(Mockfb, 'getData');
|
||||
});
|
||||
|
||||
test('should find the exact number for < 7 digits', function() {
|
||||
var options = {
|
||||
filterBy: ['tel'],
|
||||
filterOp: 'equals',
|
||||
filterValue: mozContactNum
|
||||
};
|
||||
Contacts.findByNumber(mozContactNum, function(contact, matchingTel) {
|
||||
sinon.assert.calledWith(navigator.mozContacts.find, options);
|
||||
assert.deepEqual(matchingTel, expectedMatchingTel);
|
||||
assert.deepEqual(contact, expectedMatchingContact);
|
||||
});
|
||||
});
|
||||
|
||||
test('should find numbers matching for >= 7 digits', function() {
|
||||
var options = {
|
||||
filterBy: ['tel'],
|
||||
filterOp: 'match',
|
||||
filterValue: '6388830123'
|
||||
};
|
||||
|
||||
Contacts.findByNumber('6388830123', function(contact, matchingTel) {
|
||||
sinon.assert.calledWith(navigator.mozContacts.find, options);
|
||||
assert.deepEqual(matchingTel, expectedMatchingTel);
|
||||
assert.deepEqual(contact, expectedMatchingContact);
|
||||
});
|
||||
});
|
||||
|
||||
test('should search FB if no MozContact found', function() {
|
||||
navigator.mozContacts.setContacts([]);
|
||||
Contacts.findByNumber('6388830123', function(contact, matchingTel) {
|
||||
sinon.assert.calledWith(Mockfb.getContactByNumber, '6388830123');
|
||||
});
|
||||
});
|
||||
|
||||
test('should NOT search FB if MozContact found', function() {
|
||||
navigator.mozContacts.setContacts([aMozTestContact]);
|
||||
|
||||
Contacts.findByNumber(mozContactNum, function(contact, matchingTel) {
|
||||
sinon.assert.notCalled(Mockfb.getContactByNumber);
|
||||
assert.deepEqual(matchingTel, expectedMatchingTel);
|
||||
assert.deepEqual(contact, expectedMatchingContact);
|
||||
});
|
||||
});
|
||||
|
||||
test('should return null if no FB or MozContact found', function() {
|
||||
navigator.mozContacts.setContacts([]);
|
||||
Mockfb.setEmptyContacts();
|
||||
Contacts.findByNumber(searchNumber, function(contact, matchingTel) {
|
||||
assert.isNull(contact);
|
||||
});
|
||||
});
|
||||
|
||||
test('should merge w/FB if contact is facebook linked', function() {
|
||||
navigator.mozContacts.setContacts([aMozTestContact]);
|
||||
Mockfb.setIsFbLinked(true);
|
||||
Contacts.findByNumber(mozContactNum, function(contact, matchingTel) {
|
||||
sinon.assert.calledOnce(Mockfb.getData);
|
||||
assert.deepEqual(matchingTel, expectedMatchingTel);
|
||||
//Check that the facebook data was added. Fake out the date (but
|
||||
//validate it's a date format first) to avoid having the test fail
|
||||
//due to date being mismatched.
|
||||
assert.ok(contact.updated instanceof Date &&
|
||||
!isNaN(contact.updated.valueOf()));
|
||||
contact.updated = 'date';
|
||||
checkContacts(contact, aFacebookContact);
|
||||
});
|
||||
});
|
||||
|
||||
test('should NOT merge w/FB if not linked', function() {
|
||||
navigator.mozContacts.setContacts([aMozTestContact]);
|
||||
Mockfb.setIsFbLinked(false);
|
||||
Contacts.findByNumber(searchNumber, function(contact, matchingTel) {
|
||||
sinon.assert.notCalled(Mockfb.getData);
|
||||
assert.notEqual(contact.id, '567');
|
||||
assert.isUndefined(contact.jobTitle);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('> FindListByNumber ', function() {
|
||||
var limit = 20;
|
||||
|
||||
setup(function() {
|
||||
this.sinon.spy(navigator.mozContacts, 'find');
|
||||
this.sinon.spy(Mockfb, 'isFbContact');
|
||||
this.sinon.spy(Mockfb, 'getData');
|
||||
|
||||
this.sinon.stub(MockasyncStorage, 'getItem');
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
MockasyncStorage.getItem.restore();
|
||||
});
|
||||
|
||||
test('should call find with correct filter option', function() {
|
||||
//From contacts.js
|
||||
var options = {
|
||||
filterBy: ['tel'],
|
||||
filterOp: 'contains',
|
||||
filterValue: searchNumber,
|
||||
sortBy: 'familyName',
|
||||
sortOrder: 'ascending',
|
||||
filterLimit: limit
|
||||
};
|
||||
|
||||
Contacts.findListByNumber(searchNumber, limit, function() {
|
||||
sinon.assert.calledWith(navigator.mozContacts.find, options);
|
||||
});
|
||||
});
|
||||
|
||||
test('should merge w/FB contacts if mozContacts are also FB contacts',
|
||||
function() {
|
||||
Mockfb.setIsFbContact(true);
|
||||
navigator.mozContacts.setContacts([aMozTestContact]);
|
||||
MockasyncStorage.getItem.yields(false);
|
||||
|
||||
Contacts.findListByNumber(limit, searchNumber, function(contacts) {
|
||||
sinon.assert.called(Mockfb.isFbContact);
|
||||
sinon.assert.called(Mockfb.getData);
|
||||
//Check that the facebook data was added. Fake out the date (but
|
||||
//validate it's a date format first) to avoid having the test fail
|
||||
//due to date being mismatched.
|
||||
assert.ok(contacts[0].updated instanceof Date &&
|
||||
!isNaN(contacts[0].updated.valueOf()));
|
||||
contacts[0].updated = 'date';
|
||||
// Phone numbers haven't been merged
|
||||
aFacebookContact.tel = aMozTestContact.tel;
|
||||
checkContacts(contacts[0], aFacebookContact);
|
||||
});
|
||||
});
|
||||
|
||||
test('should not merge w/FB contacts if mozContacts is not FB contacts',
|
||||
function() {
|
||||
Mockfb.setIsFbContact(false);
|
||||
navigator.mozContacts.setContacts([aMozTestContact]);
|
||||
MockasyncStorage.getItem.yields(true);
|
||||
|
||||
Contacts.findListByNumber(limit, searchNumber, function(contacts) {
|
||||
sinon.assert.called(Mockfb.isFbContact);
|
||||
//Check that facebook data was not added
|
||||
assert.isUndefined(contacts[0].honorificPrefix);
|
||||
assert.isUndefined(contacts[0].jobTitle);
|
||||
assert.isUndefined(contacts[0].org);
|
||||
assert.isUndefined(contacts[0].note);
|
||||
//Check that existing MozContact data is unchanged
|
||||
assert.deepEqual(contacts[0], aMozTestContact);
|
||||
});
|
||||
});
|
||||
|
||||
test('should NOT get FB contacts if no MozContacts found', function() {
|
||||
navigator.mozContacts.setContacts([]);
|
||||
Contacts.findListByNumber(searchNumber, limit, function(contacts) {
|
||||
sinon.assert.notCalled(Mockfb.isFbContact);
|
||||
assert.isNull(contacts);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
327
gaia/apps/sharedtest/test/unit/dialer/font_size_manager_test.js
Executable file
327
gaia/apps/sharedtest/test/unit/dialer/font_size_manager_test.js
Executable file
|
|
@ -0,0 +1,327 @@
|
|||
/* globals FontSizeManager, FontSizeUtils, MocksHelper, MockL10n */
|
||||
|
||||
'use strict';
|
||||
|
||||
require('/shared/test/unit/mocks/mock_font_size_utils.js');
|
||||
require('/shared/test/unit/mocks/mock_l10n.js');
|
||||
|
||||
require('/shared/js/dialer/font_size_manager.js');
|
||||
|
||||
var mocksHelperForCallScreen = new MocksHelper([
|
||||
'FontSizeUtils'
|
||||
]).init();
|
||||
|
||||
suite('font size manager', function() {
|
||||
var ROOT_FONT_SIZE = 10;
|
||||
|
||||
var realMozL10n;
|
||||
|
||||
var view;
|
||||
var bdiNode;
|
||||
var innerEl;
|
||||
|
||||
mocksHelperForCallScreen.attachTestHelpers();
|
||||
|
||||
suiteSetup(function() {
|
||||
realMozL10n = navigator.mozL10n;
|
||||
navigator.mozL10n = MockL10n;
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
navigator.mozL10n = realMozL10n;
|
||||
});
|
||||
|
||||
suite('adaptToSpace', function() {
|
||||
setup(function() {
|
||||
this.sinon.stub(FontSizeUtils, 'getMaxFontSizeInfo');
|
||||
});
|
||||
|
||||
var scenarios = [0, // FontSizeManager.DIAL_PAD
|
||||
1, // FontSizeManager.SINGLE_CALL
|
||||
2, // FontSizeManager.CALL_WAITING
|
||||
3, // FontSizeManager.STATUS_BAR
|
||||
4]; // FontSizeManager.SECOND_INCOMING_CALL
|
||||
scenarios.forEach(function(scenario) {
|
||||
test('does nothing for empty inputs, scenario ' + scenario, function() {
|
||||
view = document.createElement('input');
|
||||
document.body.appendChild(view);
|
||||
|
||||
FontSizeManager.adaptToSpace(scenario, view);
|
||||
|
||||
assert.equal(view.style.fontSize, '');
|
||||
});
|
||||
|
||||
test('does nothing for empty elements, scenario ' + scenario, function() {
|
||||
view.textContent = '';
|
||||
|
||||
FontSizeManager.adaptToSpace(scenario, view);
|
||||
|
||||
assert.equal(view.style.fontSize, '');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
var viewElements = [
|
||||
{
|
||||
element: 'div',
|
||||
textField: 'textContent'
|
||||
},
|
||||
{
|
||||
element: 'input',
|
||||
textField: 'value'
|
||||
},
|
||||
{
|
||||
element: 'div',
|
||||
childElement: 'bdi',
|
||||
textField: 'textContent'
|
||||
}
|
||||
];
|
||||
|
||||
viewElements.forEach(function(viewElement) {
|
||||
suite('on ' + viewElement.element, function() {
|
||||
var getExpectedEllipsizedText = function(side, direction) {
|
||||
// MockL10n does not invert text when in RTL mode, but FSM will
|
||||
// treat the left side as the end for the purpose of ellipses.
|
||||
return side === 'end' ? 'fo\u2026' : '\u2026ar';
|
||||
};
|
||||
|
||||
setup(function() {
|
||||
document.documentElement.style.fontSize = ROOT_FONT_SIZE + 'px';
|
||||
|
||||
view = document.createElement(viewElement.element);
|
||||
if(viewElement.childElement) {
|
||||
innerEl = document.createElement(viewElement.childElement);
|
||||
view.appendChild(innerEl);
|
||||
} else {
|
||||
innerEl = view;
|
||||
}
|
||||
innerEl[viewElement.textField] = 'foobar';
|
||||
document.body.appendChild(view);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
test('calls FSU with the proper arguments', function() {
|
||||
FontSizeUtils.getMaxFontSizeInfo.returns({
|
||||
fontSize: 10,
|
||||
overflow: false
|
||||
});
|
||||
|
||||
view.style.fontFamily = 'Arial';
|
||||
view.style.width = '200px';
|
||||
|
||||
FontSizeManager.adaptToSpace(FontSizeManager.DIAL_PAD, view);
|
||||
|
||||
sinon.assert.calledWith(FontSizeUtils.getMaxFontSizeInfo,
|
||||
view[viewElement.textField],
|
||||
[26, 30, 34, 38, 41],
|
||||
view.style.fontFamily,
|
||||
view.getBoundingClientRect().width);
|
||||
});
|
||||
|
||||
test('changes the font size', function() {
|
||||
FontSizeUtils.getMaxFontSizeInfo.returns({
|
||||
fontSize: 42,
|
||||
overflow: false
|
||||
});
|
||||
|
||||
FontSizeManager.adaptToSpace(FontSizeManager.DIAL_PAD, view);
|
||||
|
||||
assert.equal(view.style.fontSize, '42px');
|
||||
});
|
||||
|
||||
test('forces maxfontsize', function() {
|
||||
FontSizeUtils.getMaxFontSizeInfo.returns({
|
||||
fontSize: 10,
|
||||
overflow: false
|
||||
});
|
||||
|
||||
FontSizeManager.adaptToSpace(FontSizeManager.DIAL_PAD, view, true);
|
||||
|
||||
sinon.assert.calledWith(FontSizeUtils.getMaxFontSizeInfo,
|
||||
view[viewElement.textField],
|
||||
[41]);
|
||||
});
|
||||
|
||||
suite('ellipses in LTR/RTL modes', function() {
|
||||
var l10nLanguageDirection;
|
||||
|
||||
suiteSetup(function() {
|
||||
l10nLanguageDirection = MockL10n.language.direction;
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
MockL10n.language.direction = l10nLanguageDirection;
|
||||
});
|
||||
|
||||
['ltr', 'rtl'].forEach(function(direction) {
|
||||
test('adds ellipsis', function() {
|
||||
MockL10n.language.direction = direction;
|
||||
|
||||
FontSizeUtils.getMaxFontSizeInfo.returns({
|
||||
fontSize: 10,
|
||||
overflow: true
|
||||
});
|
||||
this.sinon.stub(FontSizeUtils, 'getOverflowCount').returns(2);
|
||||
|
||||
FontSizeManager.adaptToSpace(FontSizeManager.DIAL_PAD, view,
|
||||
true);
|
||||
|
||||
assert.equal(view[viewElement.textField],
|
||||
getExpectedEllipsizedText('begin', direction));
|
||||
});
|
||||
|
||||
test('adds ellipsis to the correct side', function() {
|
||||
MockL10n.language.direction = direction;
|
||||
|
||||
FontSizeUtils.getMaxFontSizeInfo.returns({
|
||||
fontSize: 10,
|
||||
overflow: true
|
||||
});
|
||||
this.sinon.stub(FontSizeUtils, 'getOverflowCount').returns(2);
|
||||
|
||||
FontSizeManager.adaptToSpace(FontSizeManager.DIAL_PAD, view, true,
|
||||
'end');
|
||||
|
||||
assert.equal(view[viewElement.textField],
|
||||
getExpectedEllipsizedText('end', direction));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('special view cases', function() {
|
||||
var originalFontSize;
|
||||
|
||||
setup(function() {
|
||||
document.documentElement.style.fontSize = ROOT_FONT_SIZE + 'px';
|
||||
|
||||
FontSizeUtils.getMaxFontSizeInfo.returns({
|
||||
fontSize: 42,
|
||||
overflow: false
|
||||
});
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
suite('on <input> with no value', function() {
|
||||
setup(function() {
|
||||
view = document.createElement('input');
|
||||
document.body.appendChild(view);
|
||||
});
|
||||
|
||||
test('no adaptation is made', function() {
|
||||
FontSizeManager.adaptToSpace(FontSizeManager.DIAL_PAD, view);
|
||||
assert.notEqual(view.style.fontSize, '42px');
|
||||
});
|
||||
});
|
||||
|
||||
suite('on child <bdi>', function() {
|
||||
setup(function() {
|
||||
originalFontSize = document.documentElement.style.fontSize;
|
||||
|
||||
document.documentElement.style.fontSize = ROOT_FONT_SIZE + 'px';
|
||||
|
||||
bdiNode = document.createElement('bdi');
|
||||
bdiNode.textContent = 'testing';
|
||||
|
||||
view = document.createElement('div');
|
||||
view.appendChild(bdiNode);
|
||||
|
||||
document.body.appendChild(view);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
document.body.innerHTML = '';
|
||||
document.documentElement.style.fontSize = originalFontSize;
|
||||
});
|
||||
|
||||
test('bdi Node is preserved', function() {
|
||||
FontSizeUtils.getMaxFontSizeInfo.returns({
|
||||
fontSize: 42,
|
||||
overflow: true
|
||||
});
|
||||
|
||||
FontSizeManager.adaptToSpace(FontSizeManager.SINGLE_CALL, view);
|
||||
var el = view.querySelector('bdi');
|
||||
assert.isNotNull(el);
|
||||
});
|
||||
});
|
||||
|
||||
suite('on <div> with no text content', function() {
|
||||
setup(function() {
|
||||
view = document.createElement('div');
|
||||
document.body.appendChild(view);
|
||||
});
|
||||
|
||||
test('no adaptation is made', function() {
|
||||
FontSizeManager.adaptToSpace(FontSizeManager.DIAL_PAD, view);
|
||||
assert.notEqual(view.style.fontSize, '42px');
|
||||
});
|
||||
});
|
||||
|
||||
// Bug 1082139 - JavascriptException: JavascriptException: TypeError:
|
||||
// window.getComputedStyle(...) is null at://
|
||||
// app://callscreen.gaiamobile.org/gaia_build_defer_index.js line: 146
|
||||
suite('if window.getComputedStyle(view) is null', function() {
|
||||
setup(function() {
|
||||
view = document.createElement('input');
|
||||
view.value = 'foobar';
|
||||
document.body.appendChild(view);
|
||||
|
||||
this.sinon.stub(window, 'getComputedStyle').returns(null);
|
||||
});
|
||||
|
||||
test('no adaptation is made', function() {
|
||||
FontSizeManager.adaptToSpace(FontSizeManager.DIAL_PAD, view);
|
||||
assert.notEqual(view.style.fontSize, '42px');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('ensureFixedBaseline', function() {
|
||||
setup(function() {
|
||||
document.documentElement.style.fontSize = ROOT_FONT_SIZE + 'px';
|
||||
view = document.createElement('div');
|
||||
view.textContent = 'foobar';
|
||||
document.body.appendChild(view);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
test('sets correct line heights', function() {
|
||||
FontSizeManager.ensureFixedBaseline(FontSizeManager.SINGLE_CALL, view);
|
||||
assert.equal(view.style.lineHeight, '46px');
|
||||
|
||||
view.style.fontSize = '100px';
|
||||
FontSizeManager.ensureFixedBaseline(FontSizeManager.SINGLE_CALL, view);
|
||||
assert.equal(view.style.lineHeight, '1px');
|
||||
});
|
||||
});
|
||||
|
||||
suite('resetFixedBaseline', function() {
|
||||
setup(function() {
|
||||
view = document.createElement('div');
|
||||
view.textContent = 'foobar';
|
||||
view.style.lineHeight = '12px';
|
||||
document.body.appendChild(view);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
test('resets line heights', function() {
|
||||
FontSizeManager.resetFixedBaseline(view);
|
||||
assert.equal(view.style.lineHeight, '');
|
||||
});
|
||||
});
|
||||
});
|
||||
253
gaia/apps/sharedtest/test/unit/dialer/telephony_messages_test.js
Executable file
253
gaia/apps/sharedtest/test/unit/dialer/telephony_messages_test.js
Executable file
|
|
@ -0,0 +1,253 @@
|
|||
'use strict';
|
||||
|
||||
/* globals MocksHelper, TelephonyMessages, MockConfirmDialog, LazyLoader */
|
||||
|
||||
require('/shared/test/unit/mocks/mock_lazy_loader.js');
|
||||
require('/shared/test/unit/mocks/mock_confirm_dialog.js');
|
||||
require('/shared/test/unit/mocks/dialer/mock_tone_player.js');
|
||||
require('/shared/test/unit/mocks/dialer/mock_telephony_messages.js');
|
||||
|
||||
require('/shared/js/dialer/telephony_messages.js');
|
||||
|
||||
var mocksHelperForTelephonyMessages = new MocksHelper([
|
||||
'LazyLoader',
|
||||
'TonePlayer',
|
||||
'ConfirmDialog'
|
||||
]);
|
||||
|
||||
suite('Telephony messages', function() {
|
||||
var subject;
|
||||
|
||||
mocksHelperForTelephonyMessages.attachTestHelpers();
|
||||
|
||||
suiteSetup(function() {
|
||||
subject = TelephonyMessages;
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
// This doesn't really matter. We do this to prevent the output from
|
||||
// appearing during testing.
|
||||
this.sinon.stub(console, 'error');
|
||||
});
|
||||
|
||||
suite('Display messages', function() {
|
||||
var expectedMessages = [
|
||||
{
|
||||
message: 'BadNumber',
|
||||
title: 'invalidNumberToDialTitle',
|
||||
body: 'invalidNumberToDialMessage'
|
||||
},
|
||||
{
|
||||
message: 'FlightMode',
|
||||
title: 'callAirplaneModeTitle',
|
||||
body: 'callAirplaneModeMessage'
|
||||
},
|
||||
{
|
||||
message: 'NoNetwork',
|
||||
title: 'emergencyDialogTitle',
|
||||
body: 'emergencyDialogBodyBadNumber'
|
||||
},
|
||||
{
|
||||
message: 'DeviceNotAccepted',
|
||||
title: 'emergencyDialogTitle',
|
||||
body: 'emergencyDialogBodyDeviceNotAccepted'
|
||||
},
|
||||
{
|
||||
message: 'UnableToCall',
|
||||
title: 'unableToCallTitle',
|
||||
body: 'unableToCallMessage'
|
||||
},
|
||||
{
|
||||
message: 'NumberIsBusy',
|
||||
title: 'numberIsBusyTitle',
|
||||
body: 'numberIsBusyMessage'
|
||||
},
|
||||
{
|
||||
message: 'FixedDialingNumbers',
|
||||
title: 'fdnIsActiveTitle',
|
||||
body: 'fdnIsActiveMessage'
|
||||
},
|
||||
{
|
||||
message: 'OtherConnectionInUse',
|
||||
title: 'otherConnectionInUseTitle',
|
||||
body: 'otherConnectionInUseMessage'
|
||||
},
|
||||
{
|
||||
message: 'EmergencyCallOnly',
|
||||
title: 'emergency-call-only',
|
||||
body: 'emergency-call-error'
|
||||
},
|
||||
{
|
||||
message: 'OperatorDeterminedBarring',
|
||||
title: 'unableToCallTitle',
|
||||
body: 'callBarredByTheOperatorMessage'
|
||||
}
|
||||
];
|
||||
|
||||
setup(function() {
|
||||
this.sinon.spy(MockConfirmDialog, 'show');
|
||||
});
|
||||
|
||||
suite('Lazyloading', function() {
|
||||
setup(function() {
|
||||
this.sinon.spy(LazyLoader, 'load');
|
||||
});
|
||||
|
||||
test('Loads #confirmation-message element', function() {
|
||||
var confirmationMessage = document.createElement('span');
|
||||
confirmationMessage.id = 'confirmation-message';
|
||||
document.body.appendChild(confirmationMessage);
|
||||
|
||||
subject.displayMessage('BadNumber');
|
||||
|
||||
assert.notEqual(
|
||||
LazyLoader.load.args[0][0].indexOf(confirmationMessage), -1);
|
||||
|
||||
document.body.removeChild(confirmationMessage);
|
||||
});
|
||||
|
||||
test('Loads ConfirmDialog', function() {
|
||||
subject.displayMessage('BadNumber');
|
||||
|
||||
assert.notEqual(
|
||||
LazyLoader.load.args[0][0].indexOf('/shared/style/confirm.css'), -1);
|
||||
assert.notEqual(
|
||||
LazyLoader.load.args[0][0].indexOf('/shared/js/confirm.js'), -1);
|
||||
});
|
||||
});
|
||||
|
||||
expectedMessages.forEach(function(messageTuple) {
|
||||
test(messageTuple.message + ' displays message ' + messageTuple.title,
|
||||
function() {
|
||||
subject.displayMessage(messageTuple.message, '123');
|
||||
sinon.assert.calledWith(MockConfirmDialog.show, messageTuple.title,
|
||||
{id: messageTuple.body, args: {number: '123'}});
|
||||
});
|
||||
});
|
||||
|
||||
test('Invalid input message doesn\'t display anything', function() {
|
||||
subject.displayMessage('asdfghjkl');
|
||||
sinon.assert.notCalled(MockConfirmDialog.show);
|
||||
});
|
||||
|
||||
suite('ConfirmDialog', function() {
|
||||
test('Calls ConfirmDialog.show() with correct args', function() {
|
||||
subject.displayMessage('BadNumber', '123');
|
||||
assert.equal(MockConfirmDialog.show.args[0][2].title,
|
||||
'emergencyDialogBtnOk');
|
||||
assert.equal(typeof MockConfirmDialog.show.args[0][2].callback,
|
||||
'function');
|
||||
});
|
||||
|
||||
test('Clicking a button closes the ConfirmDialog', function() {
|
||||
this.sinon.spy(MockConfirmDialog, 'hide');
|
||||
|
||||
subject.displayMessage('BadNumber', '123');
|
||||
MockConfirmDialog.show.args[0][2].callback();
|
||||
|
||||
sinon.assert.calledOnce(MockConfirmDialog.hide);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('Disconnection handling', function() {
|
||||
setup(function() {
|
||||
this.sinon.spy(subject, 'handleError');
|
||||
});
|
||||
|
||||
test('normal call clearing', function() {
|
||||
subject.handleDisconnect(
|
||||
'NormalCallClearing', '123', subject.REGULAR_CALL
|
||||
);
|
||||
sinon.assert.notCalled(subject.handleError);
|
||||
});
|
||||
|
||||
test('abnormal call disconnection', function() {
|
||||
var reason = 'Busy';
|
||||
|
||||
subject.handleDisconnect(reason, '123', subject.REGULAR_CALL);
|
||||
sinon.assert.calledWith(
|
||||
subject.handleError, reason + 'Error', '123', subject.REGULAR_CALL
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Error handling', function() {
|
||||
var expectedErrors = [
|
||||
{
|
||||
error: 'BadNumberError',
|
||||
message: 'BadNumber'
|
||||
},
|
||||
{
|
||||
error: 'DeviceNotAcceptedError',
|
||||
message: 'DeviceNotAccepted',
|
||||
},
|
||||
{
|
||||
error: 'RadioNotAvailable',
|
||||
message: 'FlightMode'
|
||||
},
|
||||
{
|
||||
error: 'BusyError',
|
||||
message: 'NumberIsBusy'
|
||||
},
|
||||
{
|
||||
error: 'OtherConnectionInUse',
|
||||
message: 'OtherConnectionInUse'
|
||||
},
|
||||
{
|
||||
error: 'OperatorDeterminedBarringError',
|
||||
message: 'OperatorDeterminedBarring'
|
||||
},
|
||||
{
|
||||
// This is not an actual type. It is the catch-all for unhandled errors.
|
||||
error: 'Unknown/unhandled error',
|
||||
message: 'UnableToCall'
|
||||
}
|
||||
];
|
||||
|
||||
setup(function() {
|
||||
this.sinon.spy(subject, 'displayMessage');
|
||||
});
|
||||
|
||||
expectedErrors.forEach(function(errorTuple) {
|
||||
test(errorTuple.error + ' displays ' + errorTuple.message + ' error',
|
||||
function() {
|
||||
subject.handleError(errorTuple.error, '123', subject.REGULAR_CALL);
|
||||
sinon.assert.calledWith(subject.displayMessage, errorTuple.message);
|
||||
});
|
||||
});
|
||||
|
||||
test('FDNBlockedError displays FixedDialingNumbers error', function() {
|
||||
subject.handleError('FDNBlockedError', '123', subject.REGULAR_CALL);
|
||||
sinon.assert.calledWith(
|
||||
subject.displayMessage, 'FixedDialingNumbers', '123');
|
||||
});
|
||||
|
||||
test('FdnCheckFailure displays FixedDialingNumbers error', function() {
|
||||
subject.handleError('FdnCheckFailure', '123', subject.REGULAR_CALL);
|
||||
sinon.assert.calledWith(
|
||||
subject.displayMessage, 'FixedDialingNumbers', '123');
|
||||
});
|
||||
|
||||
test('BusyError displays NumberIsBusy error', function() {
|
||||
subject.handleError('BusyError');
|
||||
sinon.assert.calledWith(subject.displayMessage, 'NumberIsBusy');
|
||||
});
|
||||
|
||||
test('BadNumberError displays NoNetwork error (no network)', function() {
|
||||
subject.handleError('BadNumberError', '123', subject.NO_NETWORK);
|
||||
sinon.assert.calledWith(subject.displayMessage, 'NoNetwork');
|
||||
});
|
||||
|
||||
test('BadNumberError displays EmergencyCallOnly error (emergency only)',
|
||||
function() {
|
||||
subject.handleError('BadNumberError', '123', subject.EMERGENCY_ONLY);
|
||||
sinon.assert.calledWith(subject.displayMessage, 'EmergencyCallOnly');
|
||||
});
|
||||
|
||||
test('ModifiedDialError not displayed', function() {
|
||||
subject.handleError('ModifiedDialError');
|
||||
sinon.assert.notCalled(subject.displayMessage);
|
||||
});
|
||||
});
|
||||
});
|
||||
230
gaia/apps/sharedtest/test/unit/dialer/utils_test.js
Executable file
230
gaia/apps/sharedtest/test/unit/dialer/utils_test.js
Executable file
|
|
@ -0,0 +1,230 @@
|
|||
/* global MockL10n, Utils, l10nAssert */
|
||||
|
||||
'use strict';
|
||||
|
||||
require('/shared/test/unit/mocks/dialer/mock_contacts.js');
|
||||
require('/shared/test/unit/mocks/mock_l10n.js');
|
||||
|
||||
require('/shared/js/dialer/utils.js');
|
||||
|
||||
suite('dialer/utils', function() {
|
||||
var realL10n;
|
||||
var subject;
|
||||
|
||||
suiteSetup(function() {
|
||||
realL10n = navigator.mozL10n;
|
||||
navigator.mozL10n = MockL10n;
|
||||
subject = Utils;
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
navigator.mozL10n = realL10n;
|
||||
});
|
||||
|
||||
suite('Utility library', function() {
|
||||
test('#headerDate should identify yesterday as a time range', function() {
|
||||
var timestamp = Math.round(new Date().getTime() / 1000);
|
||||
var tsYesterday = (timestamp - (25 * 3600)) * 1000;
|
||||
var elem = document.createElement('span');
|
||||
subject.setHeaderDate(elem, tsYesterday);
|
||||
l10nAssert(elem, 'yesterday');
|
||||
});
|
||||
|
||||
test('#headerDate should identify today as a time range', function() {
|
||||
var tsToday = Math.round(new Date().getTime());
|
||||
var elem = document.createElement('span');
|
||||
subject.setHeaderDate(elem, tsToday);
|
||||
l10nAssert(elem, 'today');
|
||||
});
|
||||
|
||||
test('#headerDate should format 3 days ago as a weekday name', function() {
|
||||
var ts = Math.round(new Date().getTime() / 1000);
|
||||
var ts3DaysAgo = (ts - (73 * 3600)) * 1000;
|
||||
var threeDaysAgo = new
|
||||
Date(ts3DaysAgo).toLocaleString(navigator.languages, {
|
||||
weekday: 'long'
|
||||
});
|
||||
var elem = document.createElement('span');
|
||||
subject.setHeaderDate(elem, ts3DaysAgo);
|
||||
assert.equal(elem.textContent, threeDaysAgo);
|
||||
});
|
||||
|
||||
test('#headerDate should format a week ago as a date', function() {
|
||||
var ts = Math.round(new Date().getTime() / 1000);
|
||||
var tsWeekAgo = (ts - (193 * 3600)) * 1000;
|
||||
var weekAgo = new
|
||||
Date(tsWeekAgo).toLocaleString(navigator.languages, {
|
||||
year: '2-digit',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
});
|
||||
var elem = document.createElement('span');
|
||||
subject.setHeaderDate(elem, tsWeekAgo);
|
||||
assert.equal(elem.textContent, weekAgo);
|
||||
});
|
||||
|
||||
test('#headerDate should identify incorrect date', function() {
|
||||
var elem = document.createElement('span');
|
||||
subject.setHeaderDate(elem, 'not-a-date');
|
||||
l10nAssert(elem, 'incorrectDate');
|
||||
});
|
||||
|
||||
test('#prettyDate should identify 12 hour time', function() {
|
||||
var tsToday = Math.round(new Date().getTime());
|
||||
window.navigator.mozHour12 = true;
|
||||
var hourString = new
|
||||
Date(tsToday).toLocaleString(navigator.languages, {
|
||||
hour12: navigator.mozHour12,
|
||||
hour: 'numeric',
|
||||
minute: 'numeric'
|
||||
});
|
||||
assert.equal(subject.prettyDate(tsToday), hourString);
|
||||
});
|
||||
|
||||
test('#prettyDate should identify 24 hour time', function() {
|
||||
var tsToday = Math.round(new Date().getTime());
|
||||
window.navigator.mozHour12 = false;
|
||||
var hourString = new
|
||||
Date(tsToday).toLocaleString(navigator.languages, {
|
||||
hour12: navigator.mozHour12,
|
||||
hour: 'numeric',
|
||||
minute: 'numeric'
|
||||
});
|
||||
assert.equal(subject.prettyDate(tsToday), hourString);
|
||||
});
|
||||
});
|
||||
|
||||
suite('prettyDuration', function() {
|
||||
var durationNode;
|
||||
|
||||
setup(function() {
|
||||
durationNode = document.createElement('div');
|
||||
this.sinon.spy(MockL10n, 'setAttributes');
|
||||
});
|
||||
|
||||
test('formats as minutes if less than one hour', function() {
|
||||
Utils.prettyDuration(durationNode, 60 * 60 * 1000 - 1);
|
||||
sinon.assert.calledWith(MockL10n.setAttributes, durationNode,
|
||||
'callDurationMinutes', { h: '00', m: '59', s: '59' });
|
||||
});
|
||||
|
||||
test('formats with hours if more than one hour', function() {
|
||||
Utils.prettyDuration(durationNode, 60 * 60 * 1000);
|
||||
sinon.assert.calledWith(MockL10n.setAttributes, durationNode,
|
||||
'callDurationHours', { h: '01', m: '00', s: '00' });
|
||||
});
|
||||
|
||||
test('Single digits are padded', function() {
|
||||
var hours = 2;
|
||||
var minutes = 4;
|
||||
var seconds = 7;
|
||||
var duration = hours * 60 * 60 + minutes * 60 + seconds;
|
||||
Utils.prettyDuration(durationNode, duration * 1000);
|
||||
sinon.assert.calledWith(MockL10n.setAttributes, durationNode,
|
||||
'callDurationHours', { h: '02', m: '04', s: '07' });
|
||||
});
|
||||
|
||||
suite('When using text format', function() {
|
||||
test('single digits are not padded', function() {
|
||||
var hours = 1;
|
||||
var minutes = 4;
|
||||
var seconds = 2;
|
||||
var duration = hours * 60 * 60 + minutes * 60 + seconds;
|
||||
Utils.prettyDuration(durationNode, duration * 1000,
|
||||
'callDurationTextFormat');
|
||||
sinon.assert.calledWith(MockL10n.setAttributes, durationNode,
|
||||
'callDurationTextFormatHours', { h: '1', m: '4', s: '2' });
|
||||
});
|
||||
|
||||
test('formats as minutes if less than one hour', function() {
|
||||
Utils.prettyDuration(durationNode, 60 * 60 * 1000 - 1,
|
||||
'callDurationTextFormat');
|
||||
sinon.assert.calledWith(MockL10n.setAttributes, durationNode,
|
||||
'callDurationTextFormatMinutes', { h: '0', m: '59', s: '59' });
|
||||
});
|
||||
|
||||
test('formats as seconds if less than one minute', function() {
|
||||
Utils.prettyDuration(durationNode, 60 * 1000 - 1,
|
||||
'callDurationTextFormat');
|
||||
sinon.assert.calledWith(MockL10n.setAttributes, durationNode,
|
||||
'callDurationTextFormatSeconds', { h: '0', m: '0', s: '59' });
|
||||
});
|
||||
});
|
||||
|
||||
suite('isPhoneType()', function() {
|
||||
test('Identifies known phone types correctly', function() {
|
||||
[
|
||||
'mobile',
|
||||
'home',
|
||||
'work',
|
||||
'personal',
|
||||
'faxHome',
|
||||
'faxOffice',
|
||||
'faxOther',
|
||||
'other'
|
||||
].forEach(type => { assert.isTrue(Utils.isPhoneType(type)); });
|
||||
});
|
||||
test('Identifies unknown types correctly', function() {
|
||||
assert.isFalse(Utils.isPhoneType('custom-phone-type'));
|
||||
});
|
||||
});
|
||||
|
||||
suite('getPhoneNumberAdditionalInfo()', function() {
|
||||
var dummyCarrier = 'Dummy carrier';
|
||||
var dummyCustomType = 'My custom type';
|
||||
|
||||
test('No type, no carrier', function() {
|
||||
var result = Utils.getPhoneNumberAdditionalInfo({});
|
||||
|
||||
assert.deepEqual(result, {id: 'phone_type_mobile', args: null});
|
||||
});
|
||||
|
||||
test('Specific type, no carrier', function() {
|
||||
var result = Utils.getPhoneNumberAdditionalInfo({
|
||||
type: 'work'
|
||||
});
|
||||
|
||||
assert.deepEqual(result, {id: 'phone_type_work', args: null});
|
||||
});
|
||||
|
||||
test('Custom type, no carrier', function() {
|
||||
var result = Utils.getPhoneNumberAdditionalInfo({
|
||||
type: dummyCustomType
|
||||
});
|
||||
|
||||
assert.propertyVal(result, 'id', 'phone_type_custom');
|
||||
assert.deepPropertyVal(result, 'args.type', dummyCustomType);
|
||||
});
|
||||
|
||||
test('No type with carrier', function() {
|
||||
var result = Utils.getPhoneNumberAdditionalInfo({
|
||||
carrier: dummyCarrier
|
||||
});
|
||||
|
||||
assert.propertyVal(result, 'id', 'phone_type_mobile_and_carrier');
|
||||
assert.deepPropertyVal(result, 'args.carrier', dummyCarrier);
|
||||
});
|
||||
|
||||
test('Specific type with carrier', function() {
|
||||
var result = Utils.getPhoneNumberAdditionalInfo({
|
||||
carrier: dummyCarrier,
|
||||
type: 'work'
|
||||
});
|
||||
|
||||
assert.propertyVal(result, 'id', 'phone_type_work_and_carrier');
|
||||
assert.deepPropertyVal(result, 'args.carrier', dummyCarrier);
|
||||
});
|
||||
|
||||
test('Custom type with carrier', function() {
|
||||
var result = Utils.getPhoneNumberAdditionalInfo({
|
||||
carrier: dummyCarrier,
|
||||
type: dummyCustomType
|
||||
});
|
||||
|
||||
assert.propertyVal(result, 'id', 'phone_type_custom_and_carrier');
|
||||
assert.deepPropertyVal(result, 'args.carrier', dummyCarrier);
|
||||
assert.deepPropertyVal(result, 'args.type', dummyCustomType);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
83
gaia/apps/sharedtest/test/unit/downsample_test.js
Executable file
83
gaia/apps/sharedtest/test/unit/downsample_test.js
Executable file
|
|
@ -0,0 +1,83 @@
|
|||
'use strict';
|
||||
|
||||
/* global Downsample */
|
||||
|
||||
require('/shared/js/media/downsample.js');
|
||||
|
||||
suite('Downsample tests', function() {
|
||||
test('Check API', function() {
|
||||
assert.typeOf(Downsample, 'object');
|
||||
assert.typeOf(Downsample.sizeAtLeast, 'function');
|
||||
assert.typeOf(Downsample.sizeNoMoreThan, 'function');
|
||||
assert.typeOf(Downsample.areaAtLeast, 'function');
|
||||
assert.typeOf(Downsample.areaNoMoreThan, 'function');
|
||||
assert.typeOf(Downsample.MAX_SIZE_REDUCTION, 'number');
|
||||
assert.typeOf(Downsample.MAX_AREA_REDUCTION, 'number');
|
||||
assert.typeOf(Downsample.NONE, 'object');
|
||||
assert.equal(Downsample.NONE.dimensionScale, 1);
|
||||
assert.equal(Downsample.NONE.areaScale, 1);
|
||||
assert.equal(Downsample.NONE.toString(), '');
|
||||
});
|
||||
|
||||
var scales = [
|
||||
10, 2, 1,
|
||||
1 / 8, 2 / 8, 3 / 8, 4 / 8, 5 / 8, 6 / 8, 7 / 8,
|
||||
0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1,
|
||||
0.09, 0.08, 0.07, 0.06, 0.05, 0.04, 0.03, 0.02, 0.01
|
||||
];
|
||||
|
||||
// This is the same function we use in downsample.js
|
||||
function round(x) {
|
||||
return Math.round(x * 100) / 100;
|
||||
}
|
||||
|
||||
test('sizeAtLeast', function() {
|
||||
var min = 1 / Downsample.MAX_SIZE_REDUCTION;
|
||||
scales.forEach(function(scale) {
|
||||
var ds = Downsample.sizeAtLeast(scale);
|
||||
assert.operator(ds.dimensionScale, '<=', round(Math.max(scale, min)));
|
||||
assert.equal(ds.scale(100), ds.dimensionScale * 100);
|
||||
assert.ok(ds === Downsample.NONE || ds.toString()[0] === '#');
|
||||
});
|
||||
});
|
||||
|
||||
test('areaAtLeast', function() {
|
||||
var min = 1 / Downsample.MAX_AREA_REDUCTION;
|
||||
scales.forEach(function(scale) {
|
||||
var ds = Downsample.areaAtLeast(scale);
|
||||
assert.operator(ds.areaScale, '<=', round(Math.max(scale, min)));
|
||||
|
||||
ds = Downsample.areaAtLeast(scale * scale);
|
||||
assert.operator(ds.areaScale, '<=', round(Math.max(scale * scale, min)));
|
||||
assert.equal(ds.scale(100), ds.dimensionScale * 100);
|
||||
assert.ok(ds === Downsample.NONE || ds.toString()[0] === '#');
|
||||
});
|
||||
});
|
||||
|
||||
test('sizeNoMoreThan', function() {
|
||||
scales.forEach(function(scale) {
|
||||
var ds = Downsample.sizeNoMoreThan(scale);
|
||||
assert.operator(ds.dimensionScale, '>=', round(Math.min(scale, 1)));
|
||||
assert.equal(ds.scale(100), ds.dimensionScale * 100);
|
||||
assert.ok(ds === Downsample.NONE || ds.toString()[0] === '#');
|
||||
});
|
||||
});
|
||||
|
||||
test('areaNoMoreThan', function() {
|
||||
scales.forEach(function(scale) {
|
||||
var ds = Downsample.areaNoMoreThan(scale);
|
||||
assert.operator(ds.areaScale, '>=', round(Math.min(scale, 1)));
|
||||
|
||||
ds = Downsample.areaNoMoreThan(scale * scale);
|
||||
assert.operator(ds.areaScale, '>=', round(Math.min(scale * scale, 1)));
|
||||
assert.equal(ds.scale(100), ds.dimensionScale * 100);
|
||||
assert.ok(ds === Downsample.NONE || ds.toString()[0] === '#');
|
||||
});
|
||||
});
|
||||
|
||||
test('max reduction amounts', function() {
|
||||
var ds = Downsample.sizeNoMoreThan(0);
|
||||
assert.equal(ds.dimensionScale, round(1 / Downsample.MAX_SIZE_REDUCTION));
|
||||
assert.equal(ds.areaScale, round(1 / Downsample.MAX_AREA_REDUCTION));
|
||||
});
|
||||
});
|
||||
73
gaia/apps/sharedtest/test/unit/elements/gaia_checkbox/gaia_checkbox_test.js
Executable file
73
gaia/apps/sharedtest/test/unit/elements/gaia_checkbox/gaia_checkbox_test.js
Executable file
|
|
@ -0,0 +1,73 @@
|
|||
'use strict';
|
||||
|
||||
require('/shared/js/component_utils.js');
|
||||
require('/shared/elements/gaia_checkbox/script.js');
|
||||
|
||||
suite('GaiaCheckbox', function() {
|
||||
setup(function() {
|
||||
this.container = document.createElement('div');
|
||||
});
|
||||
|
||||
test('Label is populated', function() {
|
||||
this.container.innerHTML = '<gaia-checkbox><label>my label</label>' +
|
||||
'</gaia-checkbox>';
|
||||
assert.equal(this.container.querySelector('label').textContent,
|
||||
'my label');
|
||||
});
|
||||
|
||||
test('Not checked by default', function() {
|
||||
this.container.innerHTML = '<gaia-checkbox></gaia-checkbox>';
|
||||
var element = this.container.firstElementChild;
|
||||
assert.equal(element.checked, false);
|
||||
});
|
||||
|
||||
test('ClassName is proxied to shadow dom', function() {
|
||||
this.container.innerHTML = '';
|
||||
|
||||
var element = document.createElement('gaia-checkbox');
|
||||
element.className = 'inline';
|
||||
this.container.appendChild(element);
|
||||
var wrapper = element.shadowRoot.querySelector('#checkbox');
|
||||
assert.equal(wrapper.classList.contains('inline'), true);
|
||||
|
||||
element.className = '';
|
||||
assert.equal(wrapper.classList.contains('inline'), false);
|
||||
});
|
||||
|
||||
test('Checks the element based on checked attr', function() {
|
||||
this.container.innerHTML =
|
||||
'<gaia-checkbox checked></gaia-checkbox>';
|
||||
var element = this.container.firstElementChild;
|
||||
var span = element.shadowRoot.querySelector('span');
|
||||
assert.equal(element.checked, true);
|
||||
assert.ok(span.classList.contains('checked'));
|
||||
});
|
||||
|
||||
test('Attribute change toggles checked state', function() {
|
||||
this.container.innerHTML = '<gaia-checkbox></gaia-checkbox>';
|
||||
var element = this.container.firstElementChild;
|
||||
var checkbox = element.shadowRoot.querySelector('span');
|
||||
assert.equal(element.checked, false);
|
||||
assert.ok(!checkbox.classList.contains('checked'));
|
||||
|
||||
element.checked = 'checked';
|
||||
assert.ok(element.checked);
|
||||
assert.ok(checkbox.classList.contains('checked'));
|
||||
});
|
||||
|
||||
test('Gets right value after click', function(done) {
|
||||
this.container.innerHTML = '<gaia-checkbox></gaia-checkbox>';
|
||||
var element = this.container.firstElementChild;
|
||||
assert.ok(!element.checked);
|
||||
|
||||
element.addEventListener('change', function(e) {
|
||||
assert.equal(e.target.checked, true);
|
||||
done();
|
||||
});
|
||||
|
||||
element.handleClick({
|
||||
preventDefault: function() {},
|
||||
stopImmediatePropagation: function() {}
|
||||
});
|
||||
});
|
||||
});
|
||||
74
gaia/apps/sharedtest/test/unit/elements/gaia_confirm/gaia_confirm_test.js
Executable file
74
gaia/apps/sharedtest/test/unit/elements/gaia_confirm/gaia_confirm_test.js
Executable file
|
|
@ -0,0 +1,74 @@
|
|||
'use strict';
|
||||
|
||||
require('/shared/js/component_utils.js');
|
||||
require('/shared/elements/gaia_confirm/script.js');
|
||||
|
||||
suite('GaiaSwitch', function() {
|
||||
setup(function() {
|
||||
this.container = document.createElement('div');
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
this.container.parentNode.removeChild(this.container);
|
||||
});
|
||||
|
||||
var subject;
|
||||
setup(function() {
|
||||
this.container.innerHTML = '<gaia-confirm>' +
|
||||
'<h1>title</h1>' +
|
||||
'<p>description</p>' +
|
||||
'<gaia-buttons skin="dark">' +
|
||||
'<button class="cancel">Cancel</button>' +
|
||||
'<button class="confirm recommend">Submit</button>' +
|
||||
'</gaia-buttons>' +
|
||||
'</gaia-confirm>';
|
||||
|
||||
subject = this.container.firstElementChild;
|
||||
// XXX: Currently we must build the entire dom in one go then insert it.
|
||||
document.body.appendChild(this.container);
|
||||
});
|
||||
|
||||
test('Event : confirm', function(done) {
|
||||
this.timeout(100);
|
||||
subject.addEventListener('confirm', function confirm() {
|
||||
subject.removeEventListener('confirm', confirm);
|
||||
done();
|
||||
});
|
||||
|
||||
var confirm = this.container.querySelector('.confirm');
|
||||
confirm.dispatchEvent(new CustomEvent('click'));
|
||||
});
|
||||
|
||||
|
||||
test('Event : cancel', function(done) {
|
||||
this.timeout(100);
|
||||
subject.addEventListener('confirm', function confirm() {
|
||||
subject.removeEventListener('confirm', confirm);
|
||||
done();
|
||||
});
|
||||
|
||||
var confirm = this.container.querySelector('.confirm');
|
||||
confirm.dispatchEvent(new CustomEvent('click'));
|
||||
});
|
||||
|
||||
test('Content is populated', function() {
|
||||
var element = this.container.firstElementChild;
|
||||
|
||||
// Validate title
|
||||
var titles = element.querySelectorAll('h1');
|
||||
assert.equal(titles.length, 1);
|
||||
assert.equal(titles[0].textContent, 'title');
|
||||
|
||||
// Validate description
|
||||
var descs = element.querySelectorAll('p');
|
||||
assert.equal(descs.length, 1);
|
||||
assert.equal(descs[0].textContent, 'description');
|
||||
|
||||
// Validate buttons
|
||||
var buttons = element.querySelectorAll('button');
|
||||
assert.equal(buttons.length, 2);
|
||||
assert.equal(buttons[0].textContent, 'Cancel');
|
||||
assert.equal(buttons[1].textContent, 'Submit');
|
||||
});
|
||||
|
||||
});
|
||||
75
gaia/apps/sharedtest/test/unit/elements/gaia_drawer/gaia_drawer_test.js
Executable file
75
gaia/apps/sharedtest/test/unit/elements/gaia_drawer/gaia_drawer_test.js
Executable file
|
|
@ -0,0 +1,75 @@
|
|||
/*jshint maxlen:false*/
|
||||
'use strict';
|
||||
|
||||
require('/shared/js/component_utils.js');
|
||||
require('/shared/elements/gaia_drawer/script.js');
|
||||
|
||||
suite('GaiaDrawer', function() {
|
||||
|
||||
setup(function() {
|
||||
this.sandbox = sinon.sandbox.create();
|
||||
|
||||
// Sizes are in rems, so we set the base font-size
|
||||
document.documentElement.style.fontSize = '10px';
|
||||
|
||||
this.container = document.createElement('div');
|
||||
this.container.innerHTML = '<gaia-drawer></gaia-drawer>';
|
||||
this.el = this.container.firstElementChild;
|
||||
this.el.style.height = '300px';
|
||||
|
||||
sinon.spy(this.el, 'open');
|
||||
sinon.spy(this.el, 'close');
|
||||
sinon.spy(this.el, 'toggle');
|
||||
|
||||
// Insert into DOM to get styling
|
||||
document.body.appendChild(this.el);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
this.sandbox.restore();
|
||||
});
|
||||
|
||||
test('Should toggle open when `open` attribute changes', function() {
|
||||
this.el.setAttribute('open', '');
|
||||
sinon.assert.called(this.el.open);
|
||||
|
||||
this.el.removeAttribute('open');
|
||||
sinon.assert.called(this.el.close);
|
||||
});
|
||||
|
||||
test('Should close the drawer when the background is clicked', function() {
|
||||
this.el.open();
|
||||
sinon.assert.notCalled(this.el.close);
|
||||
|
||||
this.el.shadowRoot.getElementById('background').click();
|
||||
sinon.assert.called(this.el.close);
|
||||
});
|
||||
|
||||
test('Should not close the drawer when the content is clicked', function() {
|
||||
this.el.open();
|
||||
sinon.assert.notCalled(this.el.close);
|
||||
this.el.shadowRoot.getElementById('content').click();
|
||||
sinon.assert.notCalled(this.el.close);
|
||||
});
|
||||
|
||||
suite('GaiaDrawer#toggle()', function() {
|
||||
test('Should toggle the `open` state', function() {
|
||||
this.el.toggle();
|
||||
sinon.assert.called(this.el.open);
|
||||
assert.isTrue(this.el.hasAttribute('open'));
|
||||
|
||||
this.el.toggle();
|
||||
sinon.assert.called(this.el.close);
|
||||
assert.isFalse(this.el.hasAttribute('open'));
|
||||
});
|
||||
|
||||
test('Should derive state from argument if passed', function() {
|
||||
this.el.toggle(true);
|
||||
sinon.assert.called(this.el.open);
|
||||
assert.isTrue(this.el.hasAttribute('open'));
|
||||
|
||||
this.el.toggle(true);
|
||||
assert.isTrue(this.el.hasAttribute('open'));
|
||||
});
|
||||
});
|
||||
});
|
||||
213
gaia/apps/sharedtest/test/unit/elements/gaia_grid/gaia_grid_test.js
Executable file
213
gaia/apps/sharedtest/test/unit/elements/gaia_grid/gaia_grid_test.js
Executable file
|
|
@ -0,0 +1,213 @@
|
|||
/* global GaiaGrid */
|
||||
/* global MocksHelper */
|
||||
|
||||
'use strict';
|
||||
|
||||
require('/shared/test/unit/mocks/mock_lazy_loader.js');
|
||||
require('/shared/elements/gaia_grid/js/grid_dragdrop.js');
|
||||
require('/shared/elements/gaia_grid/js/grid_icon_renderer.js');
|
||||
require('/shared/elements/gaia_grid/js/grid_layout.js');
|
||||
require('/shared/elements/gaia_grid/js/grid_view.js');
|
||||
require('/shared/elements/gaia_grid/js/grid_zoom.js');
|
||||
require('/shared/elements/gaia_grid/script.js');
|
||||
require('/shared/elements/gaia_grid/js/items/grid_item.js');
|
||||
require('/shared/elements/gaia_grid/js/items/divider.js');
|
||||
require('/shared/elements/gaia_grid/js/items/placeholder.js');
|
||||
require('/shared/elements/gaia_grid/js/items/group.js');
|
||||
|
||||
var mocksHelperForGrid = new MocksHelper([
|
||||
'LazyLoader'
|
||||
]).init();
|
||||
|
||||
suite('GaiaGrid', function() {
|
||||
|
||||
mocksHelperForGrid.attachTestHelpers();
|
||||
|
||||
setup(function() {
|
||||
this.sinon.useFakeTimers();
|
||||
this.container = document.createElement('div');
|
||||
document.body.appendChild(this.container);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
document.body.removeChild(this.container);
|
||||
});
|
||||
|
||||
suite('features', function() {
|
||||
test('default features', function() {
|
||||
this.container.innerHTML = '<gaia-grid></gaia-grid>';
|
||||
var grid = this.container.firstElementChild._grid;
|
||||
// Some features are loaded after rendering like dragdrop
|
||||
grid.render();
|
||||
assert.equal(grid.dragdrop, undefined);
|
||||
assert.equal(grid.zoom, undefined);
|
||||
assert.ok(grid.layout);
|
||||
});
|
||||
|
||||
test('/w dragdrop', function() {
|
||||
this.container.innerHTML = '<gaia-grid dragdrop></gaia-grid>';
|
||||
var grid = this.container.firstElementChild._grid;
|
||||
grid.render();
|
||||
assert.ok(grid.dragdrop);
|
||||
assert.equal(grid.zoom, undefined);
|
||||
assert.ok(grid.layout);
|
||||
});
|
||||
|
||||
test('/w zoom', function() {
|
||||
this.sinon.stub(window, 'GridZoom');
|
||||
this.container.innerHTML = '<gaia-grid zoom></gaia-grid>';
|
||||
var grid = this.container.firstElementChild._grid;
|
||||
grid.render();
|
||||
assert.ok(grid.zoom);
|
||||
assert.equal(grid.dragdrop, undefined);
|
||||
assert.ok(grid.layout);
|
||||
});
|
||||
|
||||
test('/w dragdrop and disable-sections', function() {
|
||||
this.container.innerHTML =
|
||||
'<gaia-grid dragdrop disable-sections></gaia-grid>';
|
||||
var grid = this.container.firstElementChild._grid;
|
||||
grid.render();
|
||||
assert.ok(grid.config.features.disableSections);
|
||||
});
|
||||
});
|
||||
|
||||
suite('items', function() {
|
||||
var element;
|
||||
|
||||
var fakeBookmarkItem = {
|
||||
identifier: 'http://mozilla.org',
|
||||
detail: {
|
||||
type: 'bookmark',
|
||||
id: 'http://mozilla.org',
|
||||
url: 'http://mozilla.org'
|
||||
},
|
||||
setPosition: function() {},
|
||||
setCoordinates: function() {},
|
||||
render: function() {}
|
||||
};
|
||||
|
||||
var fakeBookmarkItem2 = {
|
||||
identifier: 'http://tid.es',
|
||||
detail: {
|
||||
type: 'bookmark',
|
||||
id: 'http://tid.es',
|
||||
url: 'http://tid.es'
|
||||
},
|
||||
setPosition: function() {},
|
||||
setCoordinates: function() {},
|
||||
render: function() {}
|
||||
};
|
||||
|
||||
var fakeBookmarkItem3 = {
|
||||
identifier: 'http://firefoxos.com',
|
||||
detail: {
|
||||
type: 'bookmark',
|
||||
id: 'http://firefoxos.com',
|
||||
url: 'http://firefoxos.com'
|
||||
},
|
||||
setPosition: function() {},
|
||||
setCoordinates: function() {},
|
||||
render: function() {}
|
||||
};
|
||||
|
||||
setup(function() {
|
||||
this.container.innerHTML = '<gaia-grid></gaia-grid>';
|
||||
element = this.container.firstElementChild;
|
||||
});
|
||||
|
||||
test('adding an item increments count', function() {
|
||||
var itemLength = element.getItems().length;
|
||||
element.add(fakeBookmarkItem);
|
||||
assert.equal(element.getItems().length, itemLength + 1);
|
||||
});
|
||||
|
||||
test('null items do not change item count', function() {
|
||||
var itemLength = element.getItems().length;
|
||||
element.add(null);
|
||||
assert.equal(element.getItems().length, itemLength);
|
||||
});
|
||||
|
||||
test('removeItemByIndex', function() {
|
||||
var itemLength = element.getItems().length;
|
||||
element.add(fakeBookmarkItem2);
|
||||
element.add(fakeBookmarkItem3);
|
||||
assert.equal(element.getItems().length, itemLength + 2);
|
||||
element.removeItemByIndex(0);
|
||||
assert.equal(element.getItems().length, itemLength + 1);
|
||||
});
|
||||
|
||||
test('appendItem adds before divider', function() {
|
||||
element.clear();
|
||||
|
||||
var divider = new GaiaGrid.Divider();
|
||||
element.add(fakeBookmarkItem);
|
||||
element.add(divider);
|
||||
|
||||
var itemLength = element.getItems().length;
|
||||
element.appendItemToExpandedGroup(fakeBookmarkItem2);
|
||||
|
||||
var items = element.getItems();
|
||||
assert.equal(items.length, itemLength + 1);
|
||||
assert.equal(items[items.length - 1], divider);
|
||||
});
|
||||
|
||||
test('appendItemToExpandedGroup expands collapsed divider', function() {
|
||||
element.clear();
|
||||
|
||||
var divider = new GaiaGrid.Divider();
|
||||
element.add(fakeBookmarkItem);
|
||||
element.add(divider);
|
||||
|
||||
var itemLength = element.getItems().length;
|
||||
divider.detail.collapsed = true;
|
||||
element.appendItemToExpandedGroup(fakeBookmarkItem2);
|
||||
this.sinon.clock.tick(20);
|
||||
|
||||
var items = element.getItems();
|
||||
assert.ok(items.length > itemLength);
|
||||
assert.notEqual(divider.detail.collapsed, true);
|
||||
});
|
||||
|
||||
test('clear will dereference item elements', function() {
|
||||
element.clear();
|
||||
var domEl = document.createElement('div');
|
||||
element.appendChild(domEl);
|
||||
var myObj = fakeBookmarkItem;
|
||||
myObj.element = domEl;
|
||||
element.add(myObj);
|
||||
element.clear();
|
||||
assert.equal(myObj.element, null);
|
||||
});
|
||||
|
||||
test('items with null identifiers are not added to the grid', function() {
|
||||
element.clear();
|
||||
var oldIdentifier = fakeBookmarkItem.identifier;
|
||||
fakeBookmarkItem.identifier = null;
|
||||
element.add(fakeBookmarkItem);
|
||||
assert.equal(element.getItems().length, 0);
|
||||
fakeBookmarkItem.identifier = oldIdentifier;
|
||||
});
|
||||
|
||||
test('height with text-rows attribute', function() {
|
||||
this.container.innerHTML = '<gaia-grid></gaia-grid>';
|
||||
var gridEl = this.container.firstElementChild;
|
||||
var grid = this.container.firstElementChild._grid;
|
||||
var defaultItemHeight = grid.layout.gridItemHeight;
|
||||
assert.ok(defaultItemHeight > 0);
|
||||
|
||||
gridEl.setAttribute('text-rows', 3);
|
||||
var threeRowHeight = grid.layout.gridItemHeight;
|
||||
assert.ok(threeRowHeight > defaultItemHeight);
|
||||
|
||||
gridEl.setAttribute('text-rows', 4);
|
||||
var fourRowHeight = grid.layout.gridItemHeight;
|
||||
assert.ok(fourRowHeight > threeRowHeight);
|
||||
assert.equal(fourRowHeight - threeRowHeight,
|
||||
threeRowHeight - defaultItemHeight,
|
||||
'additional text rows match height');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
83
gaia/apps/sharedtest/test/unit/elements/gaia_grid/grid_bookmark_test.js
Executable file
83
gaia/apps/sharedtest/test/unit/elements/gaia_grid/grid_bookmark_test.js
Executable file
|
|
@ -0,0 +1,83 @@
|
|||
'use strict';
|
||||
/* global GaiaGrid */
|
||||
|
||||
require('/shared/elements/gaia_grid/js/grid_icon_renderer.js');
|
||||
require('/shared/elements/gaia_grid/script.js');
|
||||
require('/shared/elements/gaia_grid/js/items/grid_item.js');
|
||||
require('/shared/elements/gaia_grid/js/items/bookmark.js');
|
||||
|
||||
suite('GaiaGrid > Bookmark', function() {
|
||||
|
||||
var subject = null,
|
||||
stub = null;
|
||||
var stubPage1 = {
|
||||
name: 'first',
|
||||
id: 1,
|
||||
icon: 'no',
|
||||
url: 'http://mozilla.org'
|
||||
};
|
||||
|
||||
setup(function() {
|
||||
stub = sinon.stub(GaiaGrid.GridItem.prototype, 'render', function() {
|
||||
this.element = document.createElement('div');
|
||||
});
|
||||
subject = new GaiaGrid.Bookmark(stubPage1);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
stub.restore();
|
||||
});
|
||||
|
||||
test('Bookmark created properly', function() {
|
||||
assert.equal(subject.name, stubPage1.name);
|
||||
assert.equal(subject.icon, stubPage1.icon);
|
||||
assert.equal(subject.identifier, stubPage1.id);
|
||||
assert.equal(subject.detail.url, stubPage1.url);
|
||||
assert.isUndefined(subject.bookmarking);
|
||||
assert.isTrue(subject.isRemovable());
|
||||
assert.isTrue(subject.isEditable());
|
||||
subject.render();
|
||||
assert.isTrue(subject.element.classList.contains('editable'));
|
||||
});
|
||||
|
||||
test('Bookmark is not editable', function() {
|
||||
subject = new GaiaGrid.Bookmark(stubPage1, {
|
||||
isEditable: false
|
||||
});
|
||||
assert.isFalse(subject.isEditable());
|
||||
subject.render();
|
||||
assert.isFalse(subject.element.classList.contains('editable'));
|
||||
});
|
||||
|
||||
test('Launch bookmark', function(done) {
|
||||
var openStub = sinon.stub(window, 'open', function(url, name, features) {
|
||||
openStub.restore();
|
||||
assert.equal(url, stubPage1.url);
|
||||
assert.equal(name, '_samescope');
|
||||
assert.isTrue(features.contains('name=' + subject.name));
|
||||
assert.isTrue(features.contains('icon=' + subject.icon));
|
||||
assert.isTrue(features.contains('remote=true'));
|
||||
assert.isFalse(features.contains('searchName='));
|
||||
assert.isFalse(features.contains('searchUrl='));
|
||||
done();
|
||||
});
|
||||
|
||||
subject.launch();
|
||||
});
|
||||
|
||||
test('Launch bookmark with bookmarking feature', function(done) {
|
||||
var openStub = sinon.stub(window, 'open', function(url, name, features) {
|
||||
openStub.restore();
|
||||
assert.isTrue(features.contains('searchName=' + subject.name));
|
||||
assert.isTrue(features.contains('searchUrl=' +
|
||||
encodeURIComponent(subject.detail.url)));
|
||||
done();
|
||||
});
|
||||
|
||||
subject = new GaiaGrid.Bookmark(stubPage1, {
|
||||
search: true
|
||||
});
|
||||
subject.launch();
|
||||
});
|
||||
|
||||
});
|
||||
360
gaia/apps/sharedtest/test/unit/elements/gaia_grid/grid_dragdrop_test.js
Executable file
360
gaia/apps/sharedtest/test/unit/elements/gaia_grid/grid_dragdrop_test.js
Executable file
|
|
@ -0,0 +1,360 @@
|
|||
'use strict';
|
||||
/* global GaiaGrid, MocksHelper */
|
||||
|
||||
require('/shared/test/unit/mocks/mock_lazy_loader.js');
|
||||
require('/shared/elements/gaia_grid/js/grid_dragdrop.js');
|
||||
require('/shared/elements/gaia_grid/js/grid_icon_renderer.js');
|
||||
require('/shared/elements/gaia_grid/js/grid_layout.js');
|
||||
require('/shared/elements/gaia_grid/js/grid_view.js');
|
||||
require('/shared/elements/gaia_grid/js/grid_zoom.js');
|
||||
require('/shared/elements/gaia_grid/script.js');
|
||||
require('/shared/elements/gaia_grid/js/items/grid_item.js');
|
||||
require('/shared/elements/gaia_grid/js/items/divider.js');
|
||||
require('/shared/elements/gaia_grid/js/items/mozapp.js');
|
||||
require('/shared/elements/gaia_grid/js/items/bookmark.js');
|
||||
require('/shared/elements/gaia_grid/js/items/placeholder.js');
|
||||
require('/shared/elements/gaia_grid/js/items/group.js');
|
||||
|
||||
var mocksHelperForDragDrop = new MocksHelper([
|
||||
'LazyLoader'
|
||||
]).init();
|
||||
|
||||
suite('GaiaGrid > DragDrop', function() {
|
||||
var grid;
|
||||
|
||||
var stubPage1 = {
|
||||
name: 'first',
|
||||
id: 1,
|
||||
icon: 'no',
|
||||
url: 'http://mozilla.org'
|
||||
};
|
||||
|
||||
var stubPage2 = {
|
||||
name: 'second',
|
||||
id: 2,
|
||||
icon: 'no',
|
||||
url: 'http://mozilla.org/2'
|
||||
};
|
||||
|
||||
var stubPage3 = {
|
||||
name: 'third',
|
||||
id: 3,
|
||||
icon: 'no',
|
||||
url: 'http://mozilla.org/3'
|
||||
};
|
||||
|
||||
var countDividers = function() {
|
||||
var nDividers = 0;
|
||||
for (var i = 0, iLen = grid.items.length; i < iLen; i++) {
|
||||
if (grid.items[i].detail.type === 'divider') {
|
||||
nDividers ++;
|
||||
}
|
||||
}
|
||||
return nDividers;
|
||||
};
|
||||
|
||||
mocksHelperForDragDrop.attachTestHelpers();
|
||||
|
||||
suiteSetup(function() {
|
||||
document.body.innerHTML = '<div id="zoom"><div class="arrows">' +
|
||||
'</div><div class="curtain"></div><div class="indicator"></div></div>';
|
||||
this.container = document.createElement('div');
|
||||
|
||||
this.container.innerHTML = '<gaia-grid dragdrop group></gaia-grid>';
|
||||
document.body.appendChild(this.container);
|
||||
grid = this.container.firstElementChild._grid;
|
||||
|
||||
grid.add(new GaiaGrid.Bookmark(stubPage1));
|
||||
grid.add(new GaiaGrid.Bookmark(stubPage2));
|
||||
grid.render();
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
this.sinon.useFakeTimers();
|
||||
});
|
||||
|
||||
test('changes position of icons', function() {
|
||||
var firstBookmark = document.querySelector('.icon');
|
||||
firstBookmark.dispatchEvent(new CustomEvent('contextmenu',
|
||||
{bubbles: true}));
|
||||
assert.ok(grid.dragdrop.inEditMode);
|
||||
assert.ok(firstBookmark.classList.contains('active'));
|
||||
assert.equal(grid.items[0].name, 'first');
|
||||
|
||||
var moveTo = grid.layout.gridItemWidth + 1;
|
||||
|
||||
// XXX: Stub the x/y adjustments, we should probably not depend on this
|
||||
grid.dragdrop.xAdjust = 0;
|
||||
grid.dragdrop.yAdjust = 0;
|
||||
|
||||
grid.dragdrop.handleEvent({
|
||||
type: 'touchmove',
|
||||
touches: [{
|
||||
pageX: moveTo,
|
||||
pageY: 0
|
||||
}]
|
||||
});
|
||||
|
||||
grid.dragdrop.handleEvent({
|
||||
type: 'touchend',
|
||||
stopImmediatePropagation: function() {},
|
||||
preventDefault: function() {}
|
||||
});
|
||||
this.sinon.clock.tick(grid.dragdrop.touchEndFinishDelay);
|
||||
|
||||
grid.dragdrop.handleEvent({ type: 'transitionend' });
|
||||
|
||||
assert.equal(grid.items[0].name, 'second');
|
||||
assert.equal(grid.items[1].name, 'first');
|
||||
});
|
||||
|
||||
test('attention requested if item doesn\'t move', function() {
|
||||
var firstBookmark = grid.items[0].element;
|
||||
var requestAttentionStub = sinon.stub(grid.items[0], 'requestAttention');
|
||||
|
||||
firstBookmark.dispatchEvent(new CustomEvent('contextmenu',
|
||||
{bubbles: true}));
|
||||
grid.dragdrop.handleEvent({
|
||||
type: 'touchend',
|
||||
stopImmediatePropagation: function() {},
|
||||
preventDefault: function() {}
|
||||
});
|
||||
|
||||
this.sinon.clock.tick(grid.dragdrop.touchEndFinishDelay);
|
||||
grid.dragdrop.handleEvent({ type: 'transitionend' });
|
||||
|
||||
assert.ok(requestAttentionStub.called);
|
||||
requestAttentionStub.restore();
|
||||
});
|
||||
|
||||
test('cleanup if the touch gesture is canceled', function() {
|
||||
var firstBookmark = grid.items[0].element;
|
||||
firstBookmark.dispatchEvent(new CustomEvent('contextmenu',
|
||||
{bubbles: true}));
|
||||
assert.ok(grid.dragdrop.inEditMode);
|
||||
assert.ok(firstBookmark.classList.contains('active'));
|
||||
|
||||
grid.dragdrop.handleEvent({
|
||||
type: 'touchcancel',
|
||||
stopImmediatePropagation: function() {},
|
||||
preventDefault: function() {}
|
||||
});
|
||||
|
||||
grid.dragdrop.handleEvent({ type: 'transitionend' });
|
||||
|
||||
assert.isFalse(firstBookmark.classList.contains('active'));
|
||||
});
|
||||
|
||||
test('long-press listener not activated during dragging', function() {
|
||||
grid.dragdrop.icon = grid.items[0];
|
||||
grid.dragdrop.handleEvent({
|
||||
type: 'touchstart',
|
||||
touches: [ { pageX: 0, pageY: 0, screenX: 0, screenY: 0 } ],
|
||||
target: grid.items[0].element
|
||||
});
|
||||
assert.equal(grid.dragdrop.longPressTimeout, null);
|
||||
});
|
||||
|
||||
test('rearrange uses reference of icon for position', function() {
|
||||
var subject = grid.dragdrop;
|
||||
subject.icon = grid.items[0];
|
||||
|
||||
// The current positions are second -> first -> placeholder
|
||||
// Simulate a drop onto the second item (index 2).
|
||||
subject.rearrange(grid.items[2]);
|
||||
|
||||
assert.equal(grid.items[0].name, 'first');
|
||||
assert.equal(grid.items[1].name, 'second');
|
||||
});
|
||||
|
||||
test('create new groups by dropping items at the end', function() {
|
||||
var requestAttentionStub =
|
||||
sinon.stub(GaiaGrid.Divider.prototype, 'requestAttention');
|
||||
var dividers = countDividers();
|
||||
var subject = grid.dragdrop;
|
||||
|
||||
// The current positions are first, second, placeholder, divider,
|
||||
subject.icon = grid.items[1];
|
||||
subject.createNewDivider(grid.items[3]);
|
||||
|
||||
// After creating the new group, there should be one extra divider.
|
||||
assert.equal(countDividers(), dividers + 1);
|
||||
|
||||
// And attention should have been requested on the new divider.
|
||||
var newDivider;
|
||||
for (var i = grid.items.length - 1; i >= 0; i--) {
|
||||
if (grid.items[i].detail.type === 'divider') {
|
||||
newDivider = grid.items[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert.ok(requestAttentionStub.calledOn(newDivider));
|
||||
requestAttentionStub.restore();
|
||||
});
|
||||
|
||||
test('rearrange collapsed group before expanded group', function() {
|
||||
var divider = grid.items[7];
|
||||
assert.equal(divider.detail.type, 'divider');
|
||||
assert.ok(divider.element.classList.contains('group'));
|
||||
|
||||
divider.collapse();
|
||||
this.sinon.clock.tick(20);
|
||||
assert.equal(divider.detail.collapsed, true);
|
||||
|
||||
var subject = grid.dragdrop;
|
||||
subject.icon = divider;
|
||||
subject.rearrange(grid.items[0]);
|
||||
|
||||
assert.equal(grid.items[0].name, 'second');
|
||||
assert.equal(grid.items[2].name, 'first');
|
||||
});
|
||||
|
||||
test('rearrange collapsed group after expanded group', function() {
|
||||
var divider = grid.items[1];
|
||||
assert.equal(divider.detail.type, 'divider');
|
||||
|
||||
var subject = grid.dragdrop;
|
||||
subject.icon = divider;
|
||||
subject.createNewDivider(grid.items[grid.items.length - 1]);
|
||||
|
||||
assert.equal(grid.items[0].name, 'first');
|
||||
assert.equal(grid.items[4].name, 'second');
|
||||
});
|
||||
|
||||
test('rearrange collapsed group before collapsed group', function() {
|
||||
var divider = grid.items[3];
|
||||
assert.equal(divider.detail.type, 'divider');
|
||||
divider.collapse();
|
||||
this.sinon.clock.tick(20);
|
||||
|
||||
divider = grid.items[3];
|
||||
assert.equal(divider.detail.type, 'divider');
|
||||
|
||||
var subject = grid.dragdrop;
|
||||
subject.icon = divider;
|
||||
subject.rearrange(grid.items[0]);
|
||||
|
||||
assert.equal(grid.items[0].name, 'second');
|
||||
assert.equal(grid.items[2].name, 'first');
|
||||
});
|
||||
|
||||
test('rearrange collapsed group after collapsed group', function() {
|
||||
var divider = grid.items[1];
|
||||
assert.equal(divider.detail.type, 'divider');
|
||||
|
||||
var subject = grid.dragdrop;
|
||||
subject.icon = divider;
|
||||
subject.createNewDivider(grid.items[grid.items.length - 1]);
|
||||
|
||||
assert.equal(grid.items[0].name, 'first');
|
||||
assert.equal(grid.items[2].name, 'second');
|
||||
});
|
||||
|
||||
test('create new group between groups', function() {
|
||||
var divider = grid.items[1];
|
||||
assert.equal(divider.detail.type, 'divider');
|
||||
divider.expand();
|
||||
this.sinon.clock.tick(20);
|
||||
|
||||
divider = grid.items[3];
|
||||
assert.equal(divider.detail.type, 'divider');
|
||||
divider.expand();
|
||||
this.sinon.clock.tick(20);
|
||||
|
||||
// Add a new item to test adding groups between groups
|
||||
grid.add(new GaiaGrid.Bookmark(stubPage3), 0);
|
||||
grid.render();
|
||||
|
||||
// Now simulate dragging the first icon over the first divider and check
|
||||
// that a new divider is created, and that the new app is in it
|
||||
var dividers = countDividers();
|
||||
|
||||
divider = grid.items[3];
|
||||
assert.equal(divider.detail.type, 'divider');
|
||||
|
||||
var subject = grid.dragdrop;
|
||||
subject.icon = grid.items[0];
|
||||
subject.createNewDivider(divider);
|
||||
|
||||
assert.equal(countDividers(), dividers + 1);
|
||||
assert.equal(grid.items[4].name, 'third');
|
||||
});
|
||||
|
||||
test('check no visual indicator for redundant moves', function() {
|
||||
// Each of these dividers represents a group with one item, so dragging any
|
||||
// group or item should mark the previous group and the group itself as
|
||||
// invalid drop targets
|
||||
var divider1 = grid.items[3];
|
||||
var divider2 = grid.items[7];
|
||||
var divider3 = grid.items[9];
|
||||
|
||||
var subject = grid.dragdrop;
|
||||
subject.icon = divider2;
|
||||
subject.begin({});
|
||||
|
||||
assert.isTrue(divider1.element.classList.contains('invalid-drop'));
|
||||
assert.isTrue(divider2.element.classList.contains('invalid-drop'));
|
||||
assert.isFalse(divider3.element.classList.contains('invalid-drop'));
|
||||
|
||||
subject.finish();
|
||||
subject.finalize();
|
||||
|
||||
// Check that dragging the first divider over the top of the container
|
||||
// wouldn't cause a redundant move.
|
||||
subject.icon = divider1;
|
||||
subject.begin({});
|
||||
subject.handleEvent({
|
||||
type: 'touchmove',
|
||||
touches: [{
|
||||
pageX: 0,
|
||||
pageY: -100
|
||||
}]
|
||||
});
|
||||
|
||||
assert.isFalse(grid.element.classList.contains('hover-over-top'));
|
||||
|
||||
// Tidy up
|
||||
subject.finish();
|
||||
subject.finalize();
|
||||
|
||||
assert.isFalse(divider1.element.classList.contains('invalid-drop'));
|
||||
assert.isFalse(divider2.element.classList.contains('invalid-drop'));
|
||||
});
|
||||
|
||||
test('empty groups remain during dragging', function() {
|
||||
// Each of the three bookmark items is now in a group by itself. Dragging
|
||||
// any of them outside of that group should leave an empty group, until
|
||||
// dragging finishes (and then that group should disappear).
|
||||
|
||||
// Test this by dragging the first and last items into the middle group,
|
||||
// testing that the empty group remains until the drag finishes.
|
||||
var firstIcon = grid.items[0];
|
||||
var middleIcon = grid.items[4];
|
||||
var lastIcon = grid.items[8];
|
||||
|
||||
// Expand the last divider
|
||||
grid.items[9].expand();
|
||||
this.sinon.clock.tick(20);
|
||||
|
||||
var nDividers = countDividers();
|
||||
var subject = grid.dragdrop;
|
||||
subject.icon = firstIcon;
|
||||
subject.target = firstIcon.element;
|
||||
subject.begin({});
|
||||
subject.rearrange(middleIcon);
|
||||
assert.equal(nDividers, countDividers());
|
||||
subject.finish();
|
||||
subject.finalize();
|
||||
assert.equal(nDividers - 1, countDividers());
|
||||
|
||||
nDividers = countDividers();
|
||||
subject.icon = lastIcon;
|
||||
subject.target = lastIcon.element;
|
||||
subject.begin({});
|
||||
subject.rearrange(firstIcon);
|
||||
assert.equal(nDividers, countDividers());
|
||||
subject.finish();
|
||||
subject.finalize();
|
||||
assert.equal(nDividers - 1, countDividers());
|
||||
});
|
||||
});
|
||||
88
gaia/apps/sharedtest/test/unit/elements/gaia_grid/items/grid_item_test.js
Executable file
88
gaia/apps/sharedtest/test/unit/elements/gaia_grid/items/grid_item_test.js
Executable file
|
|
@ -0,0 +1,88 @@
|
|||
'use strict';
|
||||
/* global GaiaGrid */
|
||||
|
||||
require('/shared/js/url_helper.js');
|
||||
require('/shared/elements/gaia_grid/js/grid_icon_renderer.js');
|
||||
require('/shared/elements/gaia_grid/js/grid_layout.js');
|
||||
require('/shared/elements/gaia_grid/js/grid_view.js');
|
||||
require('/shared/elements/gaia_grid/js/items/placeholder.js');
|
||||
require('/shared/elements/gaia_grid/script.js');
|
||||
require('/shared/elements/gaia_grid/js/items/grid_item.js');
|
||||
|
||||
suite('GridItem', function() {
|
||||
|
||||
setup(function() {
|
||||
this.container = document.createElement('div');
|
||||
this.container.innerHTML = '<gaia-grid></gaia-grid>';
|
||||
document.body.appendChild(this.container);
|
||||
});
|
||||
|
||||
test('renderIconFromSrc sets the background size', function(done) {
|
||||
var subject = new GaiaGrid.GridItem();
|
||||
subject.element = document.createElement('div');
|
||||
|
||||
var original = subject._displayDecoratedIcon;
|
||||
subject._displayDecoratedIcon = function(blob) {
|
||||
original.call(subject, blob);
|
||||
var gridItemHeight = parseInt(this.element.style.height, 10);
|
||||
var backgroundSize = parseInt(this.element.style.backgroundSize, 10);
|
||||
assert.ok(backgroundSize > 0);
|
||||
assert.isTrue(gridItemHeight >= backgroundSize);
|
||||
done();
|
||||
};
|
||||
|
||||
subject.renderIconFromSrc('/style/icons/Blank.png');
|
||||
});
|
||||
|
||||
test('basic auth in icon urls', function() {
|
||||
var subject = new GaiaGrid.GridItem();
|
||||
subject.app = {
|
||||
origin: 'some:user@mozilla.org'
|
||||
};
|
||||
|
||||
var result = subject.closestIconFromList({
|
||||
100: '/icon.png'
|
||||
});
|
||||
assert.equal(result, 'some:user@mozilla.org/icon.png');
|
||||
});
|
||||
|
||||
test('scale doesn\'t affect style width', function() {
|
||||
var subject = new GaiaGrid.GridItem();
|
||||
|
||||
subject.grid.config.element =
|
||||
{ appendChild: function() {}, offsetWidth: 333 };
|
||||
subject.updateTitle = function() {};
|
||||
subject.renderIcon = function() {};
|
||||
|
||||
subject.render();
|
||||
|
||||
var width1 = subject.element.style.width;
|
||||
|
||||
subject.element = null;
|
||||
subject.scale = 2;
|
||||
subject.render();
|
||||
|
||||
var width2 = subject.element.style.width;
|
||||
|
||||
assert.equal(width1, width2);
|
||||
});
|
||||
|
||||
test('Detect W3C web app manifest icons format', function() {
|
||||
var icons = [];
|
||||
window.IconsHelper = {
|
||||
'getBestIconFromWebManifest': function() {}
|
||||
};
|
||||
this.sinon.stub(window.IconsHelper,
|
||||
'getBestIconFromWebManifest', function() {
|
||||
return new URL('http://example.com/icon.png');
|
||||
});
|
||||
var subject = new GaiaGrid.GridItem();
|
||||
subject.app = {
|
||||
'manifestURL': 'http://example.com/manifest.json'
|
||||
};
|
||||
var result = subject.closestIconFromList(icons);
|
||||
assert.equal(result, 'http://example.com/icon.png');
|
||||
window.WebManifestHelper = undefined;
|
||||
});
|
||||
|
||||
});
|
||||
41
gaia/apps/sharedtest/test/unit/elements/gaia_menu/gaia_menu_test.js
Executable file
41
gaia/apps/sharedtest/test/unit/elements/gaia_menu/gaia_menu_test.js
Executable file
|
|
@ -0,0 +1,41 @@
|
|||
'use strict';
|
||||
|
||||
require('/shared/js/component_utils.js');
|
||||
require('/shared/elements/gaia_menu/script.js');
|
||||
|
||||
suite('GaiaMenu', function() {
|
||||
setup(function() {
|
||||
this.container = document.createElement('div');
|
||||
this.container.innerHTML =
|
||||
'<gaia-menu>' +
|
||||
'<header>title</header>' +
|
||||
'<button>button1</button>'+
|
||||
'<button>button2</button>'+
|
||||
'</gaia-menu>';
|
||||
this.element = this.container.firstElementChild;
|
||||
});
|
||||
|
||||
test('Content is populated', function() {
|
||||
// Validate header
|
||||
var headers = this.element.querySelectorAll('header');
|
||||
assert.equal(headers.length, 1);
|
||||
assert.equal(headers[0].textContent, 'title');
|
||||
|
||||
// Validate button
|
||||
var buttons = this.element.querySelectorAll('button');
|
||||
assert.equal(buttons.length, 2);
|
||||
|
||||
// Validate a 'cancel' button was added
|
||||
var cancelButton = this.element.shadowRoot.querySelector('button');
|
||||
assert.equal(cancelButton.textContent, 'Cancel');
|
||||
});
|
||||
|
||||
test('Cancel event is dispatched', function(done) {
|
||||
this.element.addEventListener('gaiamenu-cancel', function(e) {
|
||||
done();
|
||||
});
|
||||
|
||||
this.element.shadowRoot.querySelector('button')
|
||||
.dispatchEvent(new CustomEvent('click'));
|
||||
});
|
||||
});
|
||||
81
gaia/apps/sharedtest/test/unit/elements/gaia_radio/gaia_radio_test.js
Executable file
81
gaia/apps/sharedtest/test/unit/elements/gaia_radio/gaia_radio_test.js
Executable file
|
|
@ -0,0 +1,81 @@
|
|||
'use strict';
|
||||
|
||||
require('/shared/js/component_utils.js');
|
||||
require('/shared/elements/gaia_radio/script.js');
|
||||
|
||||
suite('GaiaRadio', function() {
|
||||
var inputs;
|
||||
|
||||
setup(function() {
|
||||
this.container = document.createElement('div');
|
||||
this.container.innerHTML = '<gaia-radio name="group"></gaia-radio>' +
|
||||
'<gaia-radio role="dialog" name="group" checked></gaia-radio>' +
|
||||
'<gaia-radio name="menu" data-role="menuitemradio" checked></gaia-radio>';
|
||||
document.body.appendChild(this.container);
|
||||
inputs = this.container.querySelectorAll('gaia-radio');
|
||||
});
|
||||
|
||||
function simulateClick(element) {
|
||||
element.handleClick({
|
||||
preventDefault: function() {},
|
||||
stopImmediatePropagation: function() {}
|
||||
});
|
||||
}
|
||||
|
||||
function getRadio(element) {
|
||||
return element.shadowRoot.querySelector('#radio');
|
||||
}
|
||||
|
||||
test('Checked state', function() {
|
||||
assert.equal(inputs[0].checked, false);
|
||||
assert.equal(inputs[1].checked, true);
|
||||
|
||||
assert.equal(getRadio(inputs[0]).getAttribute('aria-checked'), 'false');
|
||||
assert.equal(getRadio(inputs[1]).getAttribute('aria-checked'), 'true');
|
||||
|
||||
simulateClick(inputs[0]);
|
||||
assert.equal(inputs[0].checked, true);
|
||||
assert.equal(inputs[1].checked, false);
|
||||
});
|
||||
|
||||
test('ClassName is proxied to shadow dom', function() {
|
||||
this.container.innerHTML = '';
|
||||
|
||||
var element = document.createElement('gaia-radio');
|
||||
element.className = 'inline';
|
||||
this.container.appendChild(element);
|
||||
var wrapper = element.shadowRoot.querySelector('#radio');
|
||||
assert.equal(wrapper.classList.contains('inline'), true);
|
||||
|
||||
element.className = '';
|
||||
assert.equal(wrapper.classList.contains('inline'), false);
|
||||
});
|
||||
|
||||
test('Check accessibility roles', function() {
|
||||
assert.equal(inputs[0].getAttribute('role'), 'presentation');
|
||||
assert.equal(inputs[1].getAttribute('role'), 'dialog');
|
||||
assert.equal(getRadio(inputs[2]).getAttribute('role'), 'menuitemradio');
|
||||
});
|
||||
|
||||
test('Gets right value after click', function(done) {
|
||||
inputs[0].addEventListener('change', function(e) {
|
||||
assert.equal(e.target.checked, true);
|
||||
assert.equal(inputs[0].checked, true);
|
||||
assert.equal(inputs[1].checked, false);
|
||||
assert.equal(getRadio(inputs[0]).getAttribute('aria-checked'), 'true');
|
||||
assert.equal(getRadio(inputs[1]).getAttribute('aria-checked'), 'false');
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
simulateClick(inputs[0]);
|
||||
});
|
||||
|
||||
test('Clicking when checked does not change state', function() {
|
||||
var input = inputs[0];
|
||||
input.checked = true;
|
||||
simulateClick(input);
|
||||
assert.isTrue(input.checked);
|
||||
});
|
||||
|
||||
});
|
||||
288
gaia/apps/sharedtest/test/unit/elements/gaia_sim_picker/gaia_sim_picker_test.js
Executable file
288
gaia/apps/sharedtest/test/unit/elements/gaia_sim_picker/gaia_sim_picker_test.js
Executable file
|
|
@ -0,0 +1,288 @@
|
|||
/* globals MockGaiaMenu, MocksHelper, MockL10n, MockNavigatorMozIccManager,
|
||||
MockTelephonyHelper */
|
||||
|
||||
'use strict';
|
||||
|
||||
require('/shared/test/unit/mocks/mock_navigator_moz_icc_manager.js');
|
||||
require('/shared/test/unit/mocks/mock_navigator_moz_telephony.js');
|
||||
require('/shared/test/unit/mocks/mock_l10n.js');
|
||||
require('/shared/test/unit/mocks/mock_lazy_loader.js');
|
||||
require('/shared/test/unit/mocks/elements/gaia_menu/mock_gaia_menu.js');
|
||||
require('/shared/test/unit/mocks/dialer/mock_telephony_helper.js');
|
||||
|
||||
require('/shared/js/component_utils.js');
|
||||
require('/shared/elements/gaia_sim_picker/script.js');
|
||||
|
||||
var mocksHelperForGaiaSimPicker = new MocksHelper([
|
||||
'LazyLoader'
|
||||
]).init();
|
||||
|
||||
suite('GaiaSimPicker', function() {
|
||||
var subject;
|
||||
var realMozIccManager;
|
||||
var realL10n;
|
||||
var realTelephonyHelper;
|
||||
var menu;
|
||||
var header;
|
||||
|
||||
mocksHelperForGaiaSimPicker.attachTestHelpers();
|
||||
|
||||
suiteSetup(function() {
|
||||
realMozIccManager = navigator.mozIccManager;
|
||||
navigator.mozIccManager = MockNavigatorMozIccManager;
|
||||
navigator.mozIccManager.mTeardown();
|
||||
|
||||
realL10n = navigator.mozL10n;
|
||||
navigator.mozL10n = MockL10n;
|
||||
|
||||
realTelephonyHelper = window.TelephonyHelper;
|
||||
window.TelephonyHelper = null;
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
navigator.mozIccManager = realMozIccManager;
|
||||
navigator.mozL10n = realL10n;
|
||||
|
||||
window.TelephonyHelper = realTelephonyHelper;
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
this.sinon.spy(MockGaiaMenu, 'show');
|
||||
this.sinon.stub(MockL10n, 'ready');
|
||||
this.sinon.stub(MockL10n, 'once');
|
||||
this.sinon.useFakeTimers();
|
||||
|
||||
this.container = document.createElement('div');
|
||||
this.container.innerHTML =
|
||||
'<gaia-sim-picker></gaia-sim-picker>';
|
||||
subject = this.container.firstElementChild;
|
||||
subject._menu = MockGaiaMenu;
|
||||
|
||||
this.sinon.spy(subject, 'focus');
|
||||
|
||||
navigator.mozIccManager.addIcc(0, {});
|
||||
navigator.mozIccManager.addIcc(1, {});
|
||||
|
||||
subject.getOrPick(0, '1111');
|
||||
|
||||
header = subject.shadowRoot.querySelector('#sim-picker-dial-via');
|
||||
menu = subject.shadowRoot.querySelector('gaia-menu');
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
navigator.mozIccManager.mTeardown();
|
||||
});
|
||||
|
||||
suite('getOrPick/getInUseSim', function() {
|
||||
test('header should contain phone number when getter provided', function() {
|
||||
this.sinon.spy(MockL10n, 'setAttributes');
|
||||
this.sinon.spy(MockL10n, 'translateFragment');
|
||||
subject.getOrPick(0, '1111');
|
||||
sinon.assert.calledWith(MockL10n.setAttributes,
|
||||
header,
|
||||
'gaia-sim-picker-dial-via-with-number',
|
||||
{phoneNumber: '1111'});
|
||||
sinon.assert.calledWith(MockL10n.translateFragment, subject.shadowRoot);
|
||||
});
|
||||
|
||||
test('header should not contain phone number when getter not provided',
|
||||
function() {
|
||||
this.sinon.spy(MockL10n, 'translateFragment');
|
||||
subject.getOrPick(0, null);
|
||||
assert.equal(header.getAttribute('data-l10n-id'),
|
||||
'gaia-sim-picker-select-sim');
|
||||
sinon.assert.calledWith(MockL10n.translateFragment, subject.shadowRoot);
|
||||
});
|
||||
|
||||
test('show the menu twice with different args', function() {
|
||||
this.sinon.spy(MockL10n, 'setAttributes');
|
||||
this.sinon.spy(MockL10n, 'translateFragment');
|
||||
|
||||
subject.getOrPick(0, '1111');
|
||||
sinon.assert.calledWith(MockL10n.setAttributes,
|
||||
header,
|
||||
'gaia-sim-picker-dial-via-with-number',
|
||||
{phoneNumber: '1111'});
|
||||
|
||||
var buttons = menu.querySelectorAll('button');
|
||||
assert.equal(buttons.length, 2);
|
||||
|
||||
subject.getOrPick(0, '2222');
|
||||
sinon.assert.calledWith(MockL10n.setAttributes,
|
||||
header,
|
||||
'gaia-sim-picker-dial-via-with-number',
|
||||
{phoneNumber: '2222'});
|
||||
assert.equal(buttons.length, 2);
|
||||
sinon.assert.alwaysCalledWith(
|
||||
MockL10n.translateFragment, subject.shadowRoot
|
||||
);
|
||||
sinon.assert.calledTwice(MockL10n.translateFragment);
|
||||
});
|
||||
|
||||
test('should show the menu after l10n is ready', function() {
|
||||
MockL10n.once.yield();
|
||||
sinon.assert.calledOnce(MockGaiaMenu.show);
|
||||
});
|
||||
|
||||
test('should focus on self after l10n is ready', function() {
|
||||
MockL10n.once.yield();
|
||||
sinon.assert.calledOnce(subject.focus);
|
||||
});
|
||||
|
||||
test('should retranslate after a language change', function() {
|
||||
this.sinon.stub(MockL10n, 'translateFragment');
|
||||
MockL10n.ready.yield();
|
||||
sinon.assert.calledWith(MockL10n.translateFragment, subject.shadowRoot);
|
||||
});
|
||||
});
|
||||
|
||||
suite('buttons', function() {
|
||||
test('should have 2 option buttons', function() {
|
||||
var buttons = menu.querySelectorAll('button');
|
||||
assert.equal(buttons.length, 2);
|
||||
});
|
||||
|
||||
test('should mark default SIM', function() {
|
||||
var buttons = menu.querySelectorAll('button');
|
||||
for (var i = 0; i < buttons.length; i++) {
|
||||
assert.equal(buttons[i].classList.contains('is-default'), i === 0);
|
||||
}
|
||||
});
|
||||
|
||||
test('should not mark default SIM when none is set', function() {
|
||||
subject.getOrPick(undefined, '2222');
|
||||
|
||||
var buttons = menu.querySelectorAll('button');
|
||||
for (var i = 0; i < buttons.length; i++) {
|
||||
assert.isFalse(buttons[i].classList.contains('is-default'));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
suite('callbacks and functions', function() {
|
||||
var domEventStub;
|
||||
var callbackStub;
|
||||
var passedEvent;
|
||||
var buttons;
|
||||
|
||||
// We must do this because sinon doesn't deep-copy the passed event, which
|
||||
// excludes its `detail` attr in its stub.args array.
|
||||
var copyPassedEvent = function(e) {
|
||||
passedEvent = e;
|
||||
domEventStub(e);
|
||||
};
|
||||
|
||||
setup(function() {
|
||||
domEventStub = this.sinon.stub();
|
||||
callbackStub = this.sinon.stub();
|
||||
|
||||
subject.addEventListener('gaiasimpicker-simselected', copyPassedEvent);
|
||||
subject.getOrPick(0, '1111', callbackStub);
|
||||
|
||||
// The first child is the header.
|
||||
buttons = menu.querySelectorAll('button');
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
subject.removeEventListener('gaiasimpicker-simselected', domEventStub);
|
||||
});
|
||||
|
||||
[0, 1].forEach(function(cardIndex) {
|
||||
test('should dispatch DOM event when SIM ' + cardIndex + ' is selected',
|
||||
function() {
|
||||
buttons[cardIndex].click();
|
||||
|
||||
sinon.assert.calledOnce(domEventStub);
|
||||
assert.equal(passedEvent.detail.cardIndex, cardIndex);
|
||||
assert.equal(menu.hidden, true);
|
||||
});
|
||||
|
||||
test('should fire callback when SIM ' + cardIndex + ' is selected',
|
||||
function() {
|
||||
buttons[cardIndex].click();
|
||||
|
||||
sinon.assert.calledWith(callbackStub, cardIndex);
|
||||
});
|
||||
|
||||
test('should not fire callback if DOM event is preventDefaulted on SIM ' +
|
||||
cardIndex,
|
||||
function() {
|
||||
var callbackStub2 = this.sinon.stub();
|
||||
|
||||
subject.addEventListener('gaiasimpicker-simselected', function _(e) {
|
||||
subject.removeEventListener('gaiasimpicker-simselected', _);
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
subject.getOrPick(0, '1111', callbackStub2);
|
||||
buttons[cardIndex].click();
|
||||
|
||||
sinon.assert.notCalled(callbackStub2);
|
||||
});
|
||||
|
||||
test('should fire all queued up callbacks on SIM ' + cardIndex,
|
||||
function() {
|
||||
callbackStub.reset();
|
||||
var callbackStub2 = this.sinon.stub();
|
||||
|
||||
subject.getOrPick(0, '1111', callbackStub);
|
||||
subject.getOrPick(0, '1111', callbackStub2);
|
||||
|
||||
buttons[cardIndex].click();
|
||||
|
||||
sinon.assert.calledWith(callbackStub, cardIndex);
|
||||
sinon.assert.calledWith(callbackStub2, cardIndex);
|
||||
});
|
||||
|
||||
test('should not fire stale callback on SIM ' + cardIndex, function() {
|
||||
// Should clear callbackStub from queued callbacks.
|
||||
buttons[cardIndex].click();
|
||||
callbackStub.reset();
|
||||
|
||||
var callbackStub2 = this.sinon.stub();
|
||||
|
||||
subject.getOrPick(0, '1111', callbackStub2);
|
||||
buttons[cardIndex].click();
|
||||
|
||||
sinon.assert.notCalled(callbackStub);
|
||||
sinon.assert.calledWith(callbackStub2, cardIndex);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests for special case code for the dialer. The code that it's testing
|
||||
* should have no impact anywhere else.
|
||||
*/
|
||||
suite('with TelephonyHelper', function() {
|
||||
var realTelephonyHelper;
|
||||
|
||||
suiteSetup(function() {
|
||||
realTelephonyHelper = window.TelephonyHelper;
|
||||
window.TelephonyHelper = MockTelephonyHelper;
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
window.TelephonyHelper = realTelephonyHelper;
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
MockTelephonyHelper.mInUseSim = 1;
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
MockTelephonyHelper.mTeardown();
|
||||
});
|
||||
|
||||
test('should callback with in use serviceId', function(done) {
|
||||
subject.addEventListener('gaiasimpicker-simselected', function _(e) {
|
||||
subject.removeEventListener('gaiasimpicker-simselected', _);
|
||||
|
||||
assert.equal(e.detail.cardIndex, 1);
|
||||
done();
|
||||
});
|
||||
|
||||
subject.getOrPick(0, '1111');
|
||||
});
|
||||
});
|
||||
});
|
||||
98
gaia/apps/sharedtest/test/unit/elements/gaia_switch/gaia_switch_test.js
Executable file
98
gaia/apps/sharedtest/test/unit/elements/gaia_switch/gaia_switch_test.js
Executable file
|
|
@ -0,0 +1,98 @@
|
|||
'use strict';
|
||||
|
||||
require('/shared/js/component_utils.js');
|
||||
require('/shared/elements/gaia_switch/script.js');
|
||||
|
||||
suite('GaiaSwitch', function() {
|
||||
setup(function() {
|
||||
this.container = document.createElement('div');
|
||||
});
|
||||
|
||||
test('Label is populated', function() {
|
||||
this.container.innerHTML = '<gaia-switch><label>my label</label>' +
|
||||
'</gaia-switch>';
|
||||
assert.equal(this.container.querySelector('label').textContent,
|
||||
'my label');
|
||||
});
|
||||
|
||||
test('Input is not checked by default', function() {
|
||||
this.container.innerHTML = '<gaia-switch></gaia-switch>';
|
||||
var element = this.container.firstElementChild;
|
||||
var input = element.shadowRoot.querySelector('input');
|
||||
assert.equal(input.checked, false);
|
||||
});
|
||||
|
||||
test('Checks the input based on checked attr', function() {
|
||||
this.container.innerHTML =
|
||||
'<gaia-switch checked></gaia-switch>';
|
||||
var element = this.container.firstElementChild;
|
||||
var input = element.shadowRoot.querySelector('input');
|
||||
assert.equal(input.checked, true);
|
||||
});
|
||||
|
||||
test('Attribute change toggles checked state', function() {
|
||||
this.container.innerHTML = '<gaia-switch></gaia-switch>';
|
||||
var element = this.container.firstElementChild;
|
||||
var input = element.shadowRoot.querySelector('input');
|
||||
assert.equal(input.checked, false);
|
||||
|
||||
element.checked = 'true';
|
||||
assert.equal(input.checked, true);
|
||||
});
|
||||
|
||||
test('Gets right value after click', function(done) {
|
||||
this.container.innerHTML = '<gaia-switch></gaia-switch>';
|
||||
var element = this.container.firstElementChild;
|
||||
var input = element.shadowRoot.querySelector('input');
|
||||
assert.equal(input.checked, false);
|
||||
|
||||
element.addEventListener('click', function(e) {
|
||||
setTimeout(function() {
|
||||
assert.equal(e.target.checked, true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
element.handleClick({
|
||||
target: {},
|
||||
preventDefault: function() {},
|
||||
stopImmediatePropagation: function() {}
|
||||
});
|
||||
});
|
||||
|
||||
test('Dispatches change event', function(done) {
|
||||
this.container.innerHTML = `<div id="wrapper"><gaia-switch>
|
||||
</gaia-switch></div>`;
|
||||
var wrapper = this.container.firstElementChild;
|
||||
var element = wrapper.firstElementChild;
|
||||
var input = element.shadowRoot.querySelector('input');
|
||||
assert.equal(input.checked, false);
|
||||
|
||||
wrapper.addEventListener('change', function(e) {
|
||||
assert.equal(e.type, 'change');
|
||||
assert.equal(e.target, element);
|
||||
done();
|
||||
});
|
||||
|
||||
element.handleClick({
|
||||
target: {},
|
||||
preventDefault: function() {},
|
||||
stopImmediatePropagation: function() {}
|
||||
});
|
||||
});
|
||||
|
||||
test('Clicking link element does not toggle switch', function() {
|
||||
this.container.innerHTML = `<div id="wrapper">
|
||||
<gaia-switch>
|
||||
<label></label><details></details><a href="#">Clicky</a>
|
||||
</gaia-switch>
|
||||
</div>`;
|
||||
var element = this.container.querySelector('gaia-switch');
|
||||
var details = this.container.querySelector('details');
|
||||
var link = this.container.querySelector('a');
|
||||
details.click();
|
||||
assert.equal(element.checked, true, 'Clicking details toggles the switch');
|
||||
link.click();
|
||||
assert.equal(element.checked, true, 'Clicking link does not toggle switch');
|
||||
});
|
||||
});
|
||||
86
gaia/apps/sharedtest/test/unit/elements/gaia_tabs/gaia_tabs_test.js
Executable file
86
gaia/apps/sharedtest/test/unit/elements/gaia_tabs/gaia_tabs_test.js
Executable file
|
|
@ -0,0 +1,86 @@
|
|||
'use strict';
|
||||
|
||||
require('/shared/js/component_utils.js');
|
||||
require('/shared/elements/gaia_tabs/script.js');
|
||||
|
||||
suite('GaiaTabs', function() {
|
||||
setup(function() {
|
||||
this.clock = sinon.useFakeTimers();
|
||||
this.container = document.createElement('div');
|
||||
|
||||
// Sizes are in rems, so we set the base font-size
|
||||
document.documentElement.style.fontSize = '10px';
|
||||
|
||||
this.container.innerHTML = [
|
||||
'<gaia-tabs selected="1">',
|
||||
'<div class="js-child"><span></span></div>',
|
||||
'<button class="js-child"></button>',
|
||||
'<a class="js-child"></a>',
|
||||
'</gaia-tabs>'
|
||||
].join('');
|
||||
|
||||
this.el = this.container.firstElementChild;
|
||||
this.children = this.el.querySelectorAll('.js-child');
|
||||
this.span = this.el.querySelector('span');
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
this.clock.restore();
|
||||
});
|
||||
|
||||
test('Should select the child at the given index on creation', function() {
|
||||
assert.isTrue(this.children[1].classList.contains('selected'));
|
||||
});
|
||||
|
||||
test('Should update the selected child when the ' +
|
||||
'`selected` attribute changes', function() {
|
||||
assert.isTrue(this.children[1].classList.contains('selected'));
|
||||
this.el.setAttribute('selected', '2');
|
||||
assert.isTrue(this.children[2].classList.contains('selected'));
|
||||
assert.isFalse(this.children[1].classList.contains('selected'));
|
||||
});
|
||||
|
||||
test('Should select a tab when it\'s clicked', function() {
|
||||
this.children[0].click();
|
||||
assert.isTrue(this.children[0].classList.contains('selected'));
|
||||
assert.isFalse(this.children[1].classList.contains('selected'));
|
||||
});
|
||||
|
||||
test('Should select a tab when a tab descendant is clicked', function() {
|
||||
this.span.click();
|
||||
assert.isTrue(this.children[0].classList.contains('selected'));
|
||||
assert.isFalse(this.children[1].classList.contains('selected'));
|
||||
});
|
||||
|
||||
suite('style', function() {
|
||||
setup(function(done) {
|
||||
this.container.style.width = '400px';
|
||||
document.body.appendChild(this.el);
|
||||
this.el.children[3].onload = function() {
|
||||
done();
|
||||
};
|
||||
});
|
||||
|
||||
test('Should position tabs horizontally', function() {
|
||||
this.children[0].pos = this.children[0].getBoundingClientRect();
|
||||
this.children[1].pos = this.children[1].getBoundingClientRect();
|
||||
this.children[2].pos = this.children[2].getBoundingClientRect();
|
||||
|
||||
assert.isTrue(this.children[0].pos.right < this.children[1].pos.right);
|
||||
assert.isTrue(this.children[1].pos.right < this.children[2].pos.right);
|
||||
});
|
||||
|
||||
test('Should size each tab to fill horizontal space equally', function() {
|
||||
this.el.pos = this.el.getBoundingClientRect();
|
||||
this.children[0].pos = this.children[0].getBoundingClientRect();
|
||||
this.children[1].pos = this.children[1].getBoundingClientRect();
|
||||
this.children[2].pos = this.children[2].getBoundingClientRect();
|
||||
|
||||
var expected = Math.round(this.el.pos.width / 3);
|
||||
|
||||
assert.equal(Math.round(this.children[0].pos.width), expected);
|
||||
assert.equal(Math.round(this.children[1].pos.width), expected);
|
||||
assert.equal(Math.round(this.children[2].pos.width), expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
'use strict';
|
||||
|
||||
require('/shared/js/component_utils.js');
|
||||
require('/shared/elements/gaia_textinput/script.js');
|
||||
|
||||
suite('GaiaTextinput', function() {
|
||||
setup(function() {
|
||||
this.container = document.createElement('div');
|
||||
});
|
||||
|
||||
test('simple input with placeholder', function() {
|
||||
this.container.innerHTML = '<gaia-textinput placeholder="my text">' +
|
||||
'</gaia-textinput>';
|
||||
var element = this.container.firstElementChild;
|
||||
var input = element.shadowRoot.querySelector('input');
|
||||
assert.equal(input.placeholder, 'my text');
|
||||
|
||||
// No reset link
|
||||
assert.equal(element.querySelectorAll('button[type="reset"]').length, 0);
|
||||
});
|
||||
|
||||
test('reset button is injected', function() {
|
||||
this.container.innerHTML = '<gaia-textinput reset>' +
|
||||
'</gaia-textinput>';
|
||||
var element = this.container.firstElementChild;
|
||||
var buttons = element.shadowRoot.querySelectorAll('button[type="reset"]');
|
||||
assert.equal(buttons.length, 1);
|
||||
});
|
||||
|
||||
test('value attribute', function() {
|
||||
this.container.innerHTML = '<gaia-textinput value="foo">' +
|
||||
'</gaia-textinput>';
|
||||
var element = this.container.firstElementChild;
|
||||
var input = element.shadowRoot.querySelector('input');
|
||||
assert.equal(input.value, 'foo');
|
||||
});
|
||||
});
|
||||
475
gaia/apps/sharedtest/test/unit/event_dispatcher_test.js
Executable file
475
gaia/apps/sharedtest/test/unit/event_dispatcher_test.js
Executable file
|
|
@ -0,0 +1,475 @@
|
|||
/*global EventDispatcher,
|
||||
Set
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
require('/shared/js/event_dispatcher.js');
|
||||
|
||||
suite('EventDispatcher >', function() {
|
||||
var originalObject = {
|
||||
property: 3,
|
||||
method: () => {}
|
||||
};
|
||||
|
||||
var originalRestrictedObject = {
|
||||
property: 3,
|
||||
method: () => {}
|
||||
};
|
||||
|
||||
var allowedEvents = ['allowed-event-1', 'allowed-event-2'];
|
||||
|
||||
var eventTarget = null,
|
||||
restrictedEventTarget = null;
|
||||
|
||||
suiteSetup(function() {
|
||||
eventTarget = EventDispatcher.mixin(originalObject);
|
||||
restrictedEventTarget = EventDispatcher.mixin(
|
||||
originalRestrictedObject,
|
||||
allowedEvents
|
||||
);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
eventTarget.offAll();
|
||||
});
|
||||
|
||||
suite('mixin >', function() {
|
||||
test('throws if object to mix into is not valid object', function() {
|
||||
assert.throws(() => EventDispatcher.mixin());
|
||||
assert.throws(() => EventDispatcher.mixin(null));
|
||||
assert.throws(() => EventDispatcher.mixin(() => {}));
|
||||
assert.throws(() => EventDispatcher.mixin(3));
|
||||
});
|
||||
|
||||
test('throws if object to mix into already has dispatcher methods',
|
||||
function() {
|
||||
var CustomClass = function() {};
|
||||
EventDispatcher.mixin(CustomClass.prototype);
|
||||
|
||||
assert.throws(() => EventDispatcher.mixin({ on: () => {} }));
|
||||
assert.throws(() => EventDispatcher.mixin({ on: null }));
|
||||
assert.throws(() => EventDispatcher.mixin({ off: () => {} }));
|
||||
assert.throws(() => EventDispatcher.mixin({ off: 'day off' }));
|
||||
assert.throws(() => EventDispatcher.mixin({ offAll: () => {} }));
|
||||
assert.throws(() => EventDispatcher.mixin({ emit: () => {} }));
|
||||
assert.throws(() => EventDispatcher.mixin(new CustomClass()));
|
||||
assert.throws(() => EventDispatcher.mixin(EventDispatcher.mixin({})));
|
||||
});
|
||||
|
||||
test('throws if invalid array is passed for allowedEvents', function() {
|
||||
assert.throws(() => EventDispatcher.mixin({}, null));
|
||||
assert.throws(() => EventDispatcher.mixin({}, 'event'));
|
||||
assert.throws(() => EventDispatcher.mixin({}, new Set()));
|
||||
});
|
||||
|
||||
test('correctly adds methods to target object', function() {
|
||||
assert.equal(typeof eventTarget.on, 'function');
|
||||
assert.equal(typeof eventTarget.off, 'function');
|
||||
assert.equal(typeof eventTarget.offAll, 'function');
|
||||
assert.equal(typeof eventTarget.emit, 'function');
|
||||
|
||||
assert.strictEqual(eventTarget, originalObject);
|
||||
assert.strictEqual(eventTarget.method, originalObject.method);
|
||||
assert.strictEqual(eventTarget.property, originalObject.property);
|
||||
});
|
||||
});
|
||||
|
||||
suite('on >', function() {
|
||||
test('throws if event name is not valid string', function() {
|
||||
assert.throws(() => eventTarget.on());
|
||||
assert.throws(() => eventTarget.on('', () => {}));
|
||||
assert.throws(() => eventTarget.on(null, () => {}));
|
||||
});
|
||||
|
||||
test('throws if handler is not function', function() {
|
||||
assert.throws(() => eventTarget.on('event'));
|
||||
assert.throws(() => eventTarget.on('event', null));
|
||||
assert.throws(() => eventTarget.on('event', {}));
|
||||
});
|
||||
|
||||
test('successfully registers handler', function() {
|
||||
var expectedHandler = sinon.stub(),
|
||||
unexpectedHandler = sinon.stub();
|
||||
|
||||
eventTarget.on('not-expected-event', unexpectedHandler);
|
||||
eventTarget.on('event', expectedHandler);
|
||||
|
||||
eventTarget.emit('event');
|
||||
sinon.assert.calledOnce(expectedHandler);
|
||||
|
||||
eventTarget.emit('event');
|
||||
sinon.assert.calledTwice(expectedHandler);
|
||||
sinon.assert.notCalled(unexpectedHandler);
|
||||
});
|
||||
|
||||
test('successfully registers multiple handlers', function() {
|
||||
var expectedHandler1 = sinon.stub(),
|
||||
expectedHandler2 = sinon.stub(),
|
||||
unexpectedHandler = sinon.stub();
|
||||
|
||||
eventTarget.on('not-expected-event', unexpectedHandler);
|
||||
eventTarget.on('event', expectedHandler1);
|
||||
eventTarget.on('event', expectedHandler2);
|
||||
|
||||
eventTarget.emit('event');
|
||||
|
||||
sinon.assert.notCalled(unexpectedHandler);
|
||||
sinon.assert.calledOnce(expectedHandler1);
|
||||
sinon.assert.calledOnce(expectedHandler2);
|
||||
sinon.assert.callOrder(expectedHandler1, expectedHandler2);
|
||||
});
|
||||
|
||||
suite('with allowed events >', function() {
|
||||
test('throws if event name is not allowed', function() {
|
||||
assert.throws(() => restrictedEventTarget.on('event'));
|
||||
});
|
||||
|
||||
test('successfully registers handler for allowed event', function() {
|
||||
var expectedHandler = sinon.stub(),
|
||||
unexpectedHandler = sinon.stub();
|
||||
|
||||
restrictedEventTarget.on('allowed-event-2', unexpectedHandler);
|
||||
restrictedEventTarget.on('allowed-event-1', expectedHandler);
|
||||
|
||||
restrictedEventTarget.emit('allowed-event-1');
|
||||
sinon.assert.calledOnce(expectedHandler);
|
||||
|
||||
restrictedEventTarget.emit('allowed-event-1');
|
||||
sinon.assert.calledTwice(expectedHandler);
|
||||
sinon.assert.notCalled(unexpectedHandler);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('once >', function() {
|
||||
test('throws if event name is not valid string', function() {
|
||||
assert.throws(() => eventTarget.once());
|
||||
assert.throws(() => eventTarget.once('', () => {}));
|
||||
assert.throws(() => eventTarget.once(null, () => {}));
|
||||
});
|
||||
|
||||
test('throws if handler is not function', function() {
|
||||
assert.throws(() => eventTarget.once('event'));
|
||||
assert.throws(() => eventTarget.once('event', null));
|
||||
assert.throws(() => eventTarget.once('event', {}));
|
||||
});
|
||||
|
||||
test('successfully registers handler', function() {
|
||||
var expectedHandler = sinon.stub(),
|
||||
unexpectedHandler = sinon.stub();
|
||||
|
||||
eventTarget.once('not-expected-event', unexpectedHandler);
|
||||
eventTarget.once('event', expectedHandler);
|
||||
|
||||
eventTarget.emit('event');
|
||||
sinon.assert.calledOnce(expectedHandler);
|
||||
|
||||
// Should not call handler more than once.
|
||||
eventTarget.emit('event');
|
||||
sinon.assert.calledOnce(expectedHandler);
|
||||
sinon.assert.notCalled(unexpectedHandler);
|
||||
});
|
||||
|
||||
test('successfully registers multiple handlers', function() {
|
||||
var expectedHandler1 = sinon.stub(),
|
||||
expectedHandler2 = sinon.stub(),
|
||||
unexpectedHandler = sinon.stub();
|
||||
|
||||
eventTarget.once('not-expected-event', unexpectedHandler);
|
||||
eventTarget.once('event', expectedHandler1);
|
||||
eventTarget.once('event', expectedHandler2);
|
||||
|
||||
eventTarget.emit('event');
|
||||
|
||||
sinon.assert.notCalled(unexpectedHandler);
|
||||
sinon.assert.calledOnce(expectedHandler1);
|
||||
sinon.assert.calledOnce(expectedHandler2);
|
||||
sinon.assert.callOrder(expectedHandler1, expectedHandler2);
|
||||
});
|
||||
|
||||
test('correctly passes parameters', function() {
|
||||
var handler = sinon.stub();
|
||||
|
||||
eventTarget.once('event', handler);
|
||||
eventTarget.emit('event');
|
||||
|
||||
sinon.assert.calledOnce(handler);
|
||||
sinon.assert.calledWithExactly(handler, undefined);
|
||||
|
||||
eventTarget.once('event', handler);
|
||||
eventTarget.emit('event', { a: 'b' });
|
||||
|
||||
sinon.assert.calledTwice(handler);
|
||||
sinon.assert.calledWithExactly(handler, { a: 'b' });
|
||||
});
|
||||
|
||||
suite('with allowed events >', function() {
|
||||
test('throws if event name is not allowed', function() {
|
||||
assert.throws(() => restrictedEventTarget.once('event'));
|
||||
});
|
||||
|
||||
test('successfully registers handler for allowed event', function() {
|
||||
var expectedHandler = sinon.stub(),
|
||||
unexpectedHandler = sinon.stub();
|
||||
|
||||
restrictedEventTarget.once('allowed-event-2', unexpectedHandler);
|
||||
restrictedEventTarget.once('allowed-event-1', expectedHandler);
|
||||
|
||||
restrictedEventTarget.emit('allowed-event-1');
|
||||
sinon.assert.calledOnce(expectedHandler);
|
||||
|
||||
// Should not call handler more than once.
|
||||
restrictedEventTarget.emit('allowed-event-1');
|
||||
sinon.assert.calledOnce(expectedHandler);
|
||||
sinon.assert.notCalled(unexpectedHandler);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('off >', function() {
|
||||
test('throws if event name is not valid string', function() {
|
||||
assert.throws(() => eventTarget.off());
|
||||
assert.throws(() => eventTarget.off('', () => {}));
|
||||
assert.throws(() => eventTarget.off(null, () => {}));
|
||||
});
|
||||
|
||||
test('throws if handler is not function', function() {
|
||||
assert.throws(() => eventTarget.off('event'));
|
||||
assert.throws(() => eventTarget.off('event', null));
|
||||
assert.throws(() => eventTarget.off('event', {}));
|
||||
});
|
||||
|
||||
test('successfully unregisters handler', function() {
|
||||
var handler = sinon.stub();
|
||||
|
||||
eventTarget.on('event', handler);
|
||||
eventTarget.emit('event');
|
||||
|
||||
sinon.assert.calledOnce(handler);
|
||||
|
||||
eventTarget.off('event', handler);
|
||||
eventTarget.emit('event');
|
||||
|
||||
sinon.assert.calledOnce(handler);
|
||||
});
|
||||
|
||||
test('unregisters correct handler', function() {
|
||||
var handler1 = sinon.stub(),
|
||||
handler2 = sinon.stub(),
|
||||
handler3 = sinon.stub();
|
||||
|
||||
eventTarget.on('event', handler1);
|
||||
eventTarget.on('event', handler2);
|
||||
eventTarget.emit('event');
|
||||
|
||||
sinon.assert.calledOnce(handler1);
|
||||
sinon.assert.calledOnce(handler2);
|
||||
|
||||
eventTarget.off('event', handler3);
|
||||
eventTarget.emit('event');
|
||||
|
||||
sinon.assert.calledTwice(handler1);
|
||||
sinon.assert.calledTwice(handler2);
|
||||
|
||||
eventTarget.off('event', handler1);
|
||||
eventTarget.emit('event');
|
||||
|
||||
sinon.assert.calledTwice(handler1);
|
||||
sinon.assert.calledThrice(handler2);
|
||||
|
||||
eventTarget.off('event', handler2);
|
||||
eventTarget.emit('event');
|
||||
|
||||
sinon.assert.calledTwice(handler1);
|
||||
sinon.assert.calledThrice(handler2);
|
||||
});
|
||||
|
||||
suite('with allowed events >', function() {
|
||||
test('throws if event name is not allowed', function() {
|
||||
assert.throws(() => restrictedEventTarget.off('event'));
|
||||
});
|
||||
|
||||
test('successfully unregisters handler for allowed event', function() {
|
||||
var handler = sinon.stub();
|
||||
|
||||
restrictedEventTarget.on('allowed-event-1', handler);
|
||||
restrictedEventTarget.emit('allowed-event-1');
|
||||
|
||||
sinon.assert.calledOnce(handler);
|
||||
|
||||
restrictedEventTarget.off('allowed-event-1', handler);
|
||||
restrictedEventTarget.emit('allowed-event-1');
|
||||
|
||||
sinon.assert.calledOnce(handler);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('offAll >', function() {
|
||||
test('throws if event name is not valid string', function() {
|
||||
assert.throws(() => eventTarget.offAll(''));
|
||||
assert.throws(() => eventTarget.offAll(null));
|
||||
});
|
||||
|
||||
test('unregisters all handlers for a specific event', function() {
|
||||
var handler1 = sinon.stub(),
|
||||
handler2 = sinon.stub();
|
||||
|
||||
eventTarget.on('event', handler1);
|
||||
eventTarget.on('event', handler2);
|
||||
eventTarget.emit('event');
|
||||
|
||||
sinon.assert.calledOnce(handler1);
|
||||
sinon.assert.calledOnce(handler2);
|
||||
|
||||
eventTarget.offAll('other-event');
|
||||
eventTarget.emit('event');
|
||||
|
||||
sinon.assert.calledTwice(handler1);
|
||||
sinon.assert.calledTwice(handler2);
|
||||
|
||||
eventTarget.offAll('event');
|
||||
eventTarget.emit('event');
|
||||
|
||||
sinon.assert.calledTwice(handler1);
|
||||
sinon.assert.calledTwice(handler2);
|
||||
});
|
||||
|
||||
test('unregisters all handlers for all events', function() {
|
||||
var handler1 = sinon.stub();
|
||||
var handler2 = sinon.stub();
|
||||
var handler3 = sinon.stub();
|
||||
|
||||
eventTarget.on('event-1', handler1);
|
||||
eventTarget.on('event-1', handler2);
|
||||
eventTarget.on('event-2', handler3);
|
||||
|
||||
eventTarget.offAll();
|
||||
eventTarget.emit('event-1');
|
||||
eventTarget.emit('event-2');
|
||||
|
||||
sinon.assert.notCalled(handler1);
|
||||
sinon.assert.notCalled(handler2);
|
||||
sinon.assert.notCalled(handler3);
|
||||
});
|
||||
|
||||
suite('with allowed events >', function() {
|
||||
test('throws if event name is not allowed', function() {
|
||||
assert.throws(() => restrictedEventTarget.offAll('event'));
|
||||
});
|
||||
|
||||
test('successfully unregisters all handlers for allowed event',
|
||||
function() {
|
||||
var handler1 = sinon.stub(),
|
||||
handler2 = sinon.stub();
|
||||
|
||||
restrictedEventTarget.on('allowed-event-1', handler1);
|
||||
restrictedEventTarget.on('allowed-event-1', handler2);
|
||||
restrictedEventTarget.emit('allowed-event-1');
|
||||
|
||||
sinon.assert.calledOnce(handler1);
|
||||
sinon.assert.calledOnce(handler2);
|
||||
|
||||
restrictedEventTarget.offAll('allowed-event-2');
|
||||
restrictedEventTarget.emit('allowed-event-1');
|
||||
|
||||
sinon.assert.calledTwice(handler1);
|
||||
sinon.assert.calledTwice(handler2);
|
||||
|
||||
restrictedEventTarget.offAll('allowed-event-1');
|
||||
restrictedEventTarget.emit('allowed-event-1');
|
||||
|
||||
sinon.assert.calledTwice(handler1);
|
||||
sinon.assert.calledTwice(handler2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('emit >', function() {
|
||||
test('throws if event name is not valid string', function() {
|
||||
assert.throws(() => eventTarget.emit());
|
||||
assert.throws(() => eventTarget.emit('', {}));
|
||||
assert.throws(() => eventTarget.emit(null, {}));
|
||||
});
|
||||
|
||||
test('execute all handlers in the right order', function() {
|
||||
var expectedHandler1 = sinon.stub(),
|
||||
expectedHandler2 = sinon.stub(),
|
||||
unexpectedHandler = sinon.stub();
|
||||
|
||||
eventTarget.on('event', expectedHandler1);
|
||||
eventTarget.on('event', expectedHandler2);
|
||||
eventTarget.on('other-event', unexpectedHandler);
|
||||
|
||||
eventTarget.emit('event');
|
||||
|
||||
sinon.assert.notCalled(unexpectedHandler);
|
||||
sinon.assert.calledOnce(expectedHandler1);
|
||||
sinon.assert.calledOnce(expectedHandler2);
|
||||
sinon.assert.callOrder(expectedHandler1, expectedHandler2);
|
||||
});
|
||||
|
||||
test('passes correct parameters to the handlers', function() {
|
||||
var handler1 = sinon.stub(),
|
||||
handler2 = sinon.stub(),
|
||||
handler3 = sinon.stub();
|
||||
|
||||
var parameters1 = {
|
||||
id: 1
|
||||
};
|
||||
|
||||
var parameters2 = 'hello!';
|
||||
|
||||
eventTarget.on('event', handler1);
|
||||
eventTarget.on('event-1', handler2);
|
||||
eventTarget.on('other-event', handler3);
|
||||
|
||||
eventTarget.emit('event', parameters1);
|
||||
eventTarget.emit('event-1', parameters2);
|
||||
eventTarget.emit('other-event');
|
||||
|
||||
sinon.assert.calledWith(handler1, parameters1);
|
||||
sinon.assert.calledWith(handler2, parameters2);
|
||||
sinon.assert.calledWith(handler3, undefined);
|
||||
});
|
||||
|
||||
test('execute all handlers even if exception occurs', function() {
|
||||
var handler1 = sinon.stub().throws('Type Error'),
|
||||
handler2 = sinon.stub();
|
||||
|
||||
eventTarget.on('event', handler1);
|
||||
eventTarget.on('event', handler2);
|
||||
|
||||
eventTarget.emit('event');
|
||||
|
||||
sinon.assert.calledOnce(handler1);
|
||||
sinon.assert.calledOnce(handler2);
|
||||
sinon.assert.callOrder(handler1, handler2);
|
||||
});
|
||||
|
||||
suite('with allowed events >', function() {
|
||||
test('throws if event name is not allowed', function() {
|
||||
assert.throws(() => restrictedEventTarget.emit('event'));
|
||||
});
|
||||
|
||||
test('execute all handlers for allowed event in the right order',
|
||||
function() {
|
||||
var expectedHandler1 = sinon.stub(),
|
||||
expectedHandler2 = sinon.stub(),
|
||||
unexpectedHandler = sinon.stub();
|
||||
|
||||
restrictedEventTarget.on('allowed-event-1', expectedHandler1);
|
||||
restrictedEventTarget.on('allowed-event-1', expectedHandler2);
|
||||
restrictedEventTarget.on('allowed-event-2', unexpectedHandler);
|
||||
|
||||
restrictedEventTarget.emit('allowed-event-1');
|
||||
|
||||
sinon.assert.notCalled(unexpectedHandler);
|
||||
sinon.assert.calledOnce(expectedHandler1);
|
||||
sinon.assert.calledOnce(expectedHandler2);
|
||||
sinon.assert.callOrder(expectedHandler1, expectedHandler2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
135
gaia/apps/sharedtest/test/unit/event_safety_test.js
Executable file
135
gaia/apps/sharedtest/test/unit/event_safety_test.js
Executable file
|
|
@ -0,0 +1,135 @@
|
|||
'use strict';
|
||||
/* global eventSafety */
|
||||
|
||||
require('/shared/js/event_safety.js');
|
||||
|
||||
suite('eventSafety: callback variant >', function() {
|
||||
|
||||
teardown(function() {
|
||||
// Try to trigger the event again by firing the event and ticking the
|
||||
// clock. An error will be thrown if done is called multiple times.
|
||||
this.sinon.clock.tick(1000);
|
||||
window.dispatchEvent(new CustomEvent('foobar'));
|
||||
});
|
||||
|
||||
test('fires after event', function(done) {
|
||||
this.sinon.useFakeTimers();
|
||||
eventSafety(window, 'foobar', function() {
|
||||
assert.ok(true, 'callback fired by event');
|
||||
done();
|
||||
}, 1000);
|
||||
window.dispatchEvent(new CustomEvent('foobar'));
|
||||
});
|
||||
|
||||
test('fires after setTimeout', function(done) {
|
||||
this.sinon.useFakeTimers();
|
||||
eventSafety(window, 'foobar', function() {
|
||||
assert.ok(true, 'callback fired by setTimeout');
|
||||
done();
|
||||
}, 1000);
|
||||
this.sinon.clock.tick(1000);
|
||||
});
|
||||
|
||||
test('passes on event argument', function(done) {
|
||||
this.sinon.useFakeTimers();
|
||||
eventSafety(window, 'foobar', function(evt) {
|
||||
assert.ok(evt, 'got event argument');
|
||||
assert.equal(evt.type, 'foobar', 'event name is set');
|
||||
done();
|
||||
}, 1000);
|
||||
window.dispatchEvent(new CustomEvent('foobar'));
|
||||
});
|
||||
|
||||
function testBubblingEvent(eventName) {
|
||||
test(`filters mismatched elements for ${eventName} events`, function() {
|
||||
this.sinon.useFakeTimers();
|
||||
var called = false;
|
||||
var timeoutSpy = this.sinon.spy(window, 'setTimeout');
|
||||
eventSafety(window, 'foobar', function() {
|
||||
called = true;
|
||||
}, 1000);
|
||||
|
||||
// Get the private callback and execute with arguments.
|
||||
timeoutSpy.getCall(0).args[0]({
|
||||
type: eventName,
|
||||
target: document.body // (Not what we called `eventSafety` with)
|
||||
});
|
||||
|
||||
assert.equal(called, false, 'should not call the callback');
|
||||
this.sinon.clock.tick(1000);
|
||||
assert.equal(called, true, 'called after timeout');
|
||||
});
|
||||
}
|
||||
|
||||
testBubblingEvent('transitionend');
|
||||
testBubblingEvent('animationend');
|
||||
|
||||
});
|
||||
|
||||
|
||||
suite('eventSafety: promise variant >', function() {
|
||||
|
||||
teardown(function() {
|
||||
// Try to trigger the event again by firing the event and ticking the
|
||||
// clock. An error will be thrown if done is called multiple times.
|
||||
this.sinon.clock.tick(1000);
|
||||
window.dispatchEvent(new CustomEvent('foobar'));
|
||||
});
|
||||
|
||||
test('fires after event', function(done) {
|
||||
this.sinon.useFakeTimers();
|
||||
eventSafety(window, 'foobar', 1000).then(function() {
|
||||
assert.ok(true, 'callback fired by event');
|
||||
done();
|
||||
});
|
||||
window.dispatchEvent(new CustomEvent('foobar'));
|
||||
});
|
||||
|
||||
test('fires after setTimeout', function(done) {
|
||||
this.sinon.useFakeTimers();
|
||||
eventSafety(window, 'foobar', 1000).then(function() {
|
||||
assert.ok(true, 'callback fired by setTimeout');
|
||||
done();
|
||||
});
|
||||
this.sinon.clock.tick(1000);
|
||||
});
|
||||
|
||||
test('passes on event argument', function(done) {
|
||||
this.sinon.useFakeTimers();
|
||||
eventSafety(window, 'foobar', 1000).then(function(evt) {
|
||||
assert.ok(evt, 'got event argument');
|
||||
assert.equal(evt.type, 'foobar', 'event name is set');
|
||||
done();
|
||||
});
|
||||
window.dispatchEvent(new CustomEvent('foobar'));
|
||||
});
|
||||
|
||||
function testBubblingEvent(eventName) {
|
||||
test(`filters mismatched elements for ${eventName} events`, function(done) {
|
||||
this.sinon.useFakeTimers();
|
||||
var called = false;
|
||||
var timeoutSpy = this.sinon.spy(window, 'setTimeout');
|
||||
eventSafety(window, 'foobar', 1000).then(function() {
|
||||
called = true;
|
||||
});
|
||||
|
||||
// Get the private callback and execute with arguments.
|
||||
timeoutSpy.getCall(0).args[0]({
|
||||
type: eventName,
|
||||
target: document.body // (Not what we called `eventSafety` with)
|
||||
});
|
||||
|
||||
assert.equal(called, false, 'should not call the callback');
|
||||
this.sinon.clock.tick(1000);
|
||||
// NOTE: Executing in the next Promise tick so that our Promise callback
|
||||
// has a chance to fire.
|
||||
Promise.resolve().then(() => {
|
||||
assert.equal(called, true, 'called after timeout');
|
||||
}).then(() => { done(); }, done);
|
||||
});
|
||||
}
|
||||
|
||||
testBubblingEvent('transitionend');
|
||||
testBubblingEvent('animationend');
|
||||
|
||||
});
|
||||
86
gaia/apps/sharedtest/test/unit/fb_tel_index_test.js
Executable file
86
gaia/apps/sharedtest/test/unit/fb_tel_index_test.js
Executable file
|
|
@ -0,0 +1,86 @@
|
|||
'use strict';
|
||||
|
||||
/* globals TelIndexer */
|
||||
|
||||
require('/shared/js/fb/fb_tel_index.js');
|
||||
require('/shared/js/binary_search.js');
|
||||
|
||||
suite('Telephone Prefix Indexes test', function() {
|
||||
var Tree;
|
||||
var ids = ['1234', '4567'];
|
||||
var numbers = ['655890765', '655890700'];
|
||||
|
||||
suiteSetup(function() {
|
||||
Tree = [];
|
||||
TelIndexer.index(Tree, numbers[0], ids[0]);
|
||||
TelIndexer.index(Tree, numbers[1], ids[1]);
|
||||
TelIndexer.orderTree(Tree);
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
Tree = null;
|
||||
});
|
||||
|
||||
function assertFound(result, target) {
|
||||
assert.equal(result.length, target.length);
|
||||
for (var j = 0; j < target.length; j++) {
|
||||
assert.isTrue(result.indexOf(target[j]) !== -1);
|
||||
}
|
||||
}
|
||||
|
||||
function assertNotFound(result) {
|
||||
assert.equal(result.length, 0);
|
||||
}
|
||||
|
||||
test('Find the whole number', function() {
|
||||
assertFound(TelIndexer.search(Tree, numbers[0]), [ids[0]]);
|
||||
assertFound(TelIndexer.search(Tree, numbers[1]), [ids[1]]);
|
||||
});
|
||||
|
||||
test('Partial find. Length insufficient', function() {
|
||||
var result = TelIndexer.search(Tree, '65');
|
||||
assert.equal(result.length, 0);
|
||||
});
|
||||
|
||||
test('Partial find. Minimum length', function() {
|
||||
assertFound(TelIndexer.search(Tree, '655'), ids);
|
||||
});
|
||||
|
||||
test('Partial find. Start', function() {
|
||||
assertFound(TelIndexer.search(Tree, '6558'), ids);
|
||||
});
|
||||
|
||||
test('Partial find. Middle', function() {
|
||||
assertFound(TelIndexer.search(Tree, '58907'), ids);
|
||||
});
|
||||
|
||||
test('Partial find. End', function() {
|
||||
assertFound(TelIndexer.search(Tree, '765'), [ids[0]]);
|
||||
});
|
||||
|
||||
test('Match. Middle', function() {
|
||||
assertFound(TelIndexer.search(Tree, '89070'), [ids[1]]);
|
||||
});
|
||||
|
||||
test('No match. End', function() {
|
||||
assertNotFound(TelIndexer.search(Tree, '767'));
|
||||
});
|
||||
|
||||
test('No match. Start', function() {
|
||||
assertNotFound(TelIndexer.search(Tree, '65519'));
|
||||
});
|
||||
|
||||
test('No match. Last number differs', function() {
|
||||
assertNotFound(TelIndexer.search(Tree, '655890764'));
|
||||
});
|
||||
|
||||
test('No match. Middle', function() {
|
||||
assertNotFound(TelIndexer.search(Tree, '89074'));
|
||||
});
|
||||
|
||||
test('Remove a tel number. No longer found', function() {
|
||||
TelIndexer.remove(Tree, numbers[0], ids[0]);
|
||||
assertNotFound(TelIndexer.search(Tree, numbers[0]));
|
||||
assertNotFound(TelIndexer.search(Tree, '89076'));
|
||||
});
|
||||
});
|
||||
284
gaia/apps/sharedtest/test/unit/font_size_utils_test.js
Executable file
284
gaia/apps/sharedtest/test/unit/font_size_utils_test.js
Executable file
|
|
@ -0,0 +1,284 @@
|
|||
/* global FontSizeUtils */
|
||||
|
||||
/**
|
||||
* Tests for the shared text utils helper
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
require('/shared/js/font_size_utils.js');
|
||||
|
||||
suite('shared/js/text_utils.js', function() {
|
||||
const kDefaultFace = 'Arial';
|
||||
const kContainerWidth = 100;
|
||||
const kDefaultSize = 12;
|
||||
const kAllowedSizes = [8, 10, 14];
|
||||
const kStringChar = '#';
|
||||
|
||||
function getMaxFontSizeInfo() {
|
||||
return FontSizeUtils.getMaxFontSizeInfo(text, kAllowedSizes,
|
||||
kDefaultFace, kContainerWidth);
|
||||
}
|
||||
|
||||
function getOverflowCount() {
|
||||
return FontSizeUtils.getOverflowCount(text, kDefaultSize,
|
||||
kDefaultFace, kContainerWidth);
|
||||
}
|
||||
|
||||
var context;
|
||||
|
||||
function getContext() {
|
||||
if (!context) {
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.setAttribute('moz-opaque', 'true');
|
||||
canvas.setAttribute('width', '1');
|
||||
canvas.setAttribute('height', '1');
|
||||
context = canvas.getContext('2d', { willReadFrequently: true });
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
function getDefaultContext() {
|
||||
var ctx = getContext();
|
||||
ctx.font = 'italic ' + kDefaultSize + 'px ' + kDefaultFace;
|
||||
return ctx;
|
||||
}
|
||||
|
||||
function generateStringForPixels(width, fontSize, fontFace) {
|
||||
fontSize = fontSize || kDefaultSize;
|
||||
fontFace = fontFace || kDefaultFace;
|
||||
|
||||
var ctx = getContext();
|
||||
ctx.font = 'italic ' + fontSize + 'px ' + fontFace;
|
||||
|
||||
var str = kStringChar;
|
||||
while (ctx.measureText(str + kStringChar).width < width) {
|
||||
str += kStringChar;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
var text;
|
||||
|
||||
function setupSmallString(size, face) {
|
||||
text = generateStringForPixels(1, size, face);
|
||||
return text;
|
||||
}
|
||||
|
||||
// string just barely smaller than the container width
|
||||
function setupMediumString(size, face) {
|
||||
text = generateStringForPixels(kContainerWidth, size, face);
|
||||
return text;
|
||||
}
|
||||
|
||||
// string just barely larger than the container width
|
||||
function setupMediumPlusString(size, face) {
|
||||
text = generateStringForPixels(kContainerWidth + 1, size, face) +
|
||||
kStringChar;
|
||||
return text;
|
||||
}
|
||||
|
||||
// way to large to ever fit anywhere
|
||||
function setupLargeString(size, face) {
|
||||
text = generateStringForPixels(kContainerWidth * 10, size, face);
|
||||
return text;
|
||||
}
|
||||
|
||||
setup(function() {
|
||||
text = '';
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
FontSizeUtils.resetCache();
|
||||
});
|
||||
|
||||
suite('Global', function() {
|
||||
test('FontSizeUtils exists', function() {
|
||||
assert.ok(FontSizeUtils);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Cache Mechanism', function() {
|
||||
test('Created', function() {
|
||||
FontSizeUtils._getCachedContext(kDefaultSize, kDefaultFace);
|
||||
|
||||
assert.equal(Object.keys(FontSizeUtils._cachedContexts).length, 1);
|
||||
});
|
||||
|
||||
test('Used', function() {
|
||||
var oldContext = FontSizeUtils._getCachedContext(kDefaultSize,
|
||||
kDefaultFace);
|
||||
var newContext = FontSizeUtils._getCachedContext(kDefaultSize,
|
||||
kDefaultFace);
|
||||
|
||||
assert.equal(oldContext, newContext);
|
||||
});
|
||||
|
||||
test('Cleared', function() {
|
||||
FontSizeUtils._getCachedContext(kDefaultSize, kDefaultFace);
|
||||
FontSizeUtils.resetCache();
|
||||
|
||||
assert.equal(Object.keys(FontSizeUtils._cachedContexts).length, 0);
|
||||
});
|
||||
|
||||
test('Created for specified font [size/face]', function() {
|
||||
getMaxFontSizeInfo();
|
||||
|
||||
for (var i = 0; i < kAllowedSizes.length; i++) {
|
||||
assert.ok(FontSizeUtils._getCachedContext(kAllowedSizes[i],
|
||||
kDefaultFace));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
suite('FontSizeUtils.getMaxFontSizeInfo(Small text)', function() {
|
||||
setup(function() {
|
||||
setupSmallString();
|
||||
});
|
||||
|
||||
test('Returns max font size', function() {
|
||||
var infos = getMaxFontSizeInfo();
|
||||
assert.equal(infos.fontSize, kAllowedSizes[kAllowedSizes.length - 1]);
|
||||
});
|
||||
|
||||
test('No overflow', function() {
|
||||
var infos = getMaxFontSizeInfo();
|
||||
assert.isFalse(infos.overflow);
|
||||
});
|
||||
});
|
||||
|
||||
suite('FontSizeUtils.getMaxFontSizeInfo(Medium text)', function() {
|
||||
setup(function() {
|
||||
setupMediumString();
|
||||
});
|
||||
|
||||
test('Returns middle font size', function() {
|
||||
var infos = getMaxFontSizeInfo();
|
||||
assert.equal(infos.fontSize, kAllowedSizes[1]);
|
||||
});
|
||||
|
||||
test('overflow is false', function() {
|
||||
var infos = getMaxFontSizeInfo();
|
||||
assert.isFalse(infos.overflow);
|
||||
});
|
||||
});
|
||||
|
||||
suite('FontSizeUtils.getMaxFontSizeInfo(Large Text)', function() {
|
||||
setup(function() {
|
||||
setupLargeString();
|
||||
});
|
||||
|
||||
test('Returns min font size', function() {
|
||||
var infos = getMaxFontSizeInfo();
|
||||
assert.equal(infos.fontSize, kAllowedSizes[0]);
|
||||
});
|
||||
|
||||
test('Overflow is true', function() {
|
||||
var infos = getMaxFontSizeInfo();
|
||||
assert.isTrue(infos.overflow);
|
||||
});
|
||||
});
|
||||
|
||||
suite('FontSizeUtils.getOverflowCount', function() {
|
||||
test('Should be 0 for small text', function() {
|
||||
setupSmallString();
|
||||
|
||||
assert.equal(getOverflowCount(), 0);
|
||||
});
|
||||
|
||||
test('Should be 0 for medium text', function() {
|
||||
setupMediumString();
|
||||
|
||||
assert.equal(getOverflowCount(), 0);
|
||||
});
|
||||
|
||||
test('Should be 1 for medium text plus 1 character', function() {
|
||||
setupMediumPlusString();
|
||||
|
||||
assert.equal(getOverflowCount(), 1);
|
||||
});
|
||||
|
||||
test('Should be overflow width divided by character width', function() {
|
||||
var ctx = getDefaultContext();
|
||||
var charWidth = ctx.measureText(kStringChar).width;
|
||||
|
||||
var string = setupLargeString();
|
||||
var stringWidth = ctx.measureText(string).width;
|
||||
|
||||
// Estimate the amount of character overflow we should have
|
||||
// by measuring overflow width, and then dividing that by
|
||||
// the width of the characters in the string.
|
||||
var overflowWidth = stringWidth - kContainerWidth;
|
||||
var overflowCount = overflowWidth / charWidth;
|
||||
|
||||
assert.ok(Math.abs(getOverflowCount() - overflowCount) < 1);
|
||||
});
|
||||
|
||||
test('Successive calls should not recompute the overflow from scratch',
|
||||
function() {
|
||||
var measureTextSpy = this.sinon.spy(
|
||||
CanvasRenderingContext2D.prototype, 'measureText'
|
||||
);
|
||||
|
||||
setupMediumString();
|
||||
measureTextSpy.reset();
|
||||
getOverflowCount();
|
||||
var count = measureTextSpy.callCount;
|
||||
setupMediumPlusString();
|
||||
measureTextSpy.reset();
|
||||
getOverflowCount();
|
||||
assert.equal(measureTextSpy.callCount, count + 1);
|
||||
});
|
||||
});
|
||||
|
||||
suite('FontSizeUtils.getFontWidth', function() {
|
||||
test('Should measureText correctly for multiple fontSizes', function() {
|
||||
var string = 'arbitrary' + Date.now();
|
||||
var ctx = getContext();
|
||||
// test 2px up to 24px font size measurements
|
||||
for (var fontSize = 2; fontSize < 24; fontSize++) {
|
||||
ctx.font = 'italic ' + fontSize + 'px ' + kDefaultFace;
|
||||
assert.equal(ctx.measureText(string).width,
|
||||
FontSizeUtils.getFontWidth(string, fontSize,
|
||||
kDefaultFace));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
suite('FontSizeUtils.getContentWidth', function() {
|
||||
var el;
|
||||
|
||||
setup(function() {
|
||||
el = document.createElement('div');
|
||||
el.style.width = '50px';
|
||||
el.style.padding = '10px';
|
||||
document.body.appendChild(el);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
document.body.removeChild(el);
|
||||
});
|
||||
|
||||
test('Should compute the width of content-box element', function() {
|
||||
el.style.boxSizing = 'content-box';
|
||||
var style = getComputedStyle(el);
|
||||
var styleWidth = parseInt(style.width, 10);
|
||||
var actualWidth = FontSizeUtils.getContentWidth(style);
|
||||
|
||||
assert.equal(styleWidth, 50);
|
||||
assert.equal(actualWidth, 50);
|
||||
});
|
||||
|
||||
test('Should compute the width of border-box element', function() {
|
||||
el.style.boxSizing = 'border-box';
|
||||
var style = getComputedStyle(el);
|
||||
var styleWidth = parseInt(style.width, 10);
|
||||
var actualWidth = FontSizeUtils.getContentWidth(style);
|
||||
|
||||
assert.equal(styleWidth, 50);
|
||||
assert.equal(actualWidth, 30);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
22
gaia/apps/sharedtest/test/unit/format_test.js
Executable file
22
gaia/apps/sharedtest/test/unit/format_test.js
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
/* global Format */
|
||||
|
||||
require('/shared/js/format.js');
|
||||
|
||||
suite('format', function() {
|
||||
'use strict';
|
||||
|
||||
// Result, input, len, padWith
|
||||
var testCases = [
|
||||
['1', 1, 1, ''],
|
||||
['001', 1, 3, '0'],
|
||||
[' 1', 1, 3, undefined],
|
||||
['0001', '0001', 2, '0']
|
||||
];
|
||||
|
||||
test('padLeft', function() {
|
||||
testCases.forEach(function(testCase) {
|
||||
var result = testCase.shift();
|
||||
assert.equal(result, Format.padLeft.apply(Format, testCase));
|
||||
});
|
||||
});
|
||||
});
|
||||
304
gaia/apps/sharedtest/test/unit/gesture_detector_test.js
Executable file
304
gaia/apps/sharedtest/test/unit/gesture_detector_test.js
Executable file
|
|
@ -0,0 +1,304 @@
|
|||
'use strict';
|
||||
/* global
|
||||
GestureDetector,
|
||||
requireCommon,
|
||||
SyntheticGestures
|
||||
*/
|
||||
|
||||
requireCommon('test/synthetic_gestures.js');
|
||||
require('/shared/js/gesture_detector.js');
|
||||
|
||||
suite('GestureDetector', function() {
|
||||
|
||||
suite('API', function() {
|
||||
test('public methods and constants', function() {
|
||||
assert.typeOf(GestureDetector, 'function');
|
||||
var gd = new GestureDetector(document.body);
|
||||
assert.typeOf(gd.startDetecting, 'function');
|
||||
assert.typeOf(gd.stopDetecting, 'function');
|
||||
assert.isNumber(GestureDetector.HOLD_INTERVAL);
|
||||
assert.isNumber(GestureDetector.PAN_THRESHOLD);
|
||||
assert.isNumber(GestureDetector.DOUBLE_TAP_DISTANCE);
|
||||
assert.isNumber(GestureDetector.DOUBLE_TAP_TIME);
|
||||
assert.isNumber(GestureDetector.VELOCITY_SMOOTHING);
|
||||
});
|
||||
});
|
||||
|
||||
// Disabled per bug: https://bugzilla.mozilla.org/show_bug.cgi?id=956591
|
||||
suite.skip('gesture detection', function() {
|
||||
var gd, element, events;
|
||||
|
||||
// Return the sequence of events as a string of event types
|
||||
function eventseq() {
|
||||
return events.map(function(e) { return e.type; }).join(' ');
|
||||
}
|
||||
|
||||
setup(function() {
|
||||
events = []; // Start a new array of events for each test
|
||||
element = document.createElement('div');
|
||||
element.setAttribute('style',
|
||||
'position:fixed;left:0px;top:0px;width:100%;height:100%');
|
||||
document.body.appendChild(element);
|
||||
|
||||
[
|
||||
'tap', 'dbltap',
|
||||
'pan', 'swipe',
|
||||
'holdstart', 'holdmove', 'holdend',
|
||||
'transform', 'transformend'
|
||||
].forEach(function(type) {
|
||||
element.addEventListener(type, function(e) { events.push(e); });
|
||||
});
|
||||
|
||||
gd = new GestureDetector(element, {holdEvents: true});
|
||||
gd.startDetecting();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
gd.stopDetecting();
|
||||
document.body.removeChild(element);
|
||||
});
|
||||
|
||||
test('tap', function(done) {
|
||||
SyntheticGestures.tap(element, function() {
|
||||
done(function() {
|
||||
assert.length(events, 1);
|
||||
assert.equal(events[0].type, 'tap');
|
||||
assert.equal(events[0].detail.clientX, 100);
|
||||
assert.equal(events[0].detail.clientY, 200);
|
||||
});
|
||||
}, 100, 200);
|
||||
});
|
||||
|
||||
test('dbltap', function(done) {
|
||||
SyntheticGestures.dbltap(element, function() {
|
||||
done(function() {
|
||||
assert.equal(eventseq(), 'tap tap dbltap');
|
||||
assert.equal(events[2].detail.clientX, 100);
|
||||
assert.equal(events[2].detail.clientY, 200);
|
||||
});
|
||||
}, 100, 200);
|
||||
});
|
||||
|
||||
var swipes = [
|
||||
{ name: 'E', x0: 10, y0: 10, x1: 200, y1: 10, direction: 'right' },
|
||||
{ name: 'W', x0: 200, y0: 10, x1: 10, y1: 10, direction: 'left' },
|
||||
{ name: 'S', x0: 10, y0: 10, x1: 10, y1: 200, direction: 'down' },
|
||||
{ name: 'N', x0: 10, y0: 200, x1: 10, y1: 10, direction: 'up' },
|
||||
{ name: 'NNE', x0: 100, y0: 200, x1: 110, y1: 10, direction: 'up' },
|
||||
{ name: 'NNW', x0: 100, y0: 200, x1: 90, y1: 10, direction: 'up' },
|
||||
{ name: 'SSE', x0: 100, y0: 10, x1: 110, y1: 200, direction: 'down' },
|
||||
{ name: 'SSW', x0: 100, y0: 10, x1: 90, y1: 200, direction: 'down' },
|
||||
{ name: 'ENE', x0: 10, y0: 100, x1: 200, y1: 90, direction: 'right' },
|
||||
{ name: 'ESE', x0: 10, y0: 100, x1: 200, y1: 110, direction: 'right' },
|
||||
{ name: 'WNW', x0: 200, y0: 100, x1: 10, y1: 90, direction: 'left' },
|
||||
{ name: 'WSW', x0: 200, y0: 100, x1: 10, y1: 110, direction: 'left' }
|
||||
];
|
||||
|
||||
/*
|
||||
// These tests are currently failing and have been temporarily disabled as per
|
||||
// Bug 838993. They should be fixed and re-enabled as soon as possible as per
|
||||
// Bug 840493.
|
||||
function between(v, min, max) {
|
||||
assert.isTrue(v >= min && v <= max,
|
||||
v + ' is not between ' + min + ' and ' + max);
|
||||
}
|
||||
|
||||
swipes.forEach(function(s) {
|
||||
test('swipe ' + s.name, function(done) {
|
||||
var startTime = Date.now();
|
||||
SyntheticGestures.swipe(element, s.x0, s.y0, s.x1, s.y1,
|
||||
200, checkswipe);
|
||||
function checkswipe() {
|
||||
done(function() {
|
||||
assert.match(eventseq(), /(pan )+swipe/);
|
||||
var e = events[events.length - 1];
|
||||
assert.equal(e.detail.start.clientX, s.x0);
|
||||
assert.equal(e.detail.start.clientY, s.y0);
|
||||
assert.equal(e.detail.end.clientX, s.x1);
|
||||
assert.equal(e.detail.end.clientY, s.y1);
|
||||
assert.equal(e.detail.dx, s.x1 - s.x0);
|
||||
assert.equal(e.detail.dy, s.y1 - s.y0);
|
||||
|
||||
assert.equal(e.detail.direction, s.direction);
|
||||
|
||||
var angle = Math.atan2(s.y1 - s.y0, s.x1 - s.x0) * 180 / Math.PI;
|
||||
if (angle < 0)
|
||||
angle += 360;
|
||||
between(e.detail.angle, angle - 1, angle + 1);
|
||||
|
||||
var lastpan = events[events.length - 2];
|
||||
assert.equal(lastpan.detail.absolute.dx, s.x1 - s.x0);
|
||||
assert.equal(lastpan.detail.absolute.dy, s.y1 - s.y0);
|
||||
// Add up the relative deltas for all pans
|
||||
var dx = 0, dy = 0;
|
||||
events.forEach(function(e) {
|
||||
if (e.type === 'pan') {
|
||||
dx += e.detail.relative.dx;
|
||||
dy += e.detail.relative.dy;
|
||||
assert.equal(dx, e.detail.absolute.dx);
|
||||
assert.equal(dy, e.detail.absolute.dy);
|
||||
}
|
||||
});
|
||||
assert.equal(dx, s.x1 - s.x0);
|
||||
assert.equal(dy, s.y1 - s.y0);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
swipes.forEach(function(s) {
|
||||
test.skip('mouseswipe ' + s.name, function(done) {
|
||||
SyntheticGestures.mouseswipe(element, s.x0, s.y0, s.x1, s.y1,
|
||||
200, checkswipe);
|
||||
function checkswipe() {
|
||||
done(function() {
|
||||
assert.match(eventseq(), /(pan )+swipe/);
|
||||
var e = events[events.length - 1];
|
||||
assert.equal(e.detail.start.clientX, s.x0);
|
||||
assert.equal(e.detail.start.clientY, s.y0);
|
||||
assert.equal(e.detail.end.clientX, s.x1);
|
||||
assert.equal(e.detail.end.clientY, s.y1);
|
||||
assert.equal(e.detail.dx, s.x1 - s.x0);
|
||||
assert.equal(e.detail.dy, s.y1 - s.y0);
|
||||
|
||||
assert.equal(e.detail.direction, s.direction);
|
||||
|
||||
var angle = Math.atan2(s.y1 - s.y0, s.x1 - s.x0) * 180 / Math.PI;
|
||||
if (angle < 0)
|
||||
angle += 360;
|
||||
between(e.detail.angle, angle - 1, angle + 1);
|
||||
|
||||
var lastpan = events[events.length - 2];
|
||||
assert.equal(lastpan.detail.absolute.dx, s.x1 - s.x0);
|
||||
assert.equal(lastpan.detail.absolute.dy, s.y1 - s.y0);
|
||||
// Add up the relative deltas for all pans
|
||||
var dx = 0, dy = 0;
|
||||
events.forEach(function(e) {
|
||||
if (e.type === 'pan') {
|
||||
dx += e.detail.relative.dx;
|
||||
dy += e.detail.relative.dy;
|
||||
assert.equal(dx, e.detail.absolute.dx);
|
||||
assert.equal(dy, e.detail.absolute.dy);
|
||||
}
|
||||
});
|
||||
assert.equal(dx, s.x1 - s.x0);
|
||||
assert.equal(dy, s.y1 - s.y0);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* Insanely flakey too:
|
||||
var touchDevice = (window.Touch !== undefined);
|
||||
if (touchDevice) {
|
||||
var pinches = [
|
||||
{ x0: 0, y0: 0, x1: 100, y1: 100, scale: 2, duration: 800 },
|
||||
{ x0: 0, y0: 0, x1: 100, y1: 100, scale: .5, duration: 800 },
|
||||
{ x0: 100, y0: 100, x1: 10, y1: 10, scale: 1.5, duration: 800 },
|
||||
{ x0: 200, y0: 200, x1: 10, y1: 10, scale: .75, duration: 800 },
|
||||
{ x0: 200, y0: 200, x1: 200, y1: 0, scale: 2, duration: 750 },
|
||||
{ x0: 200, y0: 200, x1: 200, y1: 0, scale: .5, duration: 750 },
|
||||
{ x0: 200, y0: 200, x1: 0, y1: 200, scale: 3, duration: 750 },
|
||||
{ x0: 200, y0: 200, x1: 0, y1: 200, scale: .3, duration: 750 }
|
||||
];
|
||||
|
||||
pinches.forEach(function(p, index) {
|
||||
var testname = 'Pinch ' + index +
|
||||
': (' + p.x0 + ',' + p.y0 + ')' +
|
||||
' & (' + p.x1 + ',' + p.y1 + ')' +
|
||||
' scale: ' + p.scale;
|
||||
|
||||
test(testname, function(done) {
|
||||
SyntheticGestures.pinch(element, p.x0, p.y0, p.x1, p.y1,
|
||||
p.scale, p.duration, checkpinch);
|
||||
function checkpinch() {
|
||||
done(function() {
|
||||
assert.match(eventseq(), /(transform )+transformend/);
|
||||
var e = events[events.length - 1];
|
||||
var d = e.detail;
|
||||
|
||||
// We asked for p.scale so synthetic gestures will change
|
||||
// the distance d between the two touches to d * p.scale.
|
||||
// But we don't actually start detecting the gesture
|
||||
// until the touches have moved a bit. So we don't expect
|
||||
// to get p.scale back exactly. (XXX: maybe I should fix this
|
||||
// in synthetic gestures instead of altering the tests).
|
||||
var d0 = Math.sqrt((p.x1 - p.x0) * (p.x1 - p.x0) +
|
||||
(p.y1 - p.y0) * (p.y1 - p.y0));
|
||||
var d1 = d0 * p.scale;
|
||||
var adjustment = GestureDetector.SCALE_THRESHOLD *
|
||||
GestureDetector.THRESHOLD_SMOOTHING;
|
||||
var expected;
|
||||
if (d1 > d0)
|
||||
expected = d1 / (d0 + adjustment);
|
||||
else
|
||||
expected = d1 / (d0 - adjustment);
|
||||
|
||||
between(d.absolute.scale, 0.95 * expected, 1.05 * expected);
|
||||
assert.equal(d.absolute.rotate, 0);
|
||||
assert.equal(d.relative.rotate, 0);
|
||||
|
||||
// compute the product of all the relative scales
|
||||
var s = 1.0;
|
||||
events.forEach(function(e) { s *= e.detail.relative.scale; });
|
||||
between(s, 0.95 * expected, 1.05 * expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
*/
|
||||
// Reuse some of the swipes data for testing hold+move events.
|
||||
// The hold tests take about 1.5s each since they require > 1s
|
||||
// just to trigger the hold detection. So only do four of each
|
||||
swipes.length = 4;
|
||||
|
||||
/*
|
||||
// These tests are currently failing and have been temporarily disabled as per
|
||||
// Bug 838993. They should be fixed and re-enabled as soon as possible as per
|
||||
// Bug 840493.
|
||||
swipes.forEach(function(s) {
|
||||
test('hold ' + s.name, function(done) {
|
||||
SyntheticGestures.hold(element, 1250, s.x0, s.y0, s.x1, s.y1,
|
||||
200, checkhold);
|
||||
function checkhold() {
|
||||
done(function() {
|
||||
assert.match(eventseq(), /holdstart (holdmove )*holdend/);
|
||||
|
||||
// Check start details
|
||||
var d = events[0].detail;
|
||||
|
||||
assert.equal(d.clientX, s.x0);
|
||||
assert.equal(d.clientY, s.y0);
|
||||
|
||||
// Check end details
|
||||
d = events[events.length - 1].detail;
|
||||
assert.equal(d.start.clientX, s.x0);
|
||||
assert.equal(d.start.clientY, s.y0);
|
||||
assert.equal(d.end.clientX, s.x1);
|
||||
assert.equal(d.end.clientY, s.y1);
|
||||
assert.equal(d.dx, s.x1 - s.x0);
|
||||
assert.equal(d.dy, s.y1 - s.y0);
|
||||
|
||||
// Check relative vs absolute for all the holdmove events
|
||||
var dx = 0, dy = 0;
|
||||
events.forEach(function(e) {
|
||||
if (e.type === 'holdmove') {
|
||||
dx += e.detail.relative.dx;
|
||||
dy += e.detail.relative.dy;
|
||||
assert.equal(dx, e.detail.absolute.dx);
|
||||
assert.equal(dy, e.detail.absolute.dy);
|
||||
}
|
||||
});
|
||||
assert.equal(dx, s.x1 - s.x0);
|
||||
assert.equal(dy, s.y1 - s.y0);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
*/
|
||||
});
|
||||
});
|
||||
66
gaia/apps/sharedtest/test/unit/gzip_worker_test.js
Executable file
66
gaia/apps/sharedtest/test/unit/gzip_worker_test.js
Executable file
|
|
@ -0,0 +1,66 @@
|
|||
/* global Gzip */
|
||||
'use strict';
|
||||
|
||||
require('/shared/js/gzip/gzip.js');
|
||||
require('/shared/js/gzip/gzip.min.js');
|
||||
|
||||
suite('gzip worker', function() {
|
||||
|
||||
test('worker successfully compresses payload', function(done) {
|
||||
var payload = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
||||
|
||||
// The first ten bytes are the header of the compressed data. Part of the
|
||||
// header will vary each time the test is run (timestamp); parts of the
|
||||
// header will vary based on the OS on which the test is running. As such,
|
||||
// the test will compare the fixed values of the header that are
|
||||
// significant to gzip compressed data.
|
||||
//
|
||||
// Bytes 0 and 1 should be: 31, 139 -- 'gzip' format
|
||||
// Byte 2 should be 8 -- 'inflate' compression algorithm
|
||||
var expectedCompressedData =
|
||||
new Uint8Array([
|
||||
31,139,8,0,58,51,24,86,0,255,5,0,5,17,128,48,176,18,38,35,14,48,119,
|
||||
183,244,127,239,247,99,66,25,23,82,105,99,157,15,49,229,82,91,31,115,
|
||||
221,246,227,188,110,244,44,0,88,155,95,95,36,0,0,0]);
|
||||
|
||||
Gzip.WORKER_URL = '/shared/js/gzip/gzip_worker.js';
|
||||
Gzip.compress(payload).then((gzipData) => {
|
||||
assert.equal(gzipData.length, 59);
|
||||
assert.equal(gzipData.length, expectedCompressedData.length);
|
||||
|
||||
// Only verify bytes 0 through 2 of the header, as explained above.
|
||||
for (var i = 0; i < gzipData.length; i++) {
|
||||
if (i < 3 || i > 9) {
|
||||
assert.equal(gzipData[i], expectedCompressedData[i]);
|
||||
}
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* Test the scenario where the gzip compression function throws an error.
|
||||
* The gzip worker should reject, providing the error message.
|
||||
* Because we can't stub the gzip compression library function (it isn't
|
||||
* instantiated until the `Gzip.compress` function is called), a mock worker
|
||||
* is used which in turn uses a mock compression library -- which is
|
||||
* hard-coded to throw. Because the test is not using the prodution worker,
|
||||
* it is imperative that the mock worker be kept in sync with the prodution
|
||||
* worker: the only difference should be:
|
||||
* < importScripts('gzip.min.js');
|
||||
* ---
|
||||
* > importScripts('mock_gzip.min.js');
|
||||
*/
|
||||
test('gzip compression throws error', function(done) {
|
||||
Gzip.WORKER_URL = '/shared/test/unit/mocks/gzip/mock_gzip_worker.js';
|
||||
Gzip.compress().then((gzipData) => {
|
||||
assert.isFalse(true);
|
||||
done();
|
||||
},
|
||||
(e) => {
|
||||
assert.equal(e, 'Failed to compress: Gzip.compress error');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
66
gaia/apps/sharedtest/test/unit/homescreens/icon_test.js
Executable file
66
gaia/apps/sharedtest/test/unit/homescreens/icon_test.js
Executable file
|
|
@ -0,0 +1,66 @@
|
|||
'use strict';
|
||||
|
||||
/* global Icon, GridIconRenderer, MocksHelper */
|
||||
/* global require, suite, test, assert, sinon */
|
||||
|
||||
require('/shared/elements/gaia_grid/js/grid_icon_renderer.js');
|
||||
require('/shared/test/unit/mocks/mock_image.js');
|
||||
|
||||
require('/shared/js/homescreens/icon.js');
|
||||
|
||||
var mocksHelperForIcon = new MocksHelper([
|
||||
'Image'
|
||||
]).init();
|
||||
|
||||
suite('icon.js >', function() {
|
||||
|
||||
mocksHelperForIcon.attachTestHelpers();
|
||||
|
||||
test('it sets the background size when there is not an icon', function() {
|
||||
var element = document.createElement('img');
|
||||
var subject = new Icon(element);
|
||||
subject.render();
|
||||
|
||||
var backgroundSize = element.style.backgroundSize;
|
||||
assert.isTrue(backgroundSize.contains(subject.size / 10));
|
||||
});
|
||||
|
||||
test('it sets the background image when there is an icon', function() {
|
||||
var fakeBlob = new Blob([], {type: 'image/png'});
|
||||
var fakeURL = URL.createObjectURL(fakeBlob);
|
||||
sinon.stub(URL, 'createObjectURL').returns(fakeURL);
|
||||
|
||||
var realPromise = window.Promise;
|
||||
window.Promise = function() {
|
||||
return {
|
||||
then: function(resolve) {
|
||||
resolve(fakeBlob);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var imageSpy = this.sinon.spy(window, 'Image');
|
||||
|
||||
var element = document.createElement('img');
|
||||
var subject = new Icon(element, 'http://myweb/xxx.png');
|
||||
subject.render();
|
||||
|
||||
sinon.stub(GridIconRenderer.prototype, 'favicon', function() {
|
||||
return {
|
||||
then: function(resolve) {
|
||||
resolve(fakeBlob);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
imageSpy.lastCall.thisValue.triggerEvent('onload');
|
||||
|
||||
var backgroundImage = element.style.backgroundImage;
|
||||
assert.equal(backgroundImage, 'url(\"' + fakeURL + '\")');
|
||||
|
||||
window.Promise = realPromise;
|
||||
GridIconRenderer.prototype.favicon.restore();
|
||||
URL.createObjectURL.restore();
|
||||
});
|
||||
|
||||
});
|
||||
40
gaia/apps/sharedtest/test/unit/ice_store_test.js
Executable file
40
gaia/apps/sharedtest/test/unit/ice_store_test.js
Executable file
|
|
@ -0,0 +1,40 @@
|
|||
/* global MockNavigatorDatastore, MockDatastore */
|
||||
'use strict';
|
||||
|
||||
require('/shared/test/unit/mocks/mock_navigator_datastore.js');
|
||||
require('/shared/js/contacts/utilities/ice_store.js');
|
||||
|
||||
suite('Sync Datastore', function() {
|
||||
var subject;
|
||||
var realDatastore;
|
||||
|
||||
suiteSetup(function() {
|
||||
realDatastore = navigator.getDataStores;
|
||||
navigator.getDataStores = MockNavigatorDatastore.getDataStores;
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
subject = window.ICEStore;
|
||||
MockDatastore._records = {'ICE_CONTACTS': [1,2]};
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
navigator.getDataStores = realDatastore;
|
||||
});
|
||||
|
||||
suite('Getting data', function() {
|
||||
test('Get ICE contacts', function(done) {
|
||||
subject.getContacts().then(function(contacts){
|
||||
assert.deepEqual(contacts, [1, 2]);
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Setting data', function() {
|
||||
test('Set ICE contacts', function(done) {
|
||||
subject.setContacts([3,4]).then(function() {
|
||||
assert.deepEqual(MockDatastore._records, {'ICE_CONTACTS': [3,4]});
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
650
gaia/apps/sharedtest/test/unit/icons_helper_test.js
Executable file
650
gaia/apps/sharedtest/test/unit/icons_helper_test.js
Executable file
|
|
@ -0,0 +1,650 @@
|
|||
/* global IconsHelper, MocksHelper, MockNavigatorDatastore, MockDatastore,
|
||||
MockXMLHttpRequest, WebManifestHelper */
|
||||
|
||||
'use strict';
|
||||
|
||||
require('/shared/test/unit/mocks/mock_navigator_datastore.js');
|
||||
require('/apps/system/test/unit/mock_xmlhttprequest.js');
|
||||
|
||||
require('/shared/js/web_manifest_helper.js');
|
||||
require('/shared/js/icons_helper.js');
|
||||
|
||||
if (!window.XMLHttpRequest) {
|
||||
window.XMLHttpRequest = null;
|
||||
}
|
||||
|
||||
var mocksForIconsHelper = new MocksHelper([
|
||||
'Datastore'
|
||||
]).init();
|
||||
|
||||
suite('Icons Helper', () => {
|
||||
var devicePixelRatioProperty;
|
||||
var dpr = 1;
|
||||
var fakeDevicePixelRatio = {
|
||||
get: () => {
|
||||
return dpr;
|
||||
}
|
||||
};
|
||||
var realDataStores;
|
||||
var realXHR;
|
||||
|
||||
mocksForIconsHelper.attachTestHelpers();
|
||||
|
||||
suiteSetup(() => {
|
||||
// As we are selecting the icons based on device pixel ratio
|
||||
// let's mock the property.
|
||||
devicePixelRatioProperty =
|
||||
Object.getOwnPropertyDescriptor(window, 'devicePixelRatio');
|
||||
Object.defineProperty(window, 'devicePixelRatio', {
|
||||
configurable: true,
|
||||
get: fakeDevicePixelRatio.get
|
||||
});
|
||||
|
||||
realDataStores = navigator.getDataStores;
|
||||
realXHR = window.XMLHttpRequest;
|
||||
|
||||
navigator.getDataStores = MockNavigatorDatastore.getDataStores;
|
||||
window.XMLHttpRequest = MockXMLHttpRequest;
|
||||
});
|
||||
|
||||
suiteTeardown(() => {
|
||||
Object.defineProperty(window, 'devicePixelRatio', devicePixelRatioProperty);
|
||||
navigator.getDataStores = realDataStores;
|
||||
window.XMLHttpRequest = realXHR;
|
||||
});
|
||||
|
||||
suite('IconsHelper.getIcon()', () => {
|
||||
var placeObj, siteObj;
|
||||
|
||||
setup(() => {
|
||||
placeObj = {
|
||||
icons: {
|
||||
'http://example.com/metaTagIconUrl': {
|
||||
sizes: ['32x32']
|
||||
}
|
||||
}
|
||||
};
|
||||
siteObj = {
|
||||
webManifestUrl: 'http://example.com',
|
||||
webManifest: WebManifestHelper.processRawManifest({
|
||||
icons: [
|
||||
{
|
||||
src: 'webManifestIconUrl',
|
||||
sizes: '32x32'
|
||||
}
|
||||
]
|
||||
}, 'http://example.com')
|
||||
};
|
||||
});
|
||||
|
||||
test('Prioritise icons from the Web manifest over the rest', done => {
|
||||
var origin = 'http://origin.com';
|
||||
var path = '/test.png';
|
||||
siteObj.manifest = {
|
||||
origin: origin,
|
||||
icons: {
|
||||
'32': path
|
||||
}
|
||||
};
|
||||
IconsHelper.getIcon('http://example.com', 32, placeObj, siteObj)
|
||||
.then(iconUrl => {
|
||||
assert.equal((new URL(iconUrl)).pathname, '/webManifestIconUrl');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('Prioritise Firefox manifest after Web manifest', done => {
|
||||
var origin = 'http://origin.com';
|
||||
var path = '/test.png';
|
||||
siteObj.webManifest = null;
|
||||
siteObj.manifest = {
|
||||
origin: origin,
|
||||
icons: {
|
||||
'32': path
|
||||
}
|
||||
};
|
||||
IconsHelper.getIcon('http://example.com', 32, placeObj, siteObj)
|
||||
.then(iconUrl => {
|
||||
assert.equal((new URL(iconUrl)).href, origin + path);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('Works with overridden siteObj.origin', done => {
|
||||
var origin = 'http://origin.com';
|
||||
var path = '/test.png';
|
||||
siteObj.webManifest = null;
|
||||
siteObj.manifest = {
|
||||
origin: origin,
|
||||
icons: {
|
||||
'32': path
|
||||
}
|
||||
};
|
||||
siteObj.origin = 'https://realorigin.com';
|
||||
IconsHelper.getIcon('http://example.com', 32, placeObj, siteObj)
|
||||
.then(iconUrl => {
|
||||
assert.equal((new URL(iconUrl)).href, siteObj.origin + path);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('Works with external icons', done => {
|
||||
var origin = 'http://origin.com';
|
||||
var path = 'http://test.com/test.png';
|
||||
siteObj.webManifest = null;
|
||||
siteObj.manifest = {
|
||||
origin: origin,
|
||||
icons: {
|
||||
'32': path
|
||||
}
|
||||
};
|
||||
IconsHelper.getIcon('http://example.com', 32, placeObj, siteObj)
|
||||
.then(iconUrl => {
|
||||
assert.equal((new URL(iconUrl)).href, path);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('Prioritise icons from the web manifest over the rest', done => {
|
||||
IconsHelper.getIcon('http://example.com', 32, placeObj, siteObj)
|
||||
.then(iconUrl => {
|
||||
assert.equal((new URL(iconUrl)).pathname, '/webManifestIconUrl');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('Return meta tag icons when the web manifest hasn\'t any', done => {
|
||||
siteObj.webManifest.icons = [];
|
||||
IconsHelper.getIcon('http://example.com', 32, placeObj, siteObj)
|
||||
.then(iconUrl => {
|
||||
assert.equal((new URL(iconUrl)).pathname, '/metaTagIconUrl');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('Prioritise icons from the meta tags over favicon', done => {
|
||||
IconsHelper.getIcon('http://example.com', 32, placeObj)
|
||||
.then(iconUrl => {
|
||||
assert.equal((new URL(iconUrl)).pathname, '/metaTagIconUrl');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('Return favicon when no icons in the meta tags or manifest', done => {
|
||||
siteObj.webManifest.icons = [];
|
||||
placeObj.icons = [];
|
||||
IconsHelper.getIcon('http://example.com', 32, placeObj, siteObj)
|
||||
.then(iconUrl => {
|
||||
assert.equal((new URL(iconUrl)).pathname, '/favicon.ico');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('Ensure we fallback to favicon.ico', done => {
|
||||
IconsHelper.getIcon('http://example.com')
|
||||
.then(iconUrl => {
|
||||
assert.equal((new URL(iconUrl)).pathname, '/favicon.ico');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
suite('-moz-resolution fragment', () => {
|
||||
teardown(() => {
|
||||
dpr = 1;
|
||||
});
|
||||
|
||||
test('Without icon target size', done => {
|
||||
IconsHelper.getIcon('http://example.com')
|
||||
.then(iconUrl => {
|
||||
assert.equal((new URL(iconUrl)).hash, '');
|
||||
})
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('With icon target size', done => {
|
||||
dpr = 1.5;
|
||||
IconsHelper.getIcon('http://example.com', 64)
|
||||
.then(iconUrl => {
|
||||
// targetSize * devicePixelRatio
|
||||
assert.ok(new URL(iconUrl).includes('-moz-resolution=96,96'));
|
||||
})
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('IconsHelper.getIconBlob()', () => {
|
||||
var createElementStub, createObjectURLStub;
|
||||
|
||||
function getStubs() {
|
||||
createElementStub = sinon.stub(document, 'createElement', () => {
|
||||
return {
|
||||
src: '',
|
||||
naturalWidth: 32,
|
||||
naturalHeight: 32,
|
||||
set onload(fn) {
|
||||
fn();
|
||||
},
|
||||
onerror: null
|
||||
};
|
||||
});
|
||||
createObjectURLStub = sinon.stub(URL, 'createObjectURL', url => url);
|
||||
}
|
||||
|
||||
teardown(() => {
|
||||
createElementStub.restore();
|
||||
createObjectURLStub.restore();
|
||||
MockXMLHttpRequest.mTeardown();
|
||||
});
|
||||
|
||||
test('The dataStore should be empty on first call', done => {
|
||||
getStubs();
|
||||
IconsHelper.getIconBlob('http://example.com', 32)
|
||||
.then(() => {
|
||||
assert.equal(MockDatastore.getLength(), 0);
|
||||
done();
|
||||
})
|
||||
.catch(() => {
|
||||
done();
|
||||
});
|
||||
setTimeout(() => {
|
||||
MockXMLHttpRequest.mSendReadyState();
|
||||
MockXMLHttpRequest.mSendOnLoad({response: 'abc'});
|
||||
});
|
||||
});
|
||||
|
||||
test('The icon should already be in the datastore', done => {
|
||||
getStubs();
|
||||
IconsHelper.getIconBlob('http://example.com', 32)
|
||||
.then(() => {
|
||||
assert.equal(MockDatastore.getLength(), 1);
|
||||
done();
|
||||
})
|
||||
.catch(() => {
|
||||
done();
|
||||
});
|
||||
setTimeout(() => {
|
||||
MockXMLHttpRequest.mSendReadyState();
|
||||
MockXMLHttpRequest.mSendOnLoad({response: 'abc'});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('defaultIconSize', () => {
|
||||
test('1', function() {
|
||||
dpr = 1;
|
||||
assert.isDefined(IconsHelper.defaultIconSize);
|
||||
assert.equal(84, IconsHelper.defaultIconSize);
|
||||
});
|
||||
|
||||
test('1.5', function() {
|
||||
dpr = 1.5;
|
||||
assert.isDefined(IconsHelper.defaultIconSize);
|
||||
assert.equal(142, IconsHelper.defaultIconSize);
|
||||
});
|
||||
|
||||
test('2', function() {
|
||||
dpr = 2;
|
||||
assert.isDefined(IconsHelper.defaultIconSize);
|
||||
assert.equal(142, IconsHelper.defaultIconSize);
|
||||
});
|
||||
});
|
||||
|
||||
suite('IconsHelper.getBestIconFromWebManifest()', () => {
|
||||
var webManifest;
|
||||
var webManifestUrl = 'http://example.com';
|
||||
test('Get correct icon with no size', () => {
|
||||
webManifest = WebManifestHelper.processRawManifest({
|
||||
icons: [
|
||||
{
|
||||
src: 'uri1',
|
||||
sizes: ''
|
||||
}
|
||||
]
|
||||
}, webManifestUrl);
|
||||
var iconUrl = IconsHelper.getBestIconFromWebManifest(webManifest);
|
||||
assert.isNotNull(iconUrl);
|
||||
assert.equal((new URL(iconUrl)).pathname, '/uri1');
|
||||
});
|
||||
|
||||
test('Get icon with size', () => {
|
||||
webManifest = WebManifestHelper.processRawManifest({
|
||||
icons: [
|
||||
{
|
||||
src: 'uri1',
|
||||
sizes: ''
|
||||
},
|
||||
{
|
||||
src: 'uri2',
|
||||
sizes: '16x16'
|
||||
}
|
||||
]
|
||||
}, webManifestUrl);
|
||||
|
||||
var iconUrl = IconsHelper.getBestIconFromWebManifest(webManifest);
|
||||
assert.isNotNull(iconUrl);
|
||||
assert.equal((new URL(iconUrl)).pathname, '/uri2');
|
||||
});
|
||||
|
||||
test('Get best icon which doesn\'t match specific size', () => {
|
||||
// With dpr = 1
|
||||
webManifest = WebManifestHelper.processRawManifest({
|
||||
icons: [
|
||||
{
|
||||
src: 'uri1',
|
||||
sizes: '90x90'
|
||||
},
|
||||
{
|
||||
src: 'uri2',
|
||||
sizes: '200x200'
|
||||
}
|
||||
]
|
||||
}, webManifestUrl);
|
||||
|
||||
var iconUrl = IconsHelper.getBestIconFromWebManifest(webManifest);
|
||||
assert.isNotNull(iconUrl);
|
||||
assert.equal((new URL(iconUrl)).pathname, '/uri1');
|
||||
});
|
||||
|
||||
test('With higher dpi', () => {
|
||||
// With dpr = 1.5
|
||||
dpr = 1.5;
|
||||
webManifest = WebManifestHelper.processRawManifest({
|
||||
icons: [
|
||||
{
|
||||
src: 'uri1',
|
||||
sizes: '90x90'
|
||||
},
|
||||
{
|
||||
src: 'uri2',
|
||||
sizes: '180x180'
|
||||
},
|
||||
{
|
||||
src: 'uri3',
|
||||
sizes: '500x500'
|
||||
}
|
||||
]
|
||||
}, webManifestUrl);
|
||||
|
||||
var iconUrl = IconsHelper.getBestIconFromWebManifest(webManifest);
|
||||
assert.isNotNull(iconUrl);
|
||||
assert.equal((new URL(iconUrl)).pathname, '/uri2');
|
||||
dpr = 1;
|
||||
});
|
||||
|
||||
test('Specific icon size', () => {
|
||||
// With dpr = 1.5
|
||||
dpr = 1.5;
|
||||
webManifest = WebManifestHelper.processRawManifest({
|
||||
icons: [
|
||||
{
|
||||
src: 'uri1',
|
||||
sizes: '90x90'
|
||||
},
|
||||
{
|
||||
src: 'uri2',
|
||||
sizes: '190x190'
|
||||
},
|
||||
{
|
||||
src: 'uri3',
|
||||
sizes: '500x500'
|
||||
}
|
||||
]
|
||||
}, webManifestUrl);
|
||||
|
||||
// With dpr 1.5 we should get icon 'uri2'
|
||||
// Let's ask for a bigger size.
|
||||
|
||||
var iconUrl = IconsHelper.getBestIconFromWebManifest(webManifest, 400);
|
||||
assert.isNotNull(iconUrl);
|
||||
assert.equal((new URL(iconUrl)).pathname, '/uri3');
|
||||
dpr = 1;
|
||||
});
|
||||
});
|
||||
|
||||
suite('IconsHelper.getBestIconFromMetaTags()', () => {
|
||||
test('Get correct icon with no size', () => {
|
||||
var icons = {
|
||||
'uri1': {
|
||||
sizes: []
|
||||
}
|
||||
};
|
||||
|
||||
var iconUrl = IconsHelper.getBestIconFromMetaTags(icons);
|
||||
assert.isNotNull(iconUrl);
|
||||
assert.equal(iconUrl, 'uri1');
|
||||
});
|
||||
|
||||
test('Get icon with size', () => {
|
||||
var icons = {
|
||||
'uri1': {
|
||||
sizes: []
|
||||
},
|
||||
'uri2': {
|
||||
sizes: ['16x16']
|
||||
}
|
||||
};
|
||||
|
||||
var iconUrl = IconsHelper.getBestIconFromMetaTags(icons);
|
||||
assert.isNotNull(iconUrl);
|
||||
assert.equal(iconUrl, 'uri2');
|
||||
});
|
||||
|
||||
test('Get best icon which doesn\'t match specific size', () => {
|
||||
// With dpr = 1
|
||||
var icons = {
|
||||
'uri1': {
|
||||
sizes: ['90x90']
|
||||
},
|
||||
'uri2': {
|
||||
sizes: ['200x200']
|
||||
}
|
||||
};
|
||||
|
||||
var iconUrl = IconsHelper.getBestIconFromMetaTags(icons);
|
||||
assert.isNotNull(iconUrl);
|
||||
assert.equal(iconUrl, 'uri1');
|
||||
});
|
||||
|
||||
test('With higher dpi', () => {
|
||||
// With dpr = 1.5
|
||||
dpr = 1.5;
|
||||
var icons = {
|
||||
'uri1': {
|
||||
sizes: ['90x90']
|
||||
},
|
||||
'uri2': {
|
||||
sizes: ['180x180']
|
||||
},
|
||||
'uri3': {
|
||||
sizes: ['500x500']
|
||||
}
|
||||
};
|
||||
var iconUrl = IconsHelper.getBestIconFromMetaTags(icons);
|
||||
assert.isNotNull(iconUrl);
|
||||
assert.equal(iconUrl, 'uri2');
|
||||
dpr = 1;
|
||||
});
|
||||
|
||||
test('Specific icon size', () => {
|
||||
// With dpr = 1.5
|
||||
dpr = 1.5;
|
||||
var icons = {
|
||||
'uri1': {
|
||||
sizes: ['90x90']
|
||||
},
|
||||
'uri2': {
|
||||
sizes: ['200x200']
|
||||
},
|
||||
'uri3': {
|
||||
sizes: ['500x500']
|
||||
}
|
||||
};
|
||||
|
||||
// With dpr 1.5 we should get icon 'uri2'
|
||||
// Let's ask for a bigger size.
|
||||
|
||||
var iconUrl = IconsHelper.getBestIconFromMetaTags(icons, 400);
|
||||
assert.isNotNull(iconUrl);
|
||||
assert.equal(iconUrl, 'uri3');
|
||||
dpr = 1;
|
||||
});
|
||||
});
|
||||
|
||||
suite('IconsHelper.getNearestSize()', () => {
|
||||
suite('No size information', () => {
|
||||
test('Single element with no size info', () => {
|
||||
var sizes = [];
|
||||
var result = IconsHelper.getNearestSize(sizes, 64);
|
||||
assert.equal(result, -1);
|
||||
});
|
||||
|
||||
test('Several items one without size info', () => {
|
||||
var sizes = [undefined, '10x10', ''];
|
||||
|
||||
var result = IconsHelper.getNearestSize(sizes, 64);
|
||||
assert.isNotNull(result);
|
||||
assert.equal(result, '10');
|
||||
});
|
||||
});
|
||||
|
||||
suite('Correct size', () => {
|
||||
test('Check sizes detected', () => {
|
||||
var sizes = ['10x10', '20x20', '30x30'];
|
||||
|
||||
var result = IconsHelper.getNearestSize(sizes, 24);
|
||||
assert.isNotNull(result);
|
||||
// nearestSize picks the nearest - larger or smaller
|
||||
assert.equal(result, 20);
|
||||
});
|
||||
|
||||
test('Check with incorrect file sizes', () => {
|
||||
var sizes = ['10', '', '30x30', 'x'];
|
||||
var result = IconsHelper.getNearestSize(sizes, 24);
|
||||
assert.equal(result, 30);
|
||||
});
|
||||
|
||||
test('bestMatch param', () => {
|
||||
var sizes = ['10x10', '20x20', '30x30'];
|
||||
var result = IconsHelper.getNearestSize(sizes, 64, 40);
|
||||
assert.equal(result, 40);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('IconsHelper.fetchIcon()', () => {
|
||||
var createElementStub, createObjectURLStub;
|
||||
|
||||
function getStubs(naturalWidth = 32) {
|
||||
createElementStub = sinon.stub(document, 'createElement', () => {
|
||||
return {
|
||||
src: '',
|
||||
naturalWidth: naturalWidth,
|
||||
naturalHeight: 32,
|
||||
set onload(fn) {
|
||||
fn();
|
||||
},
|
||||
onerror: null
|
||||
};
|
||||
});
|
||||
createObjectURLStub = sinon.stub(URL, 'createObjectURL', url => url);
|
||||
}
|
||||
|
||||
teardown(() => {
|
||||
createElementStub.restore();
|
||||
createObjectURLStub.restore();
|
||||
MockXMLHttpRequest.mTeardown();
|
||||
});
|
||||
|
||||
test('Should resolve to an object', done => {
|
||||
getStubs();
|
||||
IconsHelper.fetchIcon('http://example.com')
|
||||
.then((iconObject) => {
|
||||
assert.isTrue(createObjectURLStub.calledOnce);
|
||||
assert.isObject(iconObject);
|
||||
assert.equal(iconObject.blob.content, 'abc');
|
||||
assert.equal(iconObject.size, '32');
|
||||
done();
|
||||
})
|
||||
.catch(() => {
|
||||
assert.isTrue(false, 'Should not throw.');
|
||||
done();
|
||||
});
|
||||
MockXMLHttpRequest.mSendReadyState();
|
||||
MockXMLHttpRequest.mSendOnLoad({
|
||||
response: { content: 'abc', type: 'image/png' }
|
||||
});
|
||||
});
|
||||
|
||||
test('Should return the largest side of the image', done => {
|
||||
getStubs(512);
|
||||
IconsHelper.fetchIcon('http://example.com')
|
||||
.then((iconObject) => {
|
||||
assert.equal(iconObject.size, '512');
|
||||
done();
|
||||
})
|
||||
.catch(() => {
|
||||
assert.isTrue(false, 'Should not throw.');
|
||||
done();
|
||||
});
|
||||
MockXMLHttpRequest.mSendReadyState();
|
||||
MockXMLHttpRequest.mSendOnLoad({
|
||||
response: { content: 'abc', type: 'image/png' }
|
||||
});
|
||||
});
|
||||
|
||||
test('getBestIconFromWebManifest', function() {
|
||||
var manifest1 = WebManifestHelper.processRawManifest({
|
||||
'icons': [
|
||||
{
|
||||
'src': 'icon-64.png',
|
||||
'sizes': '32x32 64x64',
|
||||
'type': 'image/png'
|
||||
},
|
||||
{
|
||||
'src': 'icon-128.png',
|
||||
'sizes': '128x128',
|
||||
'type': 'image/png'
|
||||
}
|
||||
]
|
||||
}, 'http://example.com/manifest.json');
|
||||
|
||||
var manifest2 = WebManifestHelper.processRawManifest({
|
||||
'icons': [
|
||||
{
|
||||
'src': 'icon-with-no-sizes.png',
|
||||
'type': 'image/png'
|
||||
},
|
||||
{
|
||||
'src': 'icon-128.png',
|
||||
'sizes': '128x128',
|
||||
'type': 'image/png'
|
||||
}
|
||||
]
|
||||
}, 'http://example.com/manifest.json');
|
||||
|
||||
var manifest3 = WebManifestHelper.processRawManifest({
|
||||
'icons': []
|
||||
}, 'http://example.com/manifest.json');
|
||||
|
||||
var url = IconsHelper.getBestIconFromWebManifest(manifest1, 64);
|
||||
assert.equal(url, 'http://example.com/icon-64.png');
|
||||
|
||||
url = IconsHelper.getBestIconFromWebManifest(manifest2, 64);
|
||||
assert.equal(url, 'http://example.com/icon-128.png');
|
||||
|
||||
url = IconsHelper.getBestIconFromWebManifest(manifest3, 64);
|
||||
assert.equal(url, null);
|
||||
});
|
||||
});
|
||||
});
|
||||
587
gaia/apps/sharedtest/test/unit/image_utils_test.js
Executable file
587
gaia/apps/sharedtest/test/unit/image_utils_test.js
Executable file
|
|
@ -0,0 +1,587 @@
|
|||
'use strict';
|
||||
|
||||
/* global ImageUtils */
|
||||
|
||||
require('/shared/js/image_utils.js');
|
||||
|
||||
suite('ImageUtils', function() {
|
||||
|
||||
const testImages = [
|
||||
{
|
||||
type: 'image/jpeg',
|
||||
width: 25,
|
||||
height: 24,
|
||||
base64: '/9j/4AAQSkZJRgABAQEASABIAAD/4ge4SUNDX1BST0ZJTEUAAQEAAAeoYXBwbAIgAABtbnRyUkdCIFhZWiAH2QACABkACwAaAAthY3NwQVBQTAAAAABhcHBsAAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtkZXNjAAABCAAAAG9kc2NtAAABeAAABWxjcHJ0AAAG5AAAADh3dHB0AAAHHAAAABRyWFlaAAAHMAAAABRnWFlaAAAHRAAAABRiWFlaAAAHWAAAABRyVFJDAAAHbAAAAA5jaGFkAAAHfAAAACxiVFJDAAAHbAAAAA5nVFJDAAAHbAAAAA5kZXNjAAAAAAAAABRHZW5lcmljIFJHQiBQcm9maWxlAAAAAAAAAAAAAAAUR2VuZXJpYyBSR0IgUHJvZmlsZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbWx1YwAAAAAAAAAeAAAADHNrU0sAAAAoAAABeGhySFIAAAAoAAABoGNhRVMAAAAkAAAByHB0QlIAAAAmAAAB7HVrVUEAAAAqAAACEmZyRlUAAAAoAAACPHpoVFcAAAAWAAACZGl0SVQAAAAoAAACem5iTk8AAAAmAAAComtvS1IAAAAWAAACyGNzQ1oAAAAiAAAC3mhlSUwAAAAeAAADAGRlREUAAAAsAAADHmh1SFUAAAAoAAADSnN2U0UAAAAmAAAConpoQ04AAAAWAAADcmphSlAAAAAaAAADiHJvUk8AAAAkAAADomVsR1IAAAAiAAADxnB0UE8AAAAmAAAD6G5sTkwAAAAoAAAEDmVzRVMAAAAmAAAD6HRoVEgAAAAkAAAENnRyVFIAAAAiAAAEWmZpRkkAAAAoAAAEfHBsUEwAAAAsAAAEpHJ1UlUAAAAiAAAE0GFyRUcAAAAmAAAE8mVuVVMAAAAmAAAFGGRhREsAAAAuAAAFPgBWAWEAZQBvAGIAZQBjAG4A/QAgAFIARwBCACAAcAByAG8AZgBpAGwARwBlAG4AZQByAGkBDQBrAGkAIABSAEcAQgAgAHAAcgBvAGYAaQBsAFAAZQByAGYAaQBsACAAUgBHAEIAIABnAGUAbgDoAHIAaQBjAFAAZQByAGYAaQBsACAAUgBHAEIAIABHAGUAbgDpAHIAaQBjAG8EFwQwBDMEMAQ7BEwEPQQ4BDkAIAQ/BEAEPgREBDAEOQQ7ACAAUgBHAEIAUAByAG8AZgBpAGwAIABnAOkAbgDpAHIAaQBxAHUAZQAgAFIAVgBCkBp1KAAgAFIARwBCACCCcl9pY8+P8ABQAHIAbwBmAGkAbABvACAAUgBHAEIAIABnAGUAbgBlAHIAaQBjAG8ARwBlAG4AZQByAGkAcwBrACAAUgBHAEIALQBwAHIAbwBmAGkAbMd8vBgAIABSAEcAQgAg1QS4XNMMx3wATwBiAGUAYwBuAP0AIABSAEcAQgAgAHAAcgBvAGYAaQBsBeQF6AXVBeQF2QXcACAAUgBHAEIAIAXbBdwF3AXZAEEAbABsAGcAZQBtAGUAaQBuAGUAcwAgAFIARwBCAC0AUAByAG8AZgBpAGwAwQBsAHQAYQBsAOEAbgBvAHMAIABSAEcAQgAgAHAAcgBvAGYAaQBsZm6QGgAgAFIARwBCACBjz4/wZYdO9k4AgiwAIABSAEcAQgAgMNcw7TDVMKEwpDDrAFAAcgBvAGYAaQBsACAAUgBHAEIAIABnAGUAbgBlAHIAaQBjA5MDtQO9A7kDugPMACADwAPBA78DxgOvA7sAIABSAEcAQgBQAGUAcgBmAGkAbAAgAFIARwBCACAAZwBlAG4A6QByAGkAYwBvAEEAbABnAGUAbQBlAGUAbgAgAFIARwBCAC0AcAByAG8AZgBpAGUAbA5CDhsOIw5EDh8OJQ5MACAAUgBHAEIAIA4XDjEOSA4nDkQOGwBHAGUAbgBlAGwAIABSAEcAQgAgAFAAcgBvAGYAaQBsAGkAWQBsAGUAaQBuAGUAbgAgAFIARwBCAC0AcAByAG8AZgBpAGkAbABpAFUAbgBpAHcAZQByAHMAYQBsAG4AeQAgAHAAcgBvAGYAaQBsACAAUgBHAEIEHgQxBEkEOAQ5ACAEPwRABD4ERAQ4BDsETAAgAFIARwBCBkUGRAZBACAGKgY5BjEGSgZBACAAUgBHAEIAIAYnBkQGOQYnBkUARwBlAG4AZQByAGkAYwAgAFIARwBCACAAUAByAG8AZgBpAGwAZQBHAGUAbgBlAHIAZQBsACAAUgBHAEIALQBiAGUAcwBrAHIAaQB2AGUAbABzAGV0ZXh0AAAAAENvcHlyaWdodCAyMDA3IEFwcGxlIEluYy4sIGFsbCByaWdodHMgcmVzZXJ2ZWQuAFhZWiAAAAAAAADzUgABAAAAARbPWFlaIAAAAAAAAHRNAAA97gAAA9BYWVogAAAAAAAAWnUAAKxzAAAXNFhZWiAAAAAAAAAoGgAAFZ8AALg2Y3VydgAAAAAAAAABAc0AAHNmMzIAAAAAAAEMQgAABd7///MmAAAHkgAA/ZH///ui///9owAAA9wAAMBs/+EAnkV4aWYAAE1NACoAAAAIAAYBEgADAAAAAQABAAABGgAFAAAAAQAAAFYBGwAFAAAAAQAAAF4BKAADAAAAAQACAAABMQACAAAAEQAAAGaHaQAEAAAAAQAAAHgAAAAAAAAASAAAAAEAAABIAAAAAUFkb2JlIEltYWdlUmVhZHkAAAACoAIABAAAAAEAAAAZoAMABAAAAAEAAAAYAAAAAP/bAEMAIBYYHBgUIBwaHCQiICYwUDQwLCwwYkZKOlB0Znp4cmZwboCQuJyAiK6KbnCg2qKuvsTO0M58muLy4MjwuMrOxv/bAEMBIiQkMCowXjQ0XsaEcITGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxv/AABEIABgAGQMBIgACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/ANu5m8iEyYzjoPWsuXU5YzuJHrjHFaF/C01qyp94cgetc5JGJDtOFJ4OQcikpcstdi/Z88dNzqIZBLGrjuM1JVWxD+SGcbc4CjvirVEW2tSWrMrOblXYqoYZOAfSmZuASxhRienHSiimIlJm38Zxnpip6KKAP//Z' // jshint ignore:line
|
||||
},
|
||||
{
|
||||
type: 'image/png',
|
||||
width: 25,
|
||||
height: 24,
|
||||
base64: 'iVBORw0KGgoAAAANSUhEUgAAABkAAAAYCAYAAAAPtVbGAAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAmtpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuMS4yIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIj4KICAgICAgICAgPHhtcDpDcmVhdG9yVG9vbD5BZG9iZSBJbWFnZVJlYWR5PC94bXA6Q3JlYXRvclRvb2w+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOllSZXNvbHV0aW9uPjcyPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPgogICAgICAgICA8dGlmZjpYUmVzb2x1dGlvbj43MjwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CuH1si4AAAYdSURBVEgNrVVdbFTHFf5m7t296/1xDPYaNtA4XgcQApMQO45bsFkHKA4hqqPIqCAeUopC8hJFbR4jbZxWqC/NS6QGRaqqqFLbmJBEiRKFxGXXmApVYinQpJCA+XEKa7Lrv/2/d++dyZmLbUzi9Kkjje78nDnfOd/5Zi6klPhfvX9Qaugf1BazeSWR0Adpf7G9hWtMTRZtA4wPIMHj8Zit9s8DgbeTheZ82QrWeLwzsW3BK48DptpLpVKetra2qhov1n4YZNb6vYuoP3rmxovpqta3RBdNPh1G1RFlWbVHN97rezuzdvnrr61EmcwZGINLy3eQfhBk+gDbeGjjcORoru53tZq3tbN+CpN6A2ZYEI6wkS076AmZ6H2gdL7m6rl4155n3nd9M0L6Dj13gxBFiEtxZPduLTx17Nj1lQe2XvPdi3UrvhBmZIXzTH4vb+IaAk4Z09kZqWdy/I/7I3xD4zi+Sn74fOf+w4cJiFMXC5NRC/MtmXQNgCupoMTS6E8Kr2EPXjI7av7Epy46np9d/48Wzoxpk9m8Vlcs6VfSM/zM+YK5NBrA2h7n1ewFFiFn4siR3dq8UxroCydz43XR6hbTYs0zMgrd1j3lGQfthXewbukN/D67DV8Wg2g0JxGu2PjD4Gmjb7MU0TVG2JnEQ+QjHY1eUcE7c/4WZMJYLCFdJVUs9lSNrcFxnKpTEdxf/S/QGMFb4+0YvVVF08QNFNI3kfn6MlqXMViVKYq/CWOpXb3KcVvbaZtKQyq43e5kMkDqiMPVc6WABk0NOYeVr+D8uRZ8rK3GW2M5ii8PWAW0BGeAL7NY+zRHy/1MongTFy+JjmYXxi39IiBzsPQtkSC9xKpDeZqmDZ1pKJYsrOFj6F1dQZnKOvRFhSxzYBP/gDNpSr2mhHoub827URCzV/AOXa9ADjBSF7WKyc6WyIddsblHN7CyLosXmkbwessp9NcfxyMNF8CsEhAqoufBgrSmJ7Spf2exbHzqM3U+OcD0hTK+QxdpO9LeruaiXHSGKoy/XCeZ5mNSWEUvn65K/OubCo5dWIYRGGQ2jV9uz6M5+A2yKcbGz4Qw8RFLNf1GocRA1M+3OyC0lE6lXEX0X/h6+M/R+z4JMK1XQNh+SG/Iy7Ci3oP16wVWOzk83FjFj4k6+6xRLd3UvZcus/T0tH1VeR5OJkVMDWbb3ZeRFpOM6TEp7cPL71vv0ZEMh7R6j19aAZ/Q/TWSMQ9RrTH4gqA8mWNOMc/EDKvmynzP02dGjyrK41LedRm/B+KCq9qQ4a/uWb73fp/njRUhrdbrIzEQSx4CIXAlPNi2jut528yUir/49Wjmr+qski4xP1ty19vty6g2+vv750UwKKVzkGJ+E/hoSy54YJMVONjo0x8NGIxeYHqamHRI8Pm0Jb86fs164xNk3//7qlXGo/v2Vcm/GBgYcH3F4/HbGSnQxTrFwAcBDTE3kIaHgQ374O97LrRs/25/Xd8m1HaSTVidTcRi+pyP2ILx3No8XV1du5boetEwTa4ZhriVSCTc2387YaD5iSeWX+vomJTxuDW3tvDbvn37PZX0kvLnnw9anTt31gYtyzM0NDShbNy0uru3doBXfiuEtlXX8bhpGrVqs6enhyoBbNmyfW1TvnJo56cnIkQ6d7vamG203xqw5M/r66f6lC+jaD1pWax9x44dAWXiZtLVtW0XHd1HN/QQpfggqSZCXw+TnKJ2VJlDkqGRIjpF7EQllxeZQIBKqZEQ3rEs+RQF+AFj9o8458+S/i5xLv5GbFxT9XYzob+0Q5VxqGZ0jK2CkPQayRKFsIkAghR5iJ67UQHZKrg8yQTZgO2iqm4gSm6Rm7qTJz+9Sd8yBXdKCP4XW/CDmzc/tobm0gXhghnEwYmRkePnaE1h5yiis6SIMYr6Mrj8JzktkHGa7DZSpA2ksMvEhN3d3RsRHKeJsr0USCtJ+4au236yy2qa5lV0uTee9H7CMEy/Sq27+6dvcm4ZmUx4LBzOXHIcTwsBX6WDOavq9XKv2CA05wNb09K+quZXF2Ik8dlQLLbtEcacDE3H6Unt5ODvDg8fu6p8qrfye5eH1v6v7Vsdiw9i1H14XAAAAABJRU5ErkJggg==' // jshint ignore:line
|
||||
},
|
||||
{
|
||||
type: 'image/gif',
|
||||
width: 25,
|
||||
height: 24,
|
||||
base64: 'R0lGODlhGQAYAPcAAAAAPAANPT0AM3oQF2MpLmksMWNFL3pnOwAASAAAViMhTD09Qj09Qz89Qjw+Qjw+Qz0+Qz0/Qz4+Qz0+RD4+RCInVgAndAAleScwZiMxalc1XmZSXm9UXmJMZHl6dpEWHJAYG5wbHZsfHIogHqIhHaMlHKMmHKQkHawjH6EsH6spHrIrH7MzH7YyH7gyH7k0Hrs0Hr03H74+GLs6HoQpJZU/KZ88KrEoILYqIKA0JbU/ILo6ILA/LcE6HcI7H8E9HcM+HsQ9H8E1IMA5I8s9IL9EDqxJKr5EILpTLZtmNdpUAMNCH8VDHsZEH8tDHMZPHMxPGctLHcxIHM1JH81PHs5PHshQH9NXHdRaHtloANp4B8RnHMJEIMVFIchNIdBDINdLIcVUIctTIs5YJMxcJMVmI89kJsdoJc5rIdhyINB9I8d+Pr56XpN0eaBgcruWL92YGuGMANyiGe23FsmUKfHLIffbNP7kKraSSquDYaScfN63TNfNevDeZP/8a//vcPrtdwArhQAuhCY9ghVEmCNDjDBKkyhRnTJWnABMpBFdriJlsy5vtzh8u1BajV9ikktoolNwrB56wRx6w4BojASP1iKG0S6O0DCJ1yOR2TCW32icxlag2lWg33Gj2ESf4kKg50eh5lal41Ok5Vul5Fuo5d3Vi//6iu3dtP//v42ZwLnHzr/HzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAK0AIf8LSUNDUkdCRzEwMTL/AAAHqGFwcGwCIAAAbW50clJHQiBYWVogB9kAAgAZAAsAGgALYWNzcEFQUEwAAAAAYXBwbAAAAAAAAAAAAAAAAAAAAAAAAPbWAAEAAAAA0y1hcHBsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALZGVzYwAAAQgAAABvZHNjbQAAAXgAAAVsY3BydAAABuQAAAA4d3RwdAAABxwAAAAUclhZWgAABzAAAAAUZ1hZWgAAB0QAAAAUYlhZWgAAB1gAAAAUclRSQwAAB2wAAAAOY2hhZAAAB3wAAAAsYlRSQwAAB2wAAAAOZ1RS/0MAAAdsAAAADmRlc2MAAAAAAAAAFEdlbmVyaWMgUkdCIFByb2ZpbGUAAAAAAAAAAAAAABRHZW5lcmljIFJHQiBQcm9maWxlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABtbHVjAAAAAAAAAB4AAAAMc2tTSwAAACgAAAF4aHJIUgAAACgAAAGgY2FFUwAAACQAAAHIcHRCUgAAACYAAAHsdWtVQQAAACoAAAISZnJGVQAAACgAAAI8emhUVwAAABYAAAJkaXRJVAAAACgAAAJ6bmJOTwAAACYAAAKia29LUgAAABYAAP8CyGNzQ1oAAAAiAAAC3mhlSUwAAAAeAAADAGRlREUAAAAsAAADHmh1SFUAAAAoAAADSnN2U0UAAAAmAAAConpoQ04AAAAWAAADcmphSlAAAAAaAAADiHJvUk8AAAAkAAADomVsR1IAAAAiAAADxnB0UE8AAAAmAAAD6G5sTkwAAAAoAAAEDmVzRVMAAAAmAAAD6HRoVEgAAAAkAAAENnRyVFIAAAAiAAAEWmZpRkkAAAAoAAAEfHBsUEwAAAAsAAAEpHJ1UlUAAAAiAAAE0GFyRUcAAAAmAAAE8mVuVVMAAAAmAAAFGGRhREsAAAAuAAAFPgBWAWEAZQBvAGIAZQD/YwBuAP0AIABSAEcAQgAgAHAAcgBvAGYAaQBsAEcAZQBuAGUAcgBpAQ0AawBpACAAUgBHAEIAIABwAHIAbwBmAGkAbABQAGUAcgBmAGkAbAAgAFIARwBCACAAZwBlAG4A6AByAGkAYwBQAGUAcgBmAGkAbAAgAFIARwBCACAARwBlAG4A6QByAGkAYwBvBBcEMAQzBDAEOwRMBD0EOAQ5ACAEPwRABD4ERAQwBDkEOwAgAFIARwBCAFAAcgBvAGYAaQBsACAAZwDpAG4A6QByAGkAcQB1AGUAIABSAFYAQpAadSgAIABSAEcAQgAggnJfaWPPj/AAUAByAG8AZgBp/wBsAG8AIABSAEcAQgAgAGcAZQBuAGUAcgBpAGMAbwBHAGUAbgBlAHIAaQBzAGsAIABSAEcAQgAtAHAAcgBvAGYAaQBsx3y8GAAgAFIARwBCACDVBLhc0wzHfABPAGIAZQBjAG4A/QAgAFIARwBCACAAcAByAG8AZgBpAGwF5AXoBdUF5AXZBdwAIABSAEcAQgAgBdsF3AXcBdkAQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAAUgBHAEIALQBQAHIAbwBmAGkAbADBAGwAdABhAGwA4QBuAG8AcwAgAFIARwBCACAAcAByAG8AZgBpAGxmbpAaACAAUgBHAEIAIGPPj//wZYdO9k4AgiwAIABSAEcAQgAgMNcw7TDVMKEwpDDrAFAAcgBvAGYAaQBsACAAUgBHAEIAIABnAGUAbgBlAHIAaQBjA5MDtQO9A7kDugPMACADwAPBA78DxgOvA7sAIABSAEcAQgBQAGUAcgBmAGkAbAAgAFIARwBCACAAZwBlAG4A6QByAGkAYwBvAEEAbABnAGUAbQBlAGUAbgAgAFIARwBCAC0AcAByAG8AZgBpAGUAbA5CDhsOIw5EDh8OJQ5MACAAUgBHAEIAIA4XDjEOSA4nDkQOGwBHAGUAbgBlAGwAIABSAEcAQgAgAFAAcgBvAGYAaQBsAGkAWQBsAGX/AGkAbgBlAG4AIABSAEcAQgAtAHAAcgBvAGYAaQBpAGwAaQBVAG4AaQB3AGUAcgBzAGEAbABuAHkAIABwAHIAbwBmAGkAbAAgAFIARwBCBB4EMQRJBDgEOQAgBD8EQAQ+BEQEOAQ7BEwAIABSAEcAQgZFBkQGQQAgBioGOQYxBkoGQQAgAFIARwBCACAGJwZEBjkGJwZFAEcAZQBuAGUAcgBpAGMAIABSAEcAQgAgAFAAcgBvAGYAaQBsAGUARwBlAG4AZQByAGUAbAAgAFIARwBCAC0AYgBlAHMAawByAGkAdgBlAGwAcwBldGV4dAAAAABDb3B5cmlnaHQgMjAwrzcgQXBwbGUgSW5jLiwgYWxsIHJpZ2h0cyByZXNlcnZlZC4AWFlaIAAAAAAAAPNSAAEAAAABFs9YWVogAAAAAAAAdE0AAD3uAAAD0FhZWiAAAAAAAABadQAArHMAABc0WFlaIAAAAAAAACgaAAAVnwAAuDZjdXJ2AAAAAAAAAAEBzQAAc2YzMgAAAAAAAQxCAAAF3v//8yYAAAeSAAD9kf//+6L///2jAAAD3AAAwGwALAAAAAAZABgAAAj+AFsJHEiwoMGDCBMqJEgK1KhSojotPGgJ0yZOoUJp8rRqokAmbijl0VMp0yVJi1ih8nhESZY4bSI1msQokapUfSZSEWNGTRk2kBQhIlTIlJ+FVMBYIaNlzaNDhgZZcMTn1B6FTb54eWKkgyBBFzIk8ADoDx6FM4h06WFEAwYOGyogOGDnDh2FLIT8AAIlDJItSRQEeFNnzkQVMIA0mUFDAAAABuTAKTORBI4WPrrkKECgBpo0Y3J4DHFjxw8nUqJcwUJlh0eBH1CseBEDyJAhKV4PHABCxAkTJkboHk7c44ODx3UnJ/jg+HKCGgxGaNW8lYYHFKhTjzC9YHfmxx0KFHTAvbj58wIDAgA7' // jshint ignore:line
|
||||
},
|
||||
{
|
||||
type: 'image/bmp',
|
||||
width: 25,
|
||||
height: 24,
|
||||
base64: 'Qk1WBwAAAAAAADYAAAAoAAAAGQAAABgAAAABABgAAAAAACAHAAATCwAAEwsAAAAAAAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////AP///0c5OUBAQAAAAEY+PkBAQFUrK0BAQEU9PUQ8PEBAQEY6OkRAPEBAOkNDNkY+Pkc9Pf///0M/P0BAQEBAQERAPEY+Pv///wAAAAD///9DPT1DPj7///9CPjxEPj5DPz9CPT1CPzxDQD1EPz1CPj5DPz1CPT1CPjxDPjxHPT1EQDxEPj5DPj5CPz9AQEBDPj5EPj7///8A////Qj87Qj48QTw8Qz89Qj09QT09Qz49Qz09RD49Qz89Qj48Qj87Qz09Qz09Qz48////RD4+////Qz09Qj09RDw8Qj48QTs7////AP///0M8PEM+PUM9PUI8PEM/PUI+PEQ9PUE9PUM9PUM+PEM+PkM9PUI/PEI9PUM+PkNDQ0I9PUU7O0E8PEM+PkI+PkBAQP///wAAAAD///9CPT1DPjxDPj5CPT1HOTn///9AQED///9EPj5CPT9COkoAAP8AAP////////////9EPj5DPj5EPj5CPT1DPj5EPT1GRkb///8A////////Szw8QEBAQjk5QEBA////////////////SkoggIAA////////////////////////REQzVSsr////SUk3Q0ND////////AP///////////////////////wAAAAAAqgBAAAAAAAAAMgoAXBcNaBAKVg4AOwAAAP///0BAv////////0BAQP///////////////wD///8AAABVVVVVVVVVVVX///8AAGYAAAAMAD8XEHobGJAcH5sdJKQcJaMcJqMeIIoYAEgAAAAkJEmAgID///+AgICAgID///////8A////////////////////AAD/////Fw5xHBaRHyOsHyuyHzK4Hze9HzvCHz3EIznAHyyhFCKM////////AAAA////////////////AP///////////////wAAv////x0Xlx0bnCAosR8yth06wRxDyxxIzB1Lyx1X0x5a1B9JzSA6uiIxrP///wAAgP///////////////wD///////////8AAID///////8dIaIgKrYeNLkePsMgRMIqPJ8xLGkuKWMpP5Uha84gctgkWM4lNKAhC5D///8AAID///////////8A////////////MzOZ////HCiqHimrHjS7Hj7DH0TGID+1JSmEMwA9PAAAPAAAL0VjGaLcGpjdI2bFJBuy////QEC/////////////AP///////wAA/wAAgP///x8zsyA1wR09wR9CwxlPzCFUxS1TuhxnxDVmm0whIz0NAC+WuyHL8Ra37Rh84P///wCAv////////////wD///////8AgP////////8eOrsgPcshRcUYPr4tP7BeNVdmMCdeVG9eUmZWJyJIAAA7Z3o02/cq5P4plMn///////8AAP////////8A////////AID/////////HkPFIEPQIU3IHE/GKkmsZExihC4AhSsAeSUAajEjVgAAdnp5d+36cO//SpK2////////AP//////////AP///////wD//////////x5PzSFL1x9QyCRczAd42j5+x5JiX51RKJNKMII9JnQnAI1aUHrN14r6/0y33v///////wAAAP///////wD///////////8AQL////8dVdUeT84iU8smZM8jfdAlaMdeer6iaEuuXRGcVjKYRBWMQyOL1d1r/P8A6vz///8zzMz///////////8A////////////AEC/////IjOqIES+AFTaAGjZAIzheXSTrHBTu3w4w3oct28upEwAwJmNv///ZN7wANbr////Vaqq////////////AP///////////wCA/////1VVqg5Ev3JgoIxogGGDq3ycpNaPBNmRI9COLsF6HrNlIszHv7Td7Wedx////////////////////////wD///////////////////////8AY+/BlHXRhiLXiTDGnGjaoFbmoUfin0TfljDYo3HOx7mfpsL///////8AAP////////////////8A/////////////////////////////////9eu5KVb56BC5aRT5ahb46VW36BV57qG/+Nx////////AAD/////////////////////AP///////////////////////wCA/7+AgP//////////0fO/eu22bfO7dv//tv///////8zMzP///////////////////////////wD/////////////////////////////////qlWqVQD///////////////////+/v4D/qqr///////////////////////////////8A' // jshint ignore:line
|
||||
}
|
||||
];
|
||||
|
||||
suite('MIME type constants', function() {
|
||||
test('JPEG', function() { assert.equal(ImageUtils.JPEG, 'image/jpeg'); });
|
||||
test('PNG', function() { assert.equal(ImageUtils.PNG, 'image/png'); });
|
||||
test('GIF', function() { assert.equal(ImageUtils.GIF, 'image/gif'); });
|
||||
test('BMP', function() { assert.equal(ImageUtils.BMP, 'image/bmp'); });
|
||||
});
|
||||
|
||||
|
||||
suite('getSizeAndType', function() {
|
||||
|
||||
function makeBlobFromString(binaryString, type) {
|
||||
var buffer = new ArrayBuffer(binaryString.length);
|
||||
var bytes = new Uint8Array(buffer);
|
||||
for (var i = 0; i < binaryString.length; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
return new Blob([buffer], { type: type });
|
||||
}
|
||||
|
||||
suiteSetup(function() {
|
||||
// Create blobs for each of the test images
|
||||
testImages.forEach(function(image) {
|
||||
image.blob = makeBlobFromString(atob(image.base64), image.type);
|
||||
});
|
||||
});
|
||||
|
||||
test('rejects string argument', function(done) {
|
||||
ImageUtils.getSizeAndType('data:foo').then(null, function() { done(); });
|
||||
});
|
||||
|
||||
test('rejects numeric argument', function(done) {
|
||||
ImageUtils.getSizeAndType(1).then(null, function() { done(); });
|
||||
});
|
||||
|
||||
test('rejects object argument', function(done) {
|
||||
ImageUtils.getSizeAndType({size:100}).then(null, function() { done(); });
|
||||
});
|
||||
|
||||
// Test that we can get the width and height of each test image
|
||||
testImages.forEach(function(image) {
|
||||
test(image.type, function(done) {
|
||||
ImageUtils.getSizeAndType(image.blob).then(function resolve(data) {
|
||||
try {
|
||||
assert.equal(data.type, image.type);
|
||||
assert.equal(data.width, image.width);
|
||||
assert.equal(data.height, image.height);
|
||||
done();
|
||||
}
|
||||
catch (e) {
|
||||
done(e);
|
||||
}
|
||||
}, function reject(e) { done(new Error(e)); });
|
||||
});
|
||||
});
|
||||
|
||||
// Test that we can get the size of a JPEG file even if it has a large
|
||||
// EXIF metadata segment before the segments that hold image size.
|
||||
test('jpeg with large EXIF segments', function(done) {
|
||||
// Splice some fake EXIF data in at the start of the JPEG file
|
||||
// to force the image size data to be beyond the 32kb boundary
|
||||
var jpeg = testImages[0];
|
||||
var exif = new ArrayBuffer(40 * 1024);
|
||||
var view = new DataView(exif);
|
||||
view.setUint8(0, 0xFF);
|
||||
view.setUint8(1, 0xE0); // APP0 segment for EXIF
|
||||
view.setUint16(2, (40 * 1024) - 2);
|
||||
var newblob = new Blob([jpeg.blob.slice(0, 2),
|
||||
exif, exif, jpeg.blob.slice(2)],
|
||||
{ type: 'image/jpeg' });
|
||||
ImageUtils.getSizeAndType(newblob).then(
|
||||
function resolve(data) {
|
||||
try {
|
||||
assert.equal(data.type, jpeg.type);
|
||||
assert.equal(data.width, jpeg.width);
|
||||
assert.equal(data.height, jpeg.height);
|
||||
done();
|
||||
}
|
||||
catch (e) {
|
||||
done(e);
|
||||
}
|
||||
},
|
||||
function reject(e) {
|
||||
done(new Error(e));
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('reject zero-length blob', function(done) {
|
||||
var empty = new Blob([''], { type: 'image/jpeg' });
|
||||
ImageUtils.getSizeAndType(empty).then(
|
||||
function resolve() {
|
||||
done(new Error('failed to reject an empty blob'));
|
||||
},
|
||||
function reject() {
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('reject any blob < 16 bytes', function(done) {
|
||||
var empty = new Blob(['0123456789012345'], { type: 'image/jpeg' });
|
||||
ImageUtils.getSizeAndType(empty).then(
|
||||
function resolve() {
|
||||
done(new Error('failed to reject a short blob'));
|
||||
},
|
||||
function reject() {
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
// Test that if we truncate the test images we can't get their size
|
||||
testImages.forEach(function(image) {
|
||||
// We can't meaningfully test truncated gif files
|
||||
if (image.type === 'image/gif') {
|
||||
return;
|
||||
}
|
||||
|
||||
test('reject truncated ' + image.type, function(done) {
|
||||
var truncated = image.blob.slice(0, 20);
|
||||
ImageUtils.getSizeAndType(truncated).then(
|
||||
function resolve(data) {
|
||||
done(new Error('failed to reject a truncated blob'));
|
||||
},
|
||||
function reject() {
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Here are some files that are not image files and should be rejected
|
||||
var bogusFiles = [
|
||||
'This is a text file not an image file. It does not have a size',
|
||||
'GIF85a This is not a gif: the header is not correct',
|
||||
'\xff\xd8 Not a jpeg file because it does not have segments',
|
||||
'BM not really a BMP file since. File length data is not correct',
|
||||
'\x89PNG\r\n\x1a\n Not a PNG file: does not have an ihdr segment'
|
||||
];
|
||||
|
||||
bogusFiles.forEach(function(file, index) {
|
||||
test('Reject bogus file ' + index, function(done) {
|
||||
var bogus = makeBlobFromString(file, 'text/plain');
|
||||
ImageUtils.getSizeAndType(bogus).then(
|
||||
function resolve(data) {
|
||||
done(new Error('failed to reject bogus text:' + file));
|
||||
},
|
||||
function reject() {
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
suite('Downsample tests', function() {
|
||||
test('Check API', function() {
|
||||
assert.typeOf(ImageUtils.Downsample, 'object');
|
||||
assert.typeOf(ImageUtils.Downsample.sizeAtLeast, 'function');
|
||||
assert.typeOf(ImageUtils.Downsample.sizeNoMoreThan, 'function');
|
||||
assert.typeOf(ImageUtils.Downsample.areaAtLeast, 'function');
|
||||
assert.typeOf(ImageUtils.Downsample.areaNoMoreThan, 'function');
|
||||
assert.typeOf(ImageUtils.Downsample.MAX_SIZE_REDUCTION, 'number');
|
||||
assert.typeOf(ImageUtils.Downsample.MAX_AREA_REDUCTION, 'number');
|
||||
assert.typeOf(ImageUtils.Downsample.NONE, 'object');
|
||||
assert.equal(ImageUtils.Downsample.NONE.dimensionScale, 1);
|
||||
assert.equal(ImageUtils.Downsample.NONE.areaScale, 1);
|
||||
assert.equal(ImageUtils.Downsample.NONE.toString(), '');
|
||||
});
|
||||
|
||||
var scales = [
|
||||
10, 2, 1,
|
||||
1 / 8, 2 / 8, 3 / 8, 4 / 8, 5 / 8, 6 / 8, 7 / 8,
|
||||
0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1,
|
||||
0.09, 0.08, 0.07, 0.06, 0.05, 0.04, 0.03, 0.02, 0.01
|
||||
];
|
||||
|
||||
// This is the same function we use in downsample.js
|
||||
function round(x) {
|
||||
return Math.round(x * 100) / 100;
|
||||
}
|
||||
|
||||
test('sizeAtLeast', function() {
|
||||
var min = 1 / ImageUtils.Downsample.MAX_SIZE_REDUCTION;
|
||||
scales.forEach(function(scale) {
|
||||
var ds = ImageUtils.Downsample.sizeAtLeast(scale);
|
||||
assert.operator(ds.dimensionScale, '<=', round(Math.max(scale, min)));
|
||||
assert.equal(ds.scale(100), ds.dimensionScale * 100);
|
||||
assert.ok(ds === ImageUtils.Downsample.NONE ||
|
||||
ds.toString()[0] === '#');
|
||||
});
|
||||
});
|
||||
|
||||
test('areaAtLeast', function() {
|
||||
var min = 1 / ImageUtils.Downsample.MAX_AREA_REDUCTION;
|
||||
scales.forEach(function(scale) {
|
||||
var ds = ImageUtils.Downsample.areaAtLeast(scale);
|
||||
assert.operator(ds.areaScale, '<=', round(Math.max(scale, min)));
|
||||
|
||||
ds = ImageUtils.Downsample.areaAtLeast(scale * scale);
|
||||
assert.operator(ds.areaScale, '<=',
|
||||
round(Math.max(scale * scale, min)));
|
||||
assert.equal(ds.scale(100), ds.dimensionScale * 100);
|
||||
assert.ok(ds === ImageUtils.Downsample.NONE ||
|
||||
ds.toString()[0] === '#');
|
||||
});
|
||||
});
|
||||
|
||||
test('sizeNoMoreThan', function() {
|
||||
scales.forEach(function(scale) {
|
||||
var ds = ImageUtils.Downsample.sizeNoMoreThan(scale);
|
||||
assert.operator(ds.dimensionScale, '>=', round(Math.min(scale, 1)));
|
||||
assert.equal(ds.scale(100), ds.dimensionScale * 100);
|
||||
assert.ok(ds === ImageUtils.Downsample.NONE ||
|
||||
ds.toString()[0] === '#');
|
||||
});
|
||||
});
|
||||
|
||||
test('areaNoMoreThan', function() {
|
||||
scales.forEach(function(scale) {
|
||||
var ds = ImageUtils.Downsample.areaNoMoreThan(scale);
|
||||
assert.operator(ds.areaScale, '>=', round(Math.min(scale, 1)));
|
||||
|
||||
ds = ImageUtils.Downsample.areaNoMoreThan(scale * scale);
|
||||
assert.operator(ds.areaScale, '>=', round(Math.min(scale * scale, 1)));
|
||||
assert.equal(ds.scale(100), ds.dimensionScale * 100);
|
||||
assert.ok(ds === ImageUtils.Downsample.NONE ||
|
||||
ds.toString()[0] === '#');
|
||||
});
|
||||
});
|
||||
|
||||
test('max reduction amounts', function() {
|
||||
var ds = ImageUtils.Downsample.sizeNoMoreThan(0);
|
||||
assert.equal(ds.dimensionScale,
|
||||
round(1 / ImageUtils.Downsample.MAX_SIZE_REDUCTION));
|
||||
assert.equal(ds.areaScale,
|
||||
round(1 / ImageUtils.Downsample.MAX_AREA_REDUCTION));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function runResizeAndCropToCoverTests(imageWidth, imageHeight, imageType,
|
||||
encoderOptions) {
|
||||
var suitename = 'resizeAndCropToCover ' + imageType + ' ' +
|
||||
imageWidth + 'x' + imageHeight;
|
||||
suite(suitename, function() {
|
||||
const W = imageWidth, H = imageHeight; // The size of the test image
|
||||
const inputImageType = imageType;
|
||||
const encoderOptions = encoderOptions;
|
||||
|
||||
suiteSetup(function(done) {
|
||||
// We begin by creating a special image where each pixel value
|
||||
// encodes the coordinates of that pixel. This allows us to inspect
|
||||
// the pixels in the output image to verify that cropping
|
||||
// was done correctly.
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = W;
|
||||
canvas.height = H;
|
||||
var context = canvas.getContext('2d');
|
||||
var imageData = context.createImageData(W, H);
|
||||
var pixels = imageData.data;
|
||||
|
||||
for (var x = 0; x < canvas.width; x++) {
|
||||
for (var y = 0; y < canvas.height; y++) {
|
||||
var offset = (x + y * canvas.width) * 4;
|
||||
pixels[offset] = x; // Encode x coordinate as red value
|
||||
pixels[offset + 1] = y; // Encode y coodrdinate as green value
|
||||
imageData.data[offset + 3] = 255; // Make it opaque
|
||||
}
|
||||
}
|
||||
|
||||
context.putImageData(imageData, 0, 0);
|
||||
|
||||
// Create an encoded version of the input image
|
||||
var self = this;
|
||||
canvas.toBlob(function(blob) {
|
||||
self.inputBlob = blob;
|
||||
done();
|
||||
}, inputImageType);
|
||||
});
|
||||
|
||||
// Decode the image blob and verify that its size is as expected and
|
||||
// that its upper-left pixel has red and green values near r0 and g0
|
||||
// and that its lower-right pixel has values near r1 and g1. We do a
|
||||
// fuzzy match because jpeg is a lossy format and because sometimes
|
||||
// we're working with downsampled or upscaled images.
|
||||
function verifyImage(blob, expectedWidth, expectedHeight,
|
||||
r0, g0, r1, g1, tolerance, callback) {
|
||||
var image = new Image();
|
||||
var url = URL.createObjectURL(blob);
|
||||
image.src = url;
|
||||
image.onerror = function() {
|
||||
callback(Error('failed to load image from blob'));
|
||||
};
|
||||
|
||||
image.onload = function() {
|
||||
try {
|
||||
assert.equal(image.width, expectedWidth, 'image width');
|
||||
assert.equal(image.height, expectedHeight, 'image height');
|
||||
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = image.width;
|
||||
canvas.height = image.height;
|
||||
var context = canvas.getContext('2d');
|
||||
context.drawImage(image, 0, 0);
|
||||
var imageData = context.getImageData(0, 0,
|
||||
canvas.width, canvas.height);
|
||||
var pixels = imageData.data;
|
||||
|
||||
isNear(pixels[0], r0, 'red component of upper-left');
|
||||
isNear(pixels[1], g0, 'green component of upper-left');
|
||||
|
||||
var offset = 4 * (image.width * image.height - 1);
|
||||
isNear(pixels[offset], r1,
|
||||
'red component of lower right');
|
||||
isNear(pixels[offset + 1], g1,
|
||||
'green component of lower right');
|
||||
|
||||
image.src = '';
|
||||
URL.revokeObjectURL(url);
|
||||
canvas.width = 0;
|
||||
callback();
|
||||
}
|
||||
catch (e) {
|
||||
callback(e);
|
||||
}
|
||||
};
|
||||
|
||||
// Return true if x is close enough to the target
|
||||
function isNear(x, target, message) {
|
||||
if (target - tolerance <= x && x <= target + tolerance) {
|
||||
return;
|
||||
}
|
||||
throw Error(message + ' ' + x + ' is not near ' + target);
|
||||
}
|
||||
}
|
||||
|
||||
test('test images created', function(done) {
|
||||
assert.instanceOf(this.inputBlob, Blob, 'got Blob');
|
||||
assert.equal(this.inputBlob.type, inputImageType);
|
||||
verifyImage(this.inputBlob, W, H, 0, 0, W - 1, H - 1, 4, done);
|
||||
});
|
||||
|
||||
function onResolve(value) {
|
||||
throw Error('Must be rejected');
|
||||
}
|
||||
|
||||
test('throw for invalid input image', function(done) {
|
||||
|
||||
function checkNotBlob(promise) {
|
||||
return promise.then(
|
||||
onResolve,
|
||||
(err) => { assert.equal(err.name, 'TypeError'); }
|
||||
);
|
||||
}
|
||||
|
||||
Promise.all([
|
||||
checkNotBlob(ImageUtils.resizeAndCropToCover(null, 1, 10)),
|
||||
checkNotBlob(ImageUtils.resizeAndCropToCover('not a blob', 1, 10))
|
||||
]).then(() => done(), done);
|
||||
|
||||
});
|
||||
|
||||
test('throw for invalid input dimension', function(done) {
|
||||
|
||||
function ensureReject(promise) {
|
||||
return promise.then(
|
||||
onResolve,
|
||||
(err) => { assert.equal(err.message, 'invalid output dimensions'); }
|
||||
);
|
||||
}
|
||||
|
||||
Promise.all([
|
||||
ensureReject(ImageUtils.resizeAndCropToCover(this.inputBlob)),
|
||||
ensureReject(ImageUtils.resizeAndCropToCover(this.inputBlob, 0, 10)),
|
||||
ensureReject(ImageUtils.resizeAndCropToCover(this.inputBlob, 10, 0)),
|
||||
ensureReject(ImageUtils.resizeAndCropToCover(this.inputBlob,
|
||||
-10, 10)),
|
||||
ensureReject(ImageUtils.resizeAndCropToCover(this.inputBlob,
|
||||
10, -10)),
|
||||
ensureReject(ImageUtils.resizeAndCropToCover(this.inputBlob,
|
||||
Infinity, 10)),
|
||||
ensureReject(ImageUtils.resizeAndCropToCover(this.inputBlob,
|
||||
10, Infinity)),
|
||||
ensureReject(ImageUtils.resizeAndCropToCover(this.inputBlob, {}, 10)),
|
||||
ensureReject(ImageUtils.resizeAndCropToCover(this.inputBlob, 10, {}))
|
||||
]).then(() => done(), done);
|
||||
});
|
||||
|
||||
// Verify that if the output size is the same as the input size then
|
||||
// the output blob is the same as the input blob
|
||||
test('input returned if no resize needed', function(done) {
|
||||
var self = this;
|
||||
ImageUtils.resizeAndCropToCover(this.inputBlob, W, H).then(
|
||||
function resolve(outputBlob) {
|
||||
try {
|
||||
assert.equal(self.inputBlob, outputBlob);
|
||||
done();
|
||||
}
|
||||
catch (ex) {
|
||||
done(ex);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('unspecified output type', function(done) {
|
||||
var self = this;
|
||||
ImageUtils.resizeAndCropToCover(this.inputBlob, 10, 10)
|
||||
.then(function resolve(outputBlob) {
|
||||
assert.equal(outputBlob.type, self.inputBlob.type);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('jpeg output type with encondig options', function(done) {
|
||||
ImageUtils.resizeAndCropToCover(this.inputBlob, 10, 10, ImageUtils.JPEG,
|
||||
encoderOptions)
|
||||
.then(function resolve(outputBlob) {
|
||||
assert.equal(outputBlob.type, ImageUtils.JPEG);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('png output type', function(done) {
|
||||
ImageUtils.resizeAndCropToCover(this.inputBlob, 10, 10, ImageUtils.PNG)
|
||||
.then(function resolve(outputBlob) {
|
||||
assert.equal(outputBlob.type, ImageUtils.PNG);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('shrink no crop', function(done) {
|
||||
ImageUtils.resizeAndCropToCover(this.inputBlob, W / 3, H / 3)
|
||||
.then(function resolve(outputBlob) {
|
||||
verifyImage(outputBlob, Math.round(W / 3), Math.round(H / 3),
|
||||
0, 0, W, H, 4, done);
|
||||
});
|
||||
});
|
||||
|
||||
test('enlarge no crop', function(done) {
|
||||
ImageUtils.resizeAndCropToCover(this.inputBlob, W * 3, H * 3)
|
||||
.then(function resolve(outputBlob) {
|
||||
verifyImage(outputBlob, W * 3, H * 3, 0, 0, W, H, 4, done);
|
||||
});
|
||||
});
|
||||
|
||||
var delta = 100;
|
||||
|
||||
test('reduce height', function(done) {
|
||||
ImageUtils.resizeAndCropToCover(this.inputBlob, W, H - delta)
|
||||
.then(function resolve(outputBlob) {
|
||||
verifyImage(outputBlob, W, H - delta,
|
||||
0, delta / 2, W, H - delta / 2, 4, done);
|
||||
});
|
||||
});
|
||||
|
||||
test('reduce width', function(done) {
|
||||
ImageUtils.resizeAndCropToCover(this.inputBlob, W - delta, H)
|
||||
.then(function resolve(outputBlob) {
|
||||
verifyImage(outputBlob, W - delta, H,
|
||||
delta / 2, 0, W - delta / 2, H, 4, done);
|
||||
});
|
||||
});
|
||||
|
||||
test('enlarge height', function(done) {
|
||||
ImageUtils.resizeAndCropToCover(this.inputBlob, W, H + delta)
|
||||
.then(function resolve(outputBlob) {
|
||||
var ratio = (H + delta) / H;
|
||||
var margin = ((ratio - 1) * W) / (2 * ratio);
|
||||
verifyImage(outputBlob, W, H + delta,
|
||||
margin, 0, W - margin, H,
|
||||
4, done);
|
||||
});
|
||||
});
|
||||
|
||||
test('enlarge width', function(done) {
|
||||
ImageUtils.resizeAndCropToCover(this.inputBlob, W + delta, H)
|
||||
.then(function resolve(outputBlob) {
|
||||
var ratio = (W + delta) / W;
|
||||
var margin = ((ratio - 1) * H) / (2 * ratio);
|
||||
verifyImage(outputBlob, W + delta, H,
|
||||
0, margin, W, H - margin,
|
||||
4, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('#-moz-samplesize', function() {
|
||||
var spy;
|
||||
setup(function() {
|
||||
spy = this.sinon.spy(ImageUtils.Downsample, 'sizeNoMoreThan');
|
||||
});
|
||||
|
||||
function checkResolve(promise, checkValue) {
|
||||
return promise.then(
|
||||
function resolve(outputBlob) {
|
||||
sinon.assert.calledOnce(spy);
|
||||
assert.equal(spy.returnValues[0].toString(), checkValue);
|
||||
},
|
||||
function reject(outputBlob) {
|
||||
throw Error('Must be resolved');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (inputImageType === 'image/jpeg') {
|
||||
test('uses it at half size', function(done) {
|
||||
checkResolve(ImageUtils.resizeAndCropToCover(
|
||||
this.inputBlob, W / 2, H / 2),
|
||||
'#-moz-samplesize=2').then(done,done);
|
||||
});
|
||||
|
||||
test('uses it at third size', function(done) {
|
||||
checkResolve(ImageUtils.resizeAndCropToCover(
|
||||
this.inputBlob, W / 3, H / 3),
|
||||
'#-moz-samplesize=3').then(done,done);
|
||||
});
|
||||
test('uses it at fifth size', function(done) {
|
||||
checkResolve(ImageUtils.resizeAndCropToCover(
|
||||
this.inputBlob, W / 5, H / 5),
|
||||
'#-moz-samplesize=4').then(done,done);
|
||||
});
|
||||
|
||||
test('does not use at three quarters size', function(done) {
|
||||
checkResolve(ImageUtils.resizeAndCropToCover(
|
||||
this.inputBlob, W * 0.75, H * 0.75),
|
||||
'').then(done,done);
|
||||
});
|
||||
}
|
||||
else {
|
||||
test('does not use for non-jpeg', function(done) {
|
||||
ImageUtils.resizeAndCropToCover(this.inputBlob, W / 2, H / 2)
|
||||
.then(() => sinon.assert.notCalled(spy)).then(done, done);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// We run the resizeAndCropToCover test suite six times for for three
|
||||
// different input image sizes and two different input image types
|
||||
([
|
||||
[240, 180], // 4x3
|
||||
[180, 240], // 3x4
|
||||
[250, 250] // square
|
||||
]).forEach(function(size) {
|
||||
runResizeAndCropToCoverTests(size[0], size[1], 'image/jpeg', 0.95);
|
||||
runResizeAndCropToCoverTests(size[0], size[1], 'image/png');
|
||||
});
|
||||
});
|
||||
139
gaia/apps/sharedtest/test/unit/input_parser_test.js
Executable file
139
gaia/apps/sharedtest/test/unit/input_parser_test.js
Executable file
|
|
@ -0,0 +1,139 @@
|
|||
/* global InputParser */
|
||||
|
||||
require('/shared/js/input_parser.js');
|
||||
|
||||
suite('input_parser', function() {
|
||||
'use strict';
|
||||
var subject;
|
||||
|
||||
setup(function() {
|
||||
subject = InputParser;
|
||||
});
|
||||
|
||||
suite('#importTime', function() {
|
||||
var input = [
|
||||
'23:20',
|
||||
'23:20:15',
|
||||
'17:39:57.02',
|
||||
'01:01:01',
|
||||
'0:0',
|
||||
''
|
||||
];
|
||||
|
||||
var expected = [
|
||||
{ hours: 23, minutes: 20, seconds: 0 },
|
||||
{ hours: 23, minutes: 20, seconds: 15 },
|
||||
{ hours: 17, minutes: 39, seconds: 57 },
|
||||
{ hours: 1, minutes: 1, seconds: 1 },
|
||||
{ hours: 0, minutes: 0, seconds: 0 },
|
||||
{ hours: 0, minutes: 0, seconds: 0 }
|
||||
];
|
||||
|
||||
input.forEach(function(item, idx) {
|
||||
test('parse: "' + item + '"', function() {
|
||||
var expect = expected[idx];
|
||||
assert.deepEqual(
|
||||
subject.importTime(item),
|
||||
expect
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('#exportTime', function() {
|
||||
var input = [
|
||||
new Date(2012, 0, 1, 7, 30, 45),
|
||||
new Date(2012, 0, 1)
|
||||
];
|
||||
|
||||
var expected = [
|
||||
'07:30:45',
|
||||
'00:00:00'
|
||||
];
|
||||
|
||||
input.forEach(function(item, idx) {
|
||||
test('parse: "' + item + '"', function() {
|
||||
var expect = expected[idx];
|
||||
assert.deepEqual(
|
||||
subject.exportTime(item),
|
||||
expect
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('#importDate', function() {
|
||||
var input = [
|
||||
'1997-12-01',
|
||||
'1997-01-31',
|
||||
'2011-08-01'
|
||||
];
|
||||
|
||||
var expected = [
|
||||
{ year: 1997, month: 11, date: 1 },
|
||||
{ year: 1997, month: 0, date: 31 },
|
||||
{ year: 2011, month: 7, date: 1 }
|
||||
];
|
||||
|
||||
input.forEach(function(item, idx) {
|
||||
test('parse: "' + item + '"', function() {
|
||||
var expect = expected[idx];
|
||||
assert.deepEqual(
|
||||
subject.importDate(item),
|
||||
expect
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('#exportDate', function() {
|
||||
var input = [
|
||||
new Date(1997, 11, 1),
|
||||
new Date(1997, 0, 31),
|
||||
new Date(2011, 7, 1)
|
||||
];
|
||||
|
||||
var expected = [
|
||||
'1997-12-01',
|
||||
'1997-01-31',
|
||||
'2011-08-01'
|
||||
];
|
||||
|
||||
input.forEach(function(item, idx) {
|
||||
test('parse: "' + item + '"', function() {
|
||||
var expect = expected[idx];
|
||||
assert.deepEqual(
|
||||
subject.exportDate(item),
|
||||
expect
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
test('#formatInputDate - with value < 10', function() {
|
||||
var newStart = new Date(2011, 7, 1);
|
||||
|
||||
var startDate = subject.exportDate(newStart);
|
||||
var startTime = subject.exportTime(newStart);
|
||||
|
||||
assert.deepEqual(
|
||||
subject.formatInputDate(startDate, startTime),
|
||||
newStart
|
||||
);
|
||||
});
|
||||
|
||||
test('#formatInputDate', function() {
|
||||
var expected = new Date(
|
||||
2012, 0, 31, 7, 30, 45
|
||||
);
|
||||
|
||||
var actual = subject.formatInputDate(
|
||||
subject.exportDate(expected),
|
||||
subject.exportTime(expected)
|
||||
);
|
||||
|
||||
assert.deepEqual(expected, actual);
|
||||
});
|
||||
|
||||
});
|
||||
290
gaia/apps/sharedtest/test/unit/intl_helper_test.js
Executable file
290
gaia/apps/sharedtest/test/unit/intl_helper_test.js
Executable file
|
|
@ -0,0 +1,290 @@
|
|||
'use strict';
|
||||
|
||||
/* global IntlHelper */
|
||||
|
||||
require('/shared/js/intl_helper.js');
|
||||
|
||||
suite('IntlHelper', function() {
|
||||
var realMozL10n;
|
||||
|
||||
beforeEach(function() {
|
||||
IntlHelper._resetObjectCache();
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
realMozL10n = navigator.mozL10n;
|
||||
navigator.mozL10n = undefined;
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
navigator.mozL10n = realMozL10n;
|
||||
});
|
||||
|
||||
suite('define', function() {
|
||||
test('should work for a known type', function() {
|
||||
assert.doesNotThrow(function() {
|
||||
IntlHelper.define('shortDate', 'datetime', {
|
||||
'month': 'numeric',
|
||||
'year': '2-digit'
|
||||
});
|
||||
});
|
||||
});
|
||||
test('should throw for an unknown type', function() {
|
||||
assert.throws(function() {
|
||||
IntlHelper.define('shortDate', 'unknownType', {
|
||||
'month': 'numeric',
|
||||
'year': '2-digit'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('get', function() {
|
||||
setup(function() {
|
||||
IntlHelper.define('shortDate', 'datetime', {
|
||||
'month': 'numeric',
|
||||
'year': '2-digit'
|
||||
});
|
||||
});
|
||||
|
||||
test('should work for a defined name', function() {
|
||||
assert.doesNotThrow(function() {
|
||||
IntlHelper.get('shortDate');
|
||||
});
|
||||
});
|
||||
test('should return a formatter with the right type', function() {
|
||||
var formatter = IntlHelper.get('shortDate');
|
||||
assert.isTrue(formatter instanceof Intl.DateTimeFormat);
|
||||
});
|
||||
test('should return a formatter with the right options', function() {
|
||||
var formatter = IntlHelper.get('shortDate');
|
||||
var options = formatter.resolvedOptions();
|
||||
assert.equal(options.month, 'numeric');
|
||||
assert.equal(options.year, '2-digit');
|
||||
assert.strictEqual(options.hour, undefined);
|
||||
assert.strictEqual(options.hour12, undefined);
|
||||
});
|
||||
test('should throw for an undefined name', function() {
|
||||
assert.throws(function() {
|
||||
IntlHelper.get('unknownName');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('observe', function() {
|
||||
setup(function() {
|
||||
IntlHelper.define('shortDate', 'datetime', {
|
||||
'month': 'numeric',
|
||||
'year': '2-digit'
|
||||
});
|
||||
});
|
||||
|
||||
test('should work for a defined name', function() {
|
||||
assert.doesNotThrow(function() {
|
||||
IntlHelper.observe('shortDate', () => {});
|
||||
});
|
||||
});
|
||||
test('should throw for an undefined name', function() {
|
||||
assert.throws(function() {
|
||||
IntlHelper.observe('undefinedName', () => {});
|
||||
});
|
||||
});
|
||||
test('should throw if one of the names is undefined', function() {
|
||||
assert.throws(function() {
|
||||
IntlHelper.observe(['shortDate', 'undefinedName'], () => {});
|
||||
});
|
||||
});
|
||||
test('should fire when affected', function(done) {
|
||||
IntlHelper.observe('shortDate', done);
|
||||
|
||||
IntlHelper.handleEvent({type: 'languagechange'});
|
||||
});
|
||||
test('should fire once even when defined twice', function() {
|
||||
var firedCount = 0;
|
||||
function test() {
|
||||
firedCount++;
|
||||
}
|
||||
IntlHelper.observe('shortDate', test);
|
||||
IntlHelper.observe('shortDate', test);
|
||||
|
||||
IntlHelper.handleEvent({type: 'languagechange'});
|
||||
|
||||
assert.equal(firedCount, 1);
|
||||
});
|
||||
test('should next event even when the the first one errors', function() {
|
||||
var firedCount = 0;
|
||||
function test() {
|
||||
var x = unknownVariable + 1; // jshint ignore:line
|
||||
firedCount++;
|
||||
}
|
||||
function test2() {
|
||||
firedCount++;
|
||||
}
|
||||
IntlHelper.observe('shortDate', test);
|
||||
IntlHelper.observe('shortDate', test2);
|
||||
|
||||
// be nice to our log, avoid spamming our error
|
||||
sinon.stub(console, 'error');
|
||||
IntlHelper.handleEvent({type: 'languagechange'});
|
||||
console.error.restore();
|
||||
|
||||
assert.equal(firedCount, 1);
|
||||
});
|
||||
});
|
||||
|
||||
suite('unobserve', function() {
|
||||
setup(function() {
|
||||
IntlHelper.define('shortDate', 'datetime', {
|
||||
'month': 'numeric',
|
||||
'year': '2-digit'
|
||||
});
|
||||
});
|
||||
|
||||
test('should not fire after unobserved', function() {
|
||||
var wasFired = false;
|
||||
|
||||
function test() {
|
||||
wasFired = true;
|
||||
}
|
||||
IntlHelper.observe('shortDate', test);
|
||||
IntlHelper.unobserve('shortDate', test);
|
||||
IntlHelper.handleEvent({type: 'languagechange'});
|
||||
|
||||
assert.isFalse(wasFired);
|
||||
});
|
||||
});
|
||||
|
||||
suite('DateTime', function() {
|
||||
setup(function() {
|
||||
IntlHelper.define('shortTime', 'datetime', {
|
||||
'hour': 'numeric',
|
||||
'minute': 'numeric'
|
||||
});
|
||||
|
||||
IntlHelper.define('shortDate', 'datetime', {
|
||||
'month': 'numeric',
|
||||
'year': '2-digit'
|
||||
});
|
||||
});
|
||||
|
||||
test('should only fire hour formatter on timeformatchange', function() {
|
||||
var setTimeFired = false;
|
||||
var setDateFired = false;
|
||||
function setTime() {
|
||||
setTimeFired = true;
|
||||
}
|
||||
|
||||
function setDate() {
|
||||
setDateFired = true;
|
||||
}
|
||||
|
||||
IntlHelper.observe('shortTime', setTime);
|
||||
IntlHelper.observe('shortDate', setDate);
|
||||
|
||||
IntlHelper.handleEvent({type: 'timeformatchange'});
|
||||
|
||||
assert.isTrue(setTimeFired);
|
||||
assert.isFalse(setDateFired);
|
||||
});
|
||||
test('should fire if at least one name is affected', function() {
|
||||
var setValuesFired = false;
|
||||
function setValues() {
|
||||
setValuesFired = true;
|
||||
}
|
||||
|
||||
IntlHelper.observe(['shortDate', 'shortTime'], setValues);
|
||||
IntlHelper.handleEvent({type: 'timeformatchange'});
|
||||
|
||||
assert.isTrue(setValuesFired);
|
||||
});
|
||||
test('should not fire if timeformatchange and no hour', function() {
|
||||
var setValuesFired = false;
|
||||
function setValues() {
|
||||
setValuesFired = true;
|
||||
}
|
||||
|
||||
IntlHelper.observe('shortDate', setValues);
|
||||
IntlHelper.handleEvent({type: 'timeformatchange'});
|
||||
|
||||
assert.isFalse(setValuesFired);
|
||||
});
|
||||
test('should add hour12 to options', function() {
|
||||
var expectedValue = false;
|
||||
function setValues() {
|
||||
var formatter = IntlHelper.get('shortTime');
|
||||
assert.equal(formatter.resolvedOptions().hour12, expectedValue);
|
||||
}
|
||||
|
||||
IntlHelper.observe('shortTime', setValues);
|
||||
navigator.mozHour12 = false;
|
||||
IntlHelper.handleEvent({type: 'timeformatchange'});
|
||||
|
||||
navigator.mozHour12 = true;
|
||||
expectedValue = true;
|
||||
IntlHelper.handleEvent({type: 'timeformatchange'});
|
||||
});
|
||||
});
|
||||
|
||||
suite('Number', function() {
|
||||
setup(function() {
|
||||
IntlHelper.define('downloadPercent', 'number', {
|
||||
'style': 'percent',
|
||||
});
|
||||
});
|
||||
|
||||
test('should fire formatter on languagechange', function() {
|
||||
var setValuesFired = false;
|
||||
function setValues() {
|
||||
setValuesFired = true;
|
||||
}
|
||||
|
||||
IntlHelper.observe('downloadPercent', setValues);
|
||||
IntlHelper.handleEvent({type: 'languagechange'});
|
||||
|
||||
assert.isTrue(setValuesFired);
|
||||
});
|
||||
test('should not fire formatter on timeformatchange', function() {
|
||||
var setValuesFired = false;
|
||||
function setValues() {
|
||||
setValuesFired = true;
|
||||
}
|
||||
|
||||
IntlHelper.observe('downloadPercent', setValues);
|
||||
IntlHelper.handleEvent({type: 'timeformatchange'});
|
||||
|
||||
assert.isFalse(setValuesFired);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Collator', function() {
|
||||
setup(function() {
|
||||
IntlHelper.define('timezoneSorter', 'collator', {
|
||||
'usage': 'sort',
|
||||
'sensitivity': 'variant',
|
||||
});
|
||||
});
|
||||
|
||||
test('should fire formatter on languagechange', function() {
|
||||
var setValuesFired = false;
|
||||
function setValues() {
|
||||
setValuesFired = true;
|
||||
}
|
||||
|
||||
IntlHelper.observe('timezoneSorter', setValues);
|
||||
IntlHelper.handleEvent({type: 'languagechange'});
|
||||
|
||||
assert.isTrue(setValuesFired);
|
||||
});
|
||||
test('should not fire formatter on timeformatchange', function() {
|
||||
var setValuesFired = false;
|
||||
function setValues() {
|
||||
setValuesFired = true;
|
||||
}
|
||||
|
||||
IntlHelper.observe('timezoneSorter', setValues);
|
||||
IntlHelper.handleEvent({type: 'timeformatchange'});
|
||||
|
||||
assert.isFalse(setValuesFired);
|
||||
});
|
||||
});
|
||||
});
|
||||
132
gaia/apps/sharedtest/test/unit/l10n/bindings/bootstrap_test.js
vendored
Executable file
132
gaia/apps/sharedtest/test/unit/l10n/bindings/bootstrap_test.js
vendored
Executable file
|
|
@ -0,0 +1,132 @@
|
|||
'use strict';
|
||||
|
||||
suite('L10n bootstrap', function() {
|
||||
suite('buildLocaleList', function() {
|
||||
var buildLocaleList,
|
||||
defaultLocale,
|
||||
realGaiaVersion;
|
||||
|
||||
suiteSetup(function() {
|
||||
var L10n = navigator.mozL10n._getInternalAPI();
|
||||
defaultLocale = navigator.mozL10n.ctx.defaultLocale;
|
||||
buildLocaleList = L10n.buildLocaleList.bind(navigator.mozL10n);
|
||||
realGaiaVersion = navigator.mozL10n._config.appVersion;
|
||||
navigator.mozL10n._config.appVersion = '2.2';
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
navigator.mozL10n._config.appVersion = realGaiaVersion;
|
||||
realGaiaVersion = undefined;
|
||||
defaultLocale = undefined;
|
||||
buildLocaleList = undefined;
|
||||
});
|
||||
|
||||
test('no meta, no extra', function() {
|
||||
var meta = {};
|
||||
var res = buildLocaleList(meta);
|
||||
|
||||
assert.equal(res[0], defaultLocale);
|
||||
var obj = {};
|
||||
obj[defaultLocale] = 'app';
|
||||
assert.deepEqual(res[1], obj);
|
||||
});
|
||||
|
||||
test('meta with just defaultLanguage, no extra', function() {
|
||||
var meta = {defaultLanguage: 'ab-CD'};
|
||||
var res = buildLocaleList(meta);
|
||||
|
||||
assert.equal(res[0], 'ab-CD');
|
||||
assert.deepEqual(res[1], {'ab-CD': 'app'});
|
||||
});
|
||||
|
||||
test('full meta, simple availableLanguages, no extra', function() {
|
||||
var meta = {
|
||||
defaultLanguage: 'ab-CD',
|
||||
availableLanguages: {'lang1': NaN}
|
||||
};
|
||||
var res = buildLocaleList(meta);
|
||||
|
||||
assert.equal(res[0], 'ab-CD');
|
||||
assert.deepEqual(res[1], {'ab-CD': 'app', 'lang1': 'app'});
|
||||
});
|
||||
|
||||
test('full meta, no extra', function() {
|
||||
var meta = {
|
||||
defaultLanguage: 'ab-CD',
|
||||
availableLanguages: {'lang1': 201501151000}
|
||||
};
|
||||
var res = buildLocaleList(meta);
|
||||
|
||||
assert.equal(res[0], 'ab-CD');
|
||||
assert.deepEqual(res[1], {'ab-CD': 'app', 'lang1': 'app'});
|
||||
});
|
||||
|
||||
test('full meta, extra, matching target', function() {
|
||||
var meta = {
|
||||
defaultLanguage: 'ab-CD',
|
||||
availableLanguages: {'lang1': 201501151000}
|
||||
};
|
||||
var extraLangs = {
|
||||
'lang2': [
|
||||
{'revision': 201501151213, 'target': '2.2'}
|
||||
]
|
||||
};
|
||||
var res = buildLocaleList(meta, extraLangs);
|
||||
|
||||
assert.equal(res[0], 'ab-CD');
|
||||
assert.deepEqual(res[1], {
|
||||
'ab-CD': 'app',
|
||||
'lang1': 'app',
|
||||
'lang2': 'extra'
|
||||
});
|
||||
});
|
||||
|
||||
test('full meta, extra, target not matching', function() {
|
||||
var meta = {
|
||||
defaultLanguage: 'ab-CD',
|
||||
availableLanguages: {'lang1': 201501151000}
|
||||
};
|
||||
var extraLangs = {
|
||||
'lang2': [
|
||||
{'revision': 201501151213, 'target': '2.3'}
|
||||
]
|
||||
};
|
||||
var res = buildLocaleList(meta, extraLangs);
|
||||
|
||||
assert.equal(res[0], 'ab-CD');
|
||||
assert.deepEqual(res[1], {'ab-CD': 'app', 'lang1': 'app'});
|
||||
});
|
||||
|
||||
test('full meta, extra, updated locale', function() {
|
||||
var meta = {
|
||||
defaultLanguage: 'ab-CD',
|
||||
availableLanguages: {'lang1': 201501151000}
|
||||
};
|
||||
var extraLangs = {
|
||||
'lang1': [
|
||||
{'revision': 201501151213, 'target': '2.2'}
|
||||
]
|
||||
};
|
||||
var res = buildLocaleList(meta, extraLangs);
|
||||
|
||||
assert.equal(res[0], 'ab-CD');
|
||||
assert.deepEqual(res[1], {'ab-CD': 'app', 'lang1': 'extra'});
|
||||
});
|
||||
|
||||
test('full meta, extra, updated locale obsolete', function() {
|
||||
var meta = {
|
||||
defaultLanguage: 'ab-CD',
|
||||
availableLanguages: {'lang1': 201501161000}
|
||||
};
|
||||
var extraLangs = {
|
||||
'lang1': [
|
||||
{'revision': 201501151213, 'target': '2.2'}
|
||||
]
|
||||
};
|
||||
var res = buildLocaleList(meta, extraLangs);
|
||||
|
||||
assert.equal(res[0], 'ab-CD');
|
||||
assert.deepEqual(res[1], {'ab-CD': 'app', 'lang1': 'app'});
|
||||
});
|
||||
});
|
||||
});
|
||||
265
gaia/apps/sharedtest/test/unit/l10n/bindings/l10n_test.js
Executable file
265
gaia/apps/sharedtest/test/unit/l10n/bindings/l10n_test.js
Executable file
|
|
@ -0,0 +1,265 @@
|
|||
'use strict';
|
||||
|
||||
var strings = {
|
||||
'lang1': [
|
||||
'cropimage = Crop',
|
||||
'cropimage2 = Crop2',
|
||||
'input.placeholder = Placeholder',
|
||||
'input2.placeholder = Placeholder 2',
|
||||
'header1 = Header 1',
|
||||
'header11 = Header 1.1',
|
||||
'header2 = Header 2',
|
||||
'header2.title = Header 2 Title',
|
||||
'unreadEmails = You have {{ emails }} unread emails',
|
||||
'bad.title = alert(1)',
|
||||
'bad.onclick = alert(1)',
|
||||
],
|
||||
'lang2': [
|
||||
'cropimage = Crop Lang2',
|
||||
'cropimage2 = Crop2 Lang2',
|
||||
'input = Foo Lang2',
|
||||
'input.placeholder = Placeholder Lang2',
|
||||
'header1 = Header 1 Lang2',
|
||||
'header11 = Header 1.1 Lang2',
|
||||
'header2 = Header 2 Lang2',
|
||||
'header2.title = Header 2 Title Lang2',
|
||||
]
|
||||
};
|
||||
|
||||
suite('L10n bindings', function() {
|
||||
var xhr;
|
||||
var lang;
|
||||
|
||||
// Do not begin tests until the test locale has been loaded.
|
||||
suiteSetup(function(done) {
|
||||
xhr = sinon.useFakeXMLHttpRequest();
|
||||
|
||||
xhr.onCreate = function(request) {
|
||||
setTimeout(function() {
|
||||
request.respond(200, {}, strings[lang].join('\n'));
|
||||
}, 0);
|
||||
};
|
||||
|
||||
// en-US has already been loaded in setup.js and l10n.js is smart enough
|
||||
// not to re-fetch resources; hence, set the lang to something new
|
||||
lang = 'lang1';
|
||||
|
||||
navigator.mozL10n.ctx.registerLocales('en-US', ['lang1', 'lang2']);
|
||||
|
||||
navigator.mozL10n.language.code = lang;
|
||||
navigator.mozL10n.once(done);
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
xhr.restore();
|
||||
});
|
||||
|
||||
suite('mutations', function() {
|
||||
suite('element', function() {
|
||||
var elem;
|
||||
|
||||
suiteSetup(function() {
|
||||
elem = document.createElement('div');
|
||||
elem.setAttribute('data-l10n-id', 'cropimage');
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
document.body.removeChild(elem);
|
||||
});
|
||||
|
||||
test('insert', function(done) {
|
||||
document.body.appendChild(elem);
|
||||
setTimeout(function() {
|
||||
assert.equal(elem.textContent, 'Crop');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('change l10n-id', function(done) {
|
||||
elem.setAttribute('data-l10n-id', 'cropimage2');
|
||||
setTimeout(function() {
|
||||
assert.equal(elem.textContent, 'Crop2');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('remove l10n-id', function(done) {
|
||||
elem.removeAttribute('data-l10n-id');
|
||||
setTimeout(function() {
|
||||
assert.equal(elem.textContent, 'Crop2');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('attribute', function() {
|
||||
var elem;
|
||||
|
||||
suiteSetup(function() {
|
||||
elem = document.createElement('input');
|
||||
elem.setAttribute('data-l10n-id', 'input');
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
document.body.removeChild(elem);
|
||||
});
|
||||
|
||||
test('insert', function(done) {
|
||||
document.body.appendChild(elem);
|
||||
setTimeout(function() {
|
||||
assert.equal(elem.getAttribute('placeholder'), 'Placeholder');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('change l10n-id', function(done) {
|
||||
elem.setAttribute('data-l10n-id', 'input2');
|
||||
setTimeout(function() {
|
||||
assert.equal(elem.getAttribute('placeholder'), 'Placeholder 2');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('remove l10n-id', function(done) {
|
||||
elem.removeAttribute('data-l10n-id');
|
||||
setTimeout(function() {
|
||||
assert.equal(elem.getAttribute('placeholder'), 'Placeholder 2');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('l10n args', function() {
|
||||
var elem;
|
||||
|
||||
suiteSetup(function() {
|
||||
elem = document.createElement('div');
|
||||
elem.setAttribute('data-l10n-args', JSON.stringify({
|
||||
emails: 5
|
||||
}));
|
||||
elem.setAttribute('data-l10n-id', 'unreadEmails');
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
document.body.removeChild(elem);
|
||||
});
|
||||
|
||||
test('insert', function(done) {
|
||||
document.body.appendChild(elem);
|
||||
setTimeout(function() {
|
||||
assert.equal(elem.textContent, 'You have 5 unread emails');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('change l10n-args', function(done) {
|
||||
elem.setAttribute('data-l10n-args', JSON.stringify({
|
||||
emails: 6
|
||||
}));
|
||||
setTimeout(function() {
|
||||
assert.equal(elem.textContent, 'You have 6 unread emails');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('DOMFragment', function() {
|
||||
var frag, h1, h2;
|
||||
|
||||
suiteSetup(function() {
|
||||
frag = document.createElement('div');
|
||||
h1 = document.createElement('h1');
|
||||
h2 = document.createElement('h2');
|
||||
h1.setAttribute('data-l10n-id', 'header1');
|
||||
h2.setAttribute('data-l10n-id', 'header2');
|
||||
frag.appendChild(h1);
|
||||
frag.appendChild(h2);
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
document.body.removeChild(frag);
|
||||
});
|
||||
|
||||
test('insert', function(done) {
|
||||
document.body.appendChild(frag);
|
||||
setTimeout(function() {
|
||||
assert.equal(h1.textContent, 'Header 1');
|
||||
assert.equal(h2.textContent, 'Header 2');
|
||||
assert.equal(h2.title, 'Header 2 Title');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('change l10n-id', function(done) {
|
||||
h1.setAttribute('data-l10n-id', 'header11');
|
||||
setTimeout(function() {
|
||||
assert.equal(h1.textContent, 'Header 1.1');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('remove l10n-id', function(done) {
|
||||
h2.removeAttribute('data-l10n-id');
|
||||
setTimeout(function() {
|
||||
assert.equal(h2.textContent, 'Header 2');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('Language change', function() {
|
||||
var elem, frag, h1, h2;
|
||||
|
||||
suiteSetup(function() {
|
||||
lang = 'lang2';
|
||||
elem = document.createElement('div');
|
||||
frag = document.createElement('div');
|
||||
h1 = document.createElement('h1');
|
||||
h2 = document.createElement('h2');
|
||||
elem.setAttribute('data-l10n-id', 'cropimage');
|
||||
h1.setAttribute('data-l10n-id', 'header1');
|
||||
h2.setAttribute('data-l10n-id', 'header2');
|
||||
frag.appendChild(h1);
|
||||
frag.appendChild(h2);
|
||||
document.body.appendChild(elem);
|
||||
document.body.appendChild(frag);
|
||||
});
|
||||
|
||||
suiteTeardown(function(done) {
|
||||
document.body.removeChild(frag);
|
||||
lang = 'lang1';
|
||||
navigator.mozL10n.ctx.addEventListener('ready', function onReady() {
|
||||
navigator.mozL10n.ctx.removeEventListener('ready', onReady);
|
||||
done();
|
||||
});
|
||||
navigator.mozL10n.ctx.requestLocales(lang);
|
||||
});
|
||||
|
||||
test('retranslation', function(done) {
|
||||
navigator.mozL10n.ctx.addEventListener('ready', function onReady() {
|
||||
navigator.mozL10n.ctx.removeEventListener('ready', onReady);
|
||||
setTimeout(function() {
|
||||
assert.equal(elem.textContent, 'Crop Lang2');
|
||||
assert.equal(h1.textContent, 'Header 1 Lang2');
|
||||
assert.equal(h2.textContent, 'Header 2 Lang2');
|
||||
assert.equal(h2.title, 'Header 2 Title Lang2');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
navigator.mozL10n.ctx.requestLocales(lang);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('whitelisting', function() {
|
||||
test('does not allow attributes not on the whitelist', function() {
|
||||
var elem = document.createElement('div');
|
||||
elem.setAttribute('data-l10n-id', 'bad');
|
||||
navigator.mozL10n.translateFragment(elem);
|
||||
assert.equal(elem.getAttribute('title'), 'alert(1)');
|
||||
assert.equal(elem.getAttribute('onclick'), undefined);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
285
gaia/apps/sharedtest/test/unit/l10n/bindings/overlay_test.js
Executable file
285
gaia/apps/sharedtest/test/unit/l10n/bindings/overlay_test.js
Executable file
|
|
@ -0,0 +1,285 @@
|
|||
'use strict';
|
||||
|
||||
var strings = {
|
||||
'lang1': [
|
||||
'text-em = Hello, <em title="WORLD">world</em>!',
|
||||
'text-em-sup = Hello, <em title="WORLD">world</em><sup>(foo)</sup>!',
|
||||
'text-entity-name = Hello & world',
|
||||
'text-entity-nnnn = Hello & world',
|
||||
'text-entity-hhhh = Hello & world',
|
||||
|
||||
'struct-a = Read <a title="MORE">more</a>.',
|
||||
'struct-em-a = <em>Read</em> <a>more</a>.',
|
||||
'struct-a-a = Read <a>more</a> or <a>cancel</a>.',
|
||||
'struct-button = <button>Submit</button>',
|
||||
'struct-button-a = <button>Submit</button> or <a>cancel</a>.',
|
||||
'struct-a-button = <a>Cancel</a> or <button>submit</button>.',
|
||||
'struct-input-input = <input placeholder="Type here"> and ' +
|
||||
'<input value="send">.',
|
||||
|
||||
'filter-button = Hello, <button title="WORLD">world</button>!',
|
||||
'filter-em-onclick = Hello, <em onclick="alert(2)">world</em>!',
|
||||
'filter-button-onclick = Hello, <button onclick="alert(2)">world</button>!',
|
||||
'filter-href = Read <a href="#B">more</a>.',
|
||||
'filter-input-value = <input value="Other value">',
|
||||
'filter-input-type = <input type="submit" value="Submit"> the form.',
|
||||
'filter-nested-button = <em>No <button>button</button>.</em>',
|
||||
],
|
||||
'lang2': [
|
||||
'struct-a = Czytaj <a>więcej</a>.'
|
||||
]
|
||||
};
|
||||
|
||||
suite('L10n DOM overlays', function() {
|
||||
var xhr;
|
||||
var lang;
|
||||
|
||||
// Do not begin tests until the test locale has been loaded.
|
||||
suiteSetup(function(done) {
|
||||
xhr = sinon.useFakeXMLHttpRequest();
|
||||
|
||||
xhr.onCreate = function(request) {
|
||||
setTimeout(function() {
|
||||
request.respond(200, {}, strings[lang].join('\n'));
|
||||
}, 0);
|
||||
};
|
||||
|
||||
// en-US has already been loaded in setup.js and l10n.js is smart enough
|
||||
// not to re-fetch resources; hence, set the lang to something new
|
||||
lang = 'lang1';
|
||||
|
||||
navigator.mozL10n.ctx.registerLocales('en-US', ['lang1', 'lang2']);
|
||||
|
||||
navigator.mozL10n.language.code = lang;
|
||||
navigator.mozL10n.once(done);
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
xhr.restore();
|
||||
});
|
||||
|
||||
suite('text-level semantics', function() {
|
||||
var elem;
|
||||
|
||||
setup(function() {
|
||||
elem = document.createElement('div');
|
||||
});
|
||||
|
||||
test('em is allowed', function() {
|
||||
navigator.mozL10n.setAttributes(elem, 'text-em');
|
||||
navigator.mozL10n.translateFragment(elem);
|
||||
assert.equal(elem.innerHTML, 'Hello, <em title="WORLD">world</em>!');
|
||||
});
|
||||
|
||||
test('em and sup are both allowed', function() {
|
||||
navigator.mozL10n.setAttributes(elem, 'text-em-sup');
|
||||
navigator.mozL10n.translateFragment(elem);
|
||||
assert.equal(
|
||||
elem.innerHTML, 'Hello, <em title="WORLD">world</em><sup>(foo)</sup>!');
|
||||
});
|
||||
|
||||
test('entity name is allowed', function() {
|
||||
navigator.mozL10n.setAttributes(elem, 'text-entity-name');
|
||||
navigator.mozL10n.translateFragment(elem);
|
||||
assert.equal(elem.innerHTML, 'Hello & world');
|
||||
});
|
||||
|
||||
test('entity decimal codepoint is allowed', function() {
|
||||
navigator.mozL10n.setAttributes(elem, 'text-entity-nnnn');
|
||||
navigator.mozL10n.translateFragment(elem);
|
||||
assert.equal(elem.innerHTML, 'Hello & world');
|
||||
});
|
||||
|
||||
test('entity hexadecimal codepoint is allowed', function() {
|
||||
navigator.mozL10n.setAttributes(elem, 'text-entity-hhhh');
|
||||
navigator.mozL10n.translateFragment(elem);
|
||||
assert.equal(elem.innerHTML, 'Hello & world');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
suite('structural overlay', function() {
|
||||
var elem;
|
||||
|
||||
setup(function() {
|
||||
elem = document.createElement('div');
|
||||
});
|
||||
|
||||
test('href is overlaid', function() {
|
||||
elem.innerHTML = '<a href="#A" title="TITLE">A</a>';
|
||||
navigator.mozL10n.setAttributes(elem, 'struct-a');
|
||||
navigator.mozL10n.translateFragment(elem);
|
||||
assert.equal(
|
||||
elem.innerHTML,
|
||||
'Read <a href="#A" title="MORE">more</a>.');
|
||||
});
|
||||
|
||||
test('href is overlaid onto a', function() {
|
||||
elem.innerHTML = '<a href="#A" title="TITLE">A</a>';
|
||||
navigator.mozL10n.setAttributes(elem, 'struct-em-a');
|
||||
navigator.mozL10n.translateFragment(elem);
|
||||
assert.equal(
|
||||
elem.innerHTML,
|
||||
'<em>Read</em> <a href="#A" title="TITLE">more</a>.');
|
||||
});
|
||||
|
||||
test('hrefs are overlaid onto two a\'s', function() {
|
||||
elem.innerHTML = '<a href="#A">A</a> <a href="#B">B</a>';
|
||||
navigator.mozL10n.setAttributes(elem, 'struct-a-a');
|
||||
navigator.mozL10n.translateFragment(elem);
|
||||
assert.equal(
|
||||
elem.innerHTML,
|
||||
'Read <a href="#A">more</a> or <a href="#B">cancel</a>.');
|
||||
});
|
||||
|
||||
test('button is overlaid', function() {
|
||||
elem.innerHTML = '<button onclick="alert(1)">BUTTON</button>';
|
||||
navigator.mozL10n.setAttributes(elem, 'struct-button');
|
||||
navigator.mozL10n.translateFragment(elem);
|
||||
assert.equal(
|
||||
elem.innerHTML,
|
||||
'<button onclick="alert(1)">Submit</button>');
|
||||
});
|
||||
|
||||
test('button and a are overlaid', function() {
|
||||
elem.innerHTML =
|
||||
'<button onclick="alert(1)">BUTTON</button> <a href="#A">A</a>';
|
||||
navigator.mozL10n.setAttributes(elem, 'struct-button-a');
|
||||
navigator.mozL10n.translateFragment(elem);
|
||||
assert.equal(
|
||||
elem.innerHTML,
|
||||
'<button onclick="alert(1)">Submit</button> or ' +
|
||||
'<a href="#A">cancel</a>.');
|
||||
});
|
||||
|
||||
test('a and button are overlaid', function() {
|
||||
elem.innerHTML =
|
||||
'<button onclick="alert(1)">BUTTON</button> <a href="#A">A</a>';
|
||||
navigator.mozL10n.setAttributes(elem, 'struct-a-button');
|
||||
navigator.mozL10n.translateFragment(elem);
|
||||
assert.equal(
|
||||
elem.innerHTML,
|
||||
'<a href="#A">Cancel</a> or ' +
|
||||
'<button onclick="alert(1)">submit</button>.');
|
||||
});
|
||||
|
||||
test('inputs are overlaid', function() {
|
||||
elem.innerHTML =
|
||||
'<input type="text"> <input type="submit" onclick="alert(1)">';
|
||||
navigator.mozL10n.setAttributes(elem, 'struct-input-input');
|
||||
navigator.mozL10n.translateFragment(elem);
|
||||
assert.equal(
|
||||
elem.innerHTML,
|
||||
'<input placeholder="Type here" type="text"> and ' +
|
||||
'<input value="send" onclick="alert(1)" type="submit">.');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
suite('filtering', function() {
|
||||
var elem;
|
||||
|
||||
setup(function() {
|
||||
elem = document.createElement('div');
|
||||
});
|
||||
|
||||
test('button is not allowed', function() {
|
||||
navigator.mozL10n.setAttributes(elem, 'filter-button');
|
||||
navigator.mozL10n.translateFragment(elem);
|
||||
assert.equal(elem.innerHTML, 'Hello, world!');
|
||||
});
|
||||
|
||||
test('onclick on em is not allowed', function() {
|
||||
navigator.mozL10n.setAttributes(elem, 'filter-em-onclick');
|
||||
navigator.mozL10n.translateFragment(elem);
|
||||
assert.equal(elem.innerHTML, 'Hello, <em>world</em>!');
|
||||
});
|
||||
|
||||
test('onclick on button is not allowed', function() {
|
||||
elem.innerHTML = '<button onclick="alert(1)"></button>';
|
||||
navigator.mozL10n.setAttributes(elem, 'filter-button-onclick');
|
||||
navigator.mozL10n.translateFragment(elem);
|
||||
assert.equal(
|
||||
elem.innerHTML,
|
||||
'Hello, <button onclick="alert(1)">world</button>!');
|
||||
});
|
||||
|
||||
test('href on a is not allowed', function() {
|
||||
elem.innerHTML = '<a href="#A">A</a>';
|
||||
navigator.mozL10n.setAttributes(elem, 'filter-href');
|
||||
navigator.mozL10n.translateFragment(elem);
|
||||
assert.equal(
|
||||
elem.innerHTML,
|
||||
'Read <a href="#A">more</a>.');
|
||||
});
|
||||
|
||||
test('value on text input not allowed', function() {
|
||||
elem.innerHTML = '<input type="text" value="INPUT">';
|
||||
navigator.mozL10n.setAttributes(elem, 'filter-input-value');
|
||||
navigator.mozL10n.translateFragment(elem);
|
||||
assert.equal(
|
||||
elem.innerHTML,
|
||||
'<input value="INPUT" type="text">');
|
||||
});
|
||||
|
||||
test('type on input not allowed', function() {
|
||||
elem.innerHTML = '<input type="text" placeholder="INPUT">';
|
||||
navigator.mozL10n.setAttributes(elem, 'filter-input-type');
|
||||
navigator.mozL10n.translateFragment(elem);
|
||||
assert.equal(
|
||||
elem.innerHTML,
|
||||
'<input placeholder="INPUT" type="text"> the form.');
|
||||
});
|
||||
|
||||
test('nested button is not allowed', function() {
|
||||
elem.innerHTML = '';
|
||||
navigator.mozL10n.setAttributes(elem, 'filter-nested-button');
|
||||
navigator.mozL10n.translateFragment(elem);
|
||||
assert.equal(
|
||||
elem.innerHTML,
|
||||
'<em>No button.</em>');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
suite('Language change', function() {
|
||||
var elem;
|
||||
|
||||
suiteSetup(function() {
|
||||
lang = 'lang2';
|
||||
elem = document.createElement('div');
|
||||
elem.innerHTML = '<a href="#A"></a>';
|
||||
navigator.mozL10n.setAttributes(elem, 'struct-a');
|
||||
});
|
||||
|
||||
suiteTeardown(function(done) {
|
||||
lang = 'lang1';
|
||||
navigator.mozL10n.ctx.addEventListener('ready', function onReady() {
|
||||
navigator.mozL10n.ctx.removeEventListener('ready', onReady);
|
||||
done();
|
||||
});
|
||||
navigator.mozL10n.ctx.requestLocales(lang);
|
||||
});
|
||||
|
||||
test('retranslation', function(done) {
|
||||
navigator.mozL10n.translateFragment(elem);
|
||||
assert.equal(
|
||||
elem.innerHTML,
|
||||
'Read <a title="MORE" href="#A">more</a>.');
|
||||
|
||||
navigator.mozL10n.ctx.addEventListener('ready', function onReady() {
|
||||
navigator.mozL10n.ctx.removeEventListener('ready', onReady);
|
||||
navigator.mozL10n.translateFragment(elem);
|
||||
// XXX test known bad behavior; the title attribute leaks from lang1
|
||||
// into lang2; see https://bugzil.la/922577
|
||||
assert.equal(
|
||||
elem.innerHTML,
|
||||
'Czytaj <a title="MORE" href="#A">więcej</a>.');
|
||||
done();
|
||||
});
|
||||
|
||||
navigator.mozL10n.ctx.requestLocales(lang);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
108
gaia/apps/sharedtest/test/unit/l10n/l10n_date_test.js
Executable file
108
gaia/apps/sharedtest/test/unit/l10n/l10n_date_test.js
Executable file
|
|
@ -0,0 +1,108 @@
|
|||
'use strict';
|
||||
|
||||
require('/shared/js/l10n.js');
|
||||
require('/shared/js/l10n_date.js');
|
||||
|
||||
suite('l10n date', function(done) {
|
||||
|
||||
var l10nDate;
|
||||
|
||||
suiteSetup(function() {
|
||||
l10nDate = navigator.mozL10n.DateTimeFormat();
|
||||
});
|
||||
|
||||
suite('LocaleFormat', function() {
|
||||
test('%x format', function() {
|
||||
var result = l10nDate.localeFormat(new Date(), '%x');
|
||||
assert.ok(/\d+\/\d+\/\d+/.test(result));
|
||||
});
|
||||
|
||||
test('should handle 12 PM correctly', function() {
|
||||
var date = new Date();
|
||||
date.setHours(12);
|
||||
var result = l10nDate.localeFormat(date, '%p');
|
||||
assert.strictEqual(result, 'PM');
|
||||
});
|
||||
});
|
||||
|
||||
suite('relativeParts', function() {
|
||||
test('negative minutes', function() {
|
||||
var result = l10nDate.relativeParts(-50 * 60);
|
||||
assert.deepEqual(result, {minutes: 50});
|
||||
});
|
||||
|
||||
test('multiple values', function() {
|
||||
var result = l10nDate.relativeParts(2 * 61 * 60);
|
||||
assert.deepEqual(result, {hours: 2, minutes: 2});
|
||||
});
|
||||
|
||||
test('round low values to minutes', function() {
|
||||
var result = l10nDate.relativeParts(20);
|
||||
assert.deepEqual(result, {minutes: 0});
|
||||
|
||||
var result2 = l10nDate.relativeParts(30);
|
||||
assert.deepEqual(result2, {minutes: 1});
|
||||
});
|
||||
});
|
||||
|
||||
suite('prettyDate', function() {
|
||||
test('maxDiff is null', function() {
|
||||
var time = Date.now() - 86400 * 20 * 1000;
|
||||
var result = l10nDate.fromNow(time);
|
||||
assert.ok(result.indexOf('/') !== -1);
|
||||
});
|
||||
|
||||
test('maxDiff is specified', function() {
|
||||
var time = Date.now() - 86400 * 20 * 1000;
|
||||
var result = l10nDate.fromNow(time, false, (86400 * 21));
|
||||
|
||||
assert.ok(result.indexOf('/') === -1);
|
||||
assert.equal(result, '2 weeks ago');
|
||||
});
|
||||
|
||||
test('useCompactFormat is null', function() {
|
||||
var time = Date.now() - 86400 * 5 * 1000;
|
||||
var result = l10nDate.fromNow(time);
|
||||
|
||||
assert.equal(result, '5 days ago');
|
||||
});
|
||||
|
||||
test('useCompactFormat is true', function() {
|
||||
var time = Date.now() - 86400 * 5 * 1000;
|
||||
var result = l10nDate.fromNow(time, true);
|
||||
|
||||
assert.equal(result, '5d ago');
|
||||
});
|
||||
|
||||
test('now', function() {
|
||||
var time = Date.now() - 29 * 1000;
|
||||
var result = l10nDate.fromNow(time);
|
||||
assert.equal(result, 'just now');
|
||||
|
||||
time = Date.now() + 29 * 1000;
|
||||
result = l10nDate.fromNow(time);
|
||||
assert.equal(result, 'now');
|
||||
});
|
||||
|
||||
test('in a minute', function() {
|
||||
var time = Date.now() + 35 * 1000;
|
||||
var result = l10nDate.fromNow(time);
|
||||
assert.equal(result, 'in a minute');
|
||||
|
||||
time = Date.now() + 1.8 * 60 * 1000;
|
||||
result = l10nDate.fromNow(time);
|
||||
assert.equal(result, 'in a minute');
|
||||
});
|
||||
|
||||
test('should discard ms if diff is over 1 minute', function() {
|
||||
var time = Date.now() + 2 * 60 * 1000 - 500;
|
||||
var result = l10nDate.fromNow(time);
|
||||
assert.equal(result, 'in 2 minutes');
|
||||
|
||||
time = Date.now() + 2 * 60 * 60 * 1000 - 500;
|
||||
result = l10nDate.fromNow(time);
|
||||
assert.equal(result, 'in 2 hours');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
315
gaia/apps/sharedtest/test/unit/l10n/l10n_test.js
Executable file
315
gaia/apps/sharedtest/test/unit/l10n/l10n_test.js
Executable file
|
|
@ -0,0 +1,315 @@
|
|||
suite('L10n', function() {
|
||||
'use strict';
|
||||
var _;
|
||||
var _translateFragment;
|
||||
|
||||
var l10props = [
|
||||
'cropimage = Crop',
|
||||
'delete-n-items = {[ plural(n) ]}',
|
||||
'delete-n-items[zero] = Nothing selected',
|
||||
'delete-n-items[one] = Delete selected item?',
|
||||
'delete-n-items[other] = Delete {{ n }} items?',
|
||||
'textcontent-test = this is text content',
|
||||
'dom-overlay-test = this is text content <button>(bar)</button>',
|
||||
'attr-test.title = this is an attribute',
|
||||
'euroSign = price: 10\\u20ac to 20\\u20ac',
|
||||
'leadingSpaces = \\u0020\\u020\\u20%2F',
|
||||
'trailingBackslash = backslash\\\\',
|
||||
'multiLine = foo \\',
|
||||
' bar \\',
|
||||
' baz',
|
||||
'update = {[ plural(n) ]}',
|
||||
'update[zero] = <strong>No updates.</strong>',
|
||||
'update[one] = <strong>{{n}} update available.</strong> \\',
|
||||
' <span>Tap for more info.</span>',
|
||||
'update[other] = <strong>{{n}} updates available.</strong> \\',
|
||||
' <span>Tap for more info.</span>',
|
||||
'overlay = {[ plural(n) ]}',
|
||||
'overlay[zero] = <strong>No updates.</strong>',
|
||||
'overlay[one] = <strong>{{n}} update available.</strong> \\',
|
||||
' <span>Tap for more info.</span>',
|
||||
'overlay[other] = <strong>{{n}} updates available.</strong> \\',
|
||||
' <span>Tap for more info.</span>',
|
||||
'inline-translation-test = static content provided by inlined JSON',
|
||||
'a11y-label.ariaLabel = label via ARIA',
|
||||
'a11y-label.ariaValueText = valuetext via ARIA',
|
||||
'a11y-label.ariaMozHint = moz-hint via ARIA'
|
||||
].join('\n');
|
||||
|
||||
var inlineL10Props = {
|
||||
'inline-translation-test': 'static content provided by inlined JSON'
|
||||
};
|
||||
|
||||
var key_cropImage = 'cropimage';
|
||||
var key_delete = 'delete-n-items';
|
||||
var key_euroSign = 'euroSign';
|
||||
var key_leadingSpaces = 'leadingSpaces';
|
||||
var key_multiLine = 'multiLine';
|
||||
var key_backslash = 'trailingBackslash';
|
||||
|
||||
var xhr;
|
||||
|
||||
// Do not begin tests until the test locale has been loaded.
|
||||
suiteSetup(function(done) {
|
||||
xhr = sinon.useFakeXMLHttpRequest();
|
||||
|
||||
xhr.onCreate = function(request) {
|
||||
setTimeout(function() {
|
||||
request.respond(200, {}, l10props);
|
||||
}, 0);
|
||||
};
|
||||
|
||||
_ = navigator.mozL10n.get;
|
||||
_translateFragment = navigator.mozL10n.translateFragment;
|
||||
|
||||
// en-US has already been loaded in setup.js and l10n.js is smart enough
|
||||
// not to re-fetch resources; hence, set the lang to something new
|
||||
var lang = 'fr';
|
||||
|
||||
var inline = document.createElement('script');
|
||||
inline.setAttribute('type', 'application/l10n');
|
||||
inline.setAttribute('lang', lang);
|
||||
inline.textContent = JSON.stringify(inlineL10Props);
|
||||
document.head.appendChild(inline);
|
||||
|
||||
navigator.mozL10n.ctx.registerLocales('en-US',
|
||||
['fr', 'zh-TW', 'ar']);
|
||||
navigator.mozL10n.language.code = lang;
|
||||
navigator.mozL10n.once(done);
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
xhr.restore();
|
||||
});
|
||||
|
||||
suite('get', function() {
|
||||
test('existing key', function() {
|
||||
assert.strictEqual(_(key_cropImage), 'Crop');
|
||||
});
|
||||
|
||||
test('inexisting key', function() {
|
||||
assert.strictEqual(_('bla'), '');
|
||||
});
|
||||
|
||||
suite('plural', function() {
|
||||
test('n=0', function() {
|
||||
assert.strictEqual(_(key_delete, { n: 0 }), 'Nothing selected');
|
||||
});
|
||||
|
||||
test('n=1', function() {
|
||||
assert.strictEqual(_(key_delete, { n: 1 }), 'Delete selected item?');
|
||||
});
|
||||
|
||||
test('n=2', function() {
|
||||
assert.strictEqual(_(key_delete, { n: 2 }), 'Delete 2 items?');
|
||||
});
|
||||
});
|
||||
|
||||
test('unescape utf8 char codes', function() {
|
||||
assert.strictEqual(_(key_euroSign), 'price: 10€ to 20€');
|
||||
assert.strictEqual(_(key_leadingSpaces), ' %2F');
|
||||
});
|
||||
|
||||
test('multiline string', function() {
|
||||
assert.strictEqual(_(key_multiLine), 'foo bar baz');
|
||||
});
|
||||
|
||||
test('escaped trailing backslash', function() {
|
||||
assert.strictEqual(_(key_backslash), 'backslash\\');
|
||||
});
|
||||
});
|
||||
|
||||
suite('translateFragment', function() {
|
||||
var elem;
|
||||
setup(function() {
|
||||
elem = document.createElement('div');
|
||||
});
|
||||
|
||||
test('text content', function() {
|
||||
elem.dataset.l10nId = 'textcontent-test';
|
||||
_translateFragment(elem);
|
||||
assert.equal(elem.textContent, 'this is text content');
|
||||
});
|
||||
|
||||
test('properties', function() {
|
||||
elem.dataset.l10nId = 'attr-test';
|
||||
_translateFragment(elem);
|
||||
assert.equal(elem.getAttribute('title'), 'this is an attribute');
|
||||
});
|
||||
|
||||
test('ARIA labels', function() {
|
||||
elem.dataset.l10nId = 'a11y-label';
|
||||
_translateFragment(elem);
|
||||
assert.equal(elem.getAttribute('aria-label'), 'label via ARIA');
|
||||
});
|
||||
|
||||
test('ARIA valuetext', function() {
|
||||
elem.dataset.l10nId = 'a11y-label';
|
||||
_translateFragment(elem);
|
||||
assert.equal(elem.getAttribute('aria-valuetext'), 'valuetext via ARIA');
|
||||
});
|
||||
|
||||
test('ARIA moz-hint', function() {
|
||||
elem.dataset.l10nId = 'a11y-label';
|
||||
_translateFragment(elem);
|
||||
assert.equal(elem.getAttribute('aria-moz-hint'), 'moz-hint via ARIA');
|
||||
});
|
||||
|
||||
test('for a document fragment', function() {
|
||||
var fragment = document.createDocumentFragment();
|
||||
elem.dataset.l10nId = 'textcontent-test';
|
||||
fragment.appendChild(elem);
|
||||
_translateFragment(fragment);
|
||||
assert.equal(elem.textContent, 'this is text content');
|
||||
});
|
||||
|
||||
test('for a shadowRoot', function() {
|
||||
var container = document.createElement('div');
|
||||
container.createShadowRoot();
|
||||
elem.dataset.l10nId = 'textcontent-test';
|
||||
container.shadowRoot.appendChild(elem);
|
||||
_translateFragment(container.shadowRoot);
|
||||
assert.equal(elem.textContent, 'this is text content');
|
||||
});
|
||||
});
|
||||
|
||||
suite('localize + translate', function() {
|
||||
var elem;
|
||||
setup(function() {
|
||||
elem = document.createElement('div');
|
||||
});
|
||||
|
||||
test('text content', function() {
|
||||
navigator.mozL10n.setAttributes(elem, 'textcontent-test');
|
||||
_translateFragment(elem);
|
||||
assert.equal(elem.textContent, 'this is text content');
|
||||
});
|
||||
|
||||
test('properties', function() {
|
||||
navigator.mozL10n.setAttributes(elem, 'attr-test');
|
||||
_translateFragment(elem);
|
||||
assert.equal(elem.getAttribute('title'), 'this is an attribute');
|
||||
});
|
||||
|
||||
// XXX Remove in https://bugzil.la/1027117
|
||||
suite('properties + pluralization', function() {
|
||||
test('n=0', function() {
|
||||
navigator.mozL10n.setAttributes(elem, 'update', { n: 0 });
|
||||
_translateFragment(elem);
|
||||
var info = elem.querySelector('strong');
|
||||
var span = elem.querySelector('span');
|
||||
assert.ok(info);
|
||||
assert.isNull(span);
|
||||
assert.equal(info.textContent, 'No updates.');
|
||||
});
|
||||
|
||||
test('n=1', function() {
|
||||
navigator.mozL10n.setAttributes(elem, 'update', { n: 1 });
|
||||
_translateFragment(elem);
|
||||
var info = elem.querySelector('strong');
|
||||
var span = elem.querySelector('span');
|
||||
assert.ok(info);
|
||||
assert.ok(span);
|
||||
assert.equal(info.textContent, '1 update available.');
|
||||
});
|
||||
|
||||
test('n=2', function() {
|
||||
navigator.mozL10n.setAttributes(elem, 'update', { n: 2 });
|
||||
_translateFragment(elem);
|
||||
var info = elem.querySelector('strong');
|
||||
var span = elem.querySelector('span');
|
||||
assert.ok(info);
|
||||
assert.ok(span);
|
||||
assert.equal(info.textContent, '2 updates available.');
|
||||
});
|
||||
});
|
||||
|
||||
suite('DOM overlays + pluralization', function() {
|
||||
test('n=0', function() {
|
||||
navigator.mozL10n.setAttributes(elem, 'overlay', { n: 0 });
|
||||
_translateFragment(elem);
|
||||
var info = elem.querySelector('strong');
|
||||
var span = elem.querySelector('span');
|
||||
assert.ok(info);
|
||||
assert.isNull(span);
|
||||
assert.equal(info.textContent, 'No updates.');
|
||||
});
|
||||
|
||||
test('n=1', function() {
|
||||
navigator.mozL10n.setAttributes(elem, 'overlay', { n: 1 });
|
||||
_translateFragment(elem);
|
||||
var info = elem.querySelector('strong');
|
||||
var span = elem.querySelector('span');
|
||||
assert.ok(info);
|
||||
assert.ok(span);
|
||||
assert.equal(info.textContent, '1 update available.');
|
||||
});
|
||||
|
||||
test('n=2', function() {
|
||||
navigator.mozL10n.setAttributes(elem, 'overlay', { n: 2 });
|
||||
_translateFragment(elem);
|
||||
var info = elem.querySelector('strong');
|
||||
var span = elem.querySelector('span');
|
||||
assert.ok(info);
|
||||
assert.ok(span);
|
||||
assert.equal(info.textContent, '2 updates available.');
|
||||
});
|
||||
});
|
||||
|
||||
test('element with child, translation without a child', function() {
|
||||
elem.innerHTML = 'here is a button <button>(foo)</button>';
|
||||
navigator.mozL10n.setAttributes(elem, 'textcontent-test');
|
||||
_translateFragment(elem);
|
||||
assert.equal(elem.innerHTML, 'this is text content');
|
||||
});
|
||||
|
||||
test('element and translation with child', function() {
|
||||
elem.innerHTML = 'here is a button <button>(foo)</button>';
|
||||
navigator.mozL10n.setAttributes(elem, 'dom-overlay-test');
|
||||
_translateFragment(elem);
|
||||
assert.equal(elem.innerHTML,
|
||||
'this is text content <button>(bar)</button>');
|
||||
});
|
||||
});
|
||||
|
||||
suite('translate existing', function() {
|
||||
function setLang(lang, callback) {
|
||||
// we want onLocalized to be invoked only *after* the language change
|
||||
window.addEventListener('localized', function onLocalized() {
|
||||
window.removeEventListener('localized', onLocalized);
|
||||
callback();
|
||||
});
|
||||
navigator.mozL10n.language.code = lang;
|
||||
}
|
||||
|
||||
var elem;
|
||||
|
||||
setup(function() {
|
||||
elem = document.createElement('div');
|
||||
document.body.appendChild(elem);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
document.body.removeChild(elem);
|
||||
});
|
||||
|
||||
test('inline translation', function(done) {
|
||||
elem.dataset.l10nId = 'inline-translation-test';
|
||||
assert.equal(elem.textContent, '');
|
||||
setLang('zh-TW', function() {
|
||||
assert.equal(elem.textContent,
|
||||
'static content provided by inlined JSON');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('downloaded translation', function(done) {
|
||||
elem.dataset.l10nId = 'cropimage';
|
||||
assert.equal(elem.textContent, '');
|
||||
setLang('ar', function() {
|
||||
assert.equal(elem.textContent, 'Crop');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
71
gaia/apps/sharedtest/test/unit/l10n/lib/context/basic_test.js
Executable file
71
gaia/apps/sharedtest/test/unit/l10n/lib/context/basic_test.js
Executable file
|
|
@ -0,0 +1,71 @@
|
|||
/* global assert:true, it, describe, beforeEach */
|
||||
/* global navigator, __dirname */
|
||||
'use strict';
|
||||
|
||||
if (typeof navigator !== 'undefined') {
|
||||
var L10n = navigator.mozL10n._getInternalAPI();
|
||||
var L20n = {
|
||||
getContext: function() {
|
||||
return new L10n.Context();
|
||||
}
|
||||
};
|
||||
} else {
|
||||
var assert = require('assert');
|
||||
var L20n = require('../../../src/bindings/node');
|
||||
}
|
||||
|
||||
if (typeof navigator !== 'undefined') {
|
||||
var path =
|
||||
'app://sharedtest.gaiamobile.org/test/unit/l10n/lib/context';
|
||||
} else {
|
||||
var path = __dirname;
|
||||
}
|
||||
|
||||
describe('A simple context with one resource', function() {
|
||||
var ctx;
|
||||
|
||||
beforeEach(function(done) {
|
||||
ctx = L20n.getContext();
|
||||
ctx.resLinks.push(path + '/fixtures/basic.properties');
|
||||
ctx.ready(done);
|
||||
ctx.registerLocales('en-US');
|
||||
ctx.requestLocales('en-US');
|
||||
});
|
||||
|
||||
it('should return the string value of brandName', function() {
|
||||
var value = ctx.get('brandName');
|
||||
assert.strictEqual(value, 'Firefox');
|
||||
});
|
||||
|
||||
it('should return the value of about with the value' +
|
||||
' of brandName in it', function() {
|
||||
var value = ctx.get('about');
|
||||
assert.strictEqual(value, 'About Firefox');
|
||||
});
|
||||
|
||||
it('should return the value of cert with the value of ' +
|
||||
'organization passed directly', function() {
|
||||
var value = ctx.get('cert', {organization: 'Mozilla Foundation'});
|
||||
assert.strictEqual(value, 'Certificate signed by Mozilla Foundation');
|
||||
});
|
||||
|
||||
it('should return the correct plural form for 0', function() {
|
||||
var value = ctx.get('unreadMessages', {unread: 0});
|
||||
assert.strictEqual(value, '0 unread');
|
||||
});
|
||||
|
||||
it('should return the correct plural form for 1', function() {
|
||||
var value = ctx.get('unreadMessages', {unread: 1});
|
||||
assert.strictEqual(value, 'One unread');
|
||||
});
|
||||
|
||||
it('should return the correct plural form for 2', function() {
|
||||
var value = ctx.get('unreadMessages', {unread: 2});
|
||||
assert.strictEqual(value, '2 unread');
|
||||
});
|
||||
|
||||
it('should return the correct plural form for 3', function() {
|
||||
var value = ctx.get('unreadMessages', {unread: 3});
|
||||
assert.strictEqual(value, '3 unread');
|
||||
});
|
||||
});
|
||||
110
gaia/apps/sharedtest/test/unit/l10n/lib/context/fallback_test.js
Executable file
110
gaia/apps/sharedtest/test/unit/l10n/lib/context/fallback_test.js
Executable file
|
|
@ -0,0 +1,110 @@
|
|||
/* global assert:true, it, describe, beforeEach */
|
||||
/* global navigator, __dirname */
|
||||
'use strict';
|
||||
|
||||
if (typeof navigator !== 'undefined') {
|
||||
var L10n = navigator.mozL10n._getInternalAPI();
|
||||
var Context = L10n.Context;
|
||||
var path =
|
||||
'app://sharedtest.gaiamobile.org/test/unit/l10n/lib/context';
|
||||
} else {
|
||||
var assert = require('assert');
|
||||
var Context = require('../../../src/lib/context').Context;
|
||||
var path = __dirname;
|
||||
}
|
||||
|
||||
describe('One fallback locale', function() {
|
||||
var ctx;
|
||||
|
||||
beforeEach(function(done) {
|
||||
ctx = new Context();
|
||||
ctx.resLinks.push(path + '/fixtures/{locale}.properties');
|
||||
ctx.once(done);
|
||||
ctx.registerLocales('en-US', ['pl']);
|
||||
ctx.requestLocales('pl');
|
||||
});
|
||||
|
||||
describe('Translation in the first locale exists and is OK', function() {
|
||||
it('[e]', function() {
|
||||
assert.strictEqual(ctx.get('e'), 'E pl');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ValueError in first locale', function() {
|
||||
describe('Entity exists in second locale:', function() {
|
||||
it('[ve]', function() {
|
||||
assert.strictEqual(ctx.get('ve'), 'VE {{ boo }} pl');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ValueError in second locale:', function() {
|
||||
it('[vv]', function() {
|
||||
assert.strictEqual(ctx.get('vv'), 'VV {{ boo }} pl');
|
||||
});
|
||||
});
|
||||
|
||||
describe('IndexError in second locale:', function() {
|
||||
it('[vi]', function() {
|
||||
assert.strictEqual(ctx.get('vi'), 'VI {{ boo }} pl');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Entity missing in second locale:', function() {
|
||||
it('[vm]', function() {
|
||||
assert.strictEqual(ctx.get('vm'), 'VM {{ boo }} pl');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('IndexError in first locale', function() {
|
||||
describe('Entity exists in second locale', function() {
|
||||
it('[ie]', function() {
|
||||
assert.strictEqual(ctx.get('ie'), 'ie');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ValueError in second locale', function() {
|
||||
it('[iv]', function() {
|
||||
assert.strictEqual(ctx.get('iv'), 'iv');
|
||||
});
|
||||
});
|
||||
|
||||
describe('IndexError in second locale', function() {
|
||||
it('[ii]', function() {
|
||||
assert.strictEqual(ctx.get('ii'), 'ii');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Entity missing in second locale:', function() {
|
||||
it('[im]', function() {
|
||||
assert.strictEqual(ctx.get('im'), 'im');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Entity not found in first locale', function() {
|
||||
describe('Entity exists in second locale:', function() {
|
||||
it('[me]', function() {
|
||||
assert.strictEqual(ctx.get('me'), 'ME en-US');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ValueError in second locale:', function() {
|
||||
it('[mv]', function() {
|
||||
assert.strictEqual(ctx.get('mv'), 'MV {{ boo }} en-US');
|
||||
});
|
||||
});
|
||||
|
||||
describe('IndexError in second locale:', function() {
|
||||
it('[mi]', function() {
|
||||
assert.strictEqual(ctx.get('mi'), 'mi');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Entity missing in second locale:', function() {
|
||||
it('[mm]', function() {
|
||||
assert.strictEqual(ctx.get('mm'), '');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
brandName=Firefox
|
||||
about=About {{ brandName }}
|
||||
cert=Certificate signed by {{ organization }}
|
||||
unreadMessages={[plural(unread)]}
|
||||
unreadMessages[one]=One unread
|
||||
unreadMessages[other]={{unread}} unread
|
||||
57
gaia/apps/sharedtest/test/unit/l10n/lib/context/fixtures/en-US.properties
Executable file
57
gaia/apps/sharedtest/test/unit/l10n/lib/context/fixtures/en-US.properties
Executable file
|
|
@ -0,0 +1,57 @@
|
|||
foo=Foo en-US
|
||||
|
||||
# e - existing
|
||||
# v - ValueError
|
||||
# i - IndexError
|
||||
# m - missing
|
||||
|
||||
me=ME en-US
|
||||
mv=MV {{ boo }} en-US
|
||||
mi={[plural(boo)]}
|
||||
mi[key]=MI en-US
|
||||
|
||||
vve=VVE en-US
|
||||
vvv=VVV {{ boo }} en-US
|
||||
<vvi[boo] { key: "VVI en-US" }>
|
||||
|
||||
vie=VIE en-US
|
||||
viv=VIV {{ boo }} en-US
|
||||
vii={[plural(boo)]}
|
||||
vii[key]=VII en-US
|
||||
|
||||
vme=VME en-US
|
||||
vmv=VMV {{ boo }} en-US
|
||||
vmi={[plural(boo)]}
|
||||
vmi[key]=VMI en-US
|
||||
|
||||
|
||||
ive=IVE en-US
|
||||
ivv=IVV {{ boo }} en-US
|
||||
ivi={[plural(boo)]}
|
||||
ivi[key]=IVI en-US
|
||||
|
||||
iie=IIE en-US
|
||||
iiv=IIV {{ boo }} en-US
|
||||
iii={[plural(boo)]}
|
||||
iii[key]=III en-US
|
||||
|
||||
ime=IME en-US
|
||||
imv=IMV {{ boo }} en-US
|
||||
imi={[plural(boo)]}
|
||||
imi[key]=IMI en-US
|
||||
|
||||
|
||||
mve=MVE en-US
|
||||
mvv=MVV {{ boo }} en-US
|
||||
mvi={[plural(boo)]}
|
||||
mvi[key]=MVI en-US
|
||||
|
||||
mie=MIE en-US
|
||||
miv=MIV {{ boo }} en-US
|
||||
mii={[plural(boo)]}
|
||||
mii[key]=MII en-US
|
||||
|
||||
mme=MME en-US
|
||||
mmv=MMV {{ boo }} en-US
|
||||
mmi={[plural(boo)]}
|
||||
mmi[key]=MMI en-US
|
||||
69
gaia/apps/sharedtest/test/unit/l10n/lib/context/fixtures/pl.properties
Executable file
69
gaia/apps/sharedtest/test/unit/l10n/lib/context/fixtures/pl.properties
Executable file
|
|
@ -0,0 +1,69 @@
|
|||
foo=Foo pl
|
||||
|
||||
# e - existing
|
||||
# v - ValueError
|
||||
# i - IndexError
|
||||
# m - missing
|
||||
|
||||
e=E pl
|
||||
v=V {{ boo }} pl
|
||||
i={[plural(boo)]}
|
||||
i[key]=i pl
|
||||
|
||||
|
||||
ve=VE {{ boo }} pl
|
||||
vv=VV {{ boo }} pl
|
||||
vi=VI {{ boo }} pl
|
||||
vm=VM {{ boo }} pl
|
||||
|
||||
ie={[plural(boo)]}
|
||||
ie[key]=IE pl
|
||||
iv={[plural(boo)]}
|
||||
iv[key]=IV pl
|
||||
ii={[plural(boo)]}
|
||||
ii[key]=II pl
|
||||
im={[plural(boo)]}
|
||||
im[key]=IM pl
|
||||
|
||||
|
||||
vve=VVE {{ boo }} pl
|
||||
vvv=VVV {{ boo }} pl
|
||||
vvi=VVI {{ boo }} pl
|
||||
vvm=VVM {{ boo }} pl
|
||||
|
||||
vie=VIE {{ boo }} pl
|
||||
viv=VIV {{ boo }} pl
|
||||
vii=VII {{ boo }} pl
|
||||
vim=VIM {{ boo }} pl
|
||||
|
||||
vme=VME {{ boo }} pl
|
||||
vmv=VMV {{ boo }} pl
|
||||
vmi=VMI {{ boo }} pl
|
||||
vmm=VMM {{ boo }} pl
|
||||
|
||||
ive={[plural(boo)]}
|
||||
ive[key]=IVE pl
|
||||
ivv={[plural(boo)]}
|
||||
ivv[key]=IVV pl
|
||||
ivi={[plural(boo)]}
|
||||
ivi[key]=IVI pl
|
||||
ivm={[plural(boo)]}
|
||||
ivm[key]=IVM pl
|
||||
|
||||
iie={[plural(boo)]}
|
||||
iie[key]=IIE pl
|
||||
iiv={[plural(boo)]}
|
||||
iiv[key]=IIV pl
|
||||
iii={[plural(boo)]}
|
||||
iii[key]=III pl
|
||||
iim={[plural(boo)]}
|
||||
iim[key]=IIM pl
|
||||
|
||||
ime={[plural(boo)]}
|
||||
ime[key]=IME pl
|
||||
imv={[plural(boo)]}
|
||||
imv[key]=IMV pl
|
||||
imi={[plural(boo)]}
|
||||
imi[key]=IMI pl
|
||||
imm={[plural(boo)]}
|
||||
imm[key]=IMM pl
|
||||
116
gaia/apps/sharedtest/test/unit/l10n/lib/context/format_test.js
Executable file
116
gaia/apps/sharedtest/test/unit/l10n/lib/context/format_test.js
Executable file
|
|
@ -0,0 +1,116 @@
|
|||
/* global assert:true, it, describe, beforeEach */
|
||||
/* global navigator, __dirname */
|
||||
'use strict';
|
||||
|
||||
if (typeof navigator !== 'undefined') {
|
||||
var L10n = navigator.mozL10n._getInternalAPI();
|
||||
var Context = L10n.Context;
|
||||
var path =
|
||||
'app://sharedtest.gaiamobile.org/test/unit/l10n/lib/context';
|
||||
} else {
|
||||
var assert = require('assert');
|
||||
var Context = require('../../../src/lib/context').Context;
|
||||
var path = __dirname;
|
||||
}
|
||||
|
||||
function assertPromise(promise, expected, done) {
|
||||
promise.then(function(value) {
|
||||
assert.strictEqual(value, expected);
|
||||
}).then(done, done);
|
||||
}
|
||||
|
||||
describe('One fallback locale', function() {
|
||||
var ctx;
|
||||
|
||||
beforeEach(function(done) {
|
||||
ctx = new Context();
|
||||
ctx.resLinks.push(path + '/fixtures/{locale}.properties');
|
||||
ctx.once(done);
|
||||
ctx.registerLocales('en-US', ['pl']);
|
||||
ctx.requestLocales('pl');
|
||||
});
|
||||
|
||||
describe('Translation in the first locale exists and is OK', function() {
|
||||
it('[e]', function(done) {
|
||||
assertPromise(ctx.formatValue('e'), 'E pl', done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ValueError in first locale', function() {
|
||||
describe('Entity exists in second locale:', function() {
|
||||
it('[ve]', function(done) {
|
||||
assertPromise(ctx.formatValue('ve'), 'VE {{ boo }} pl', done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ValueError in second locale:', function() {
|
||||
it('[vv]', function(done) {
|
||||
assertPromise(ctx.formatValue('vv'), 'VV {{ boo }} pl', done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('IndexError in second locale:', function() {
|
||||
it('[vi]', function(done) {
|
||||
assertPromise(ctx.formatValue('vi'), 'VI {{ boo }} pl', done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Entity missing in second locale:', function() {
|
||||
it('[vm]', function(done) {
|
||||
assertPromise(ctx.formatValue('vm'), 'VM {{ boo }} pl', done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('IndexError in first locale', function() {
|
||||
describe('Entity exists in second locale', function() {
|
||||
it('[ie]', function(done) {
|
||||
assertPromise(ctx.formatValue('ie'), 'ie', done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ValueError in second locale', function() {
|
||||
it('[iv]', function(done) {
|
||||
assertPromise(ctx.formatValue('iv'), 'iv', done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('IndexError in second locale', function() {
|
||||
it('[ii]', function(done) {
|
||||
assertPromise(ctx.formatValue('ii'), 'ii', done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Entity missing in second locale:', function() {
|
||||
it('[im]', function(done) {
|
||||
assertPromise(ctx.formatValue('im'), 'im', done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Entity not found in first locale', function() {
|
||||
describe('Entity exists in second locale:', function() {
|
||||
it('[me]', function(done) {
|
||||
assertPromise(ctx.formatValue('me'), 'ME en-US', done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ValueError in second locale:', function() {
|
||||
it('[mv]', function(done) {
|
||||
assertPromise(ctx.formatValue('mv'), 'MV {{ boo }} en-US', done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('IndexError in second locale:', function() {
|
||||
it('[mi]', function(done) {
|
||||
assertPromise(ctx.formatValue('mi'), 'mi', done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Entity missing in second locale:', function() {
|
||||
it('[mm]', function(done) {
|
||||
assertPromise(ctx.formatValue('mm'), 'mm', done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
59
gaia/apps/sharedtest/test/unit/l10n/lib/context/langnego_test.js
Executable file
59
gaia/apps/sharedtest/test/unit/l10n/lib/context/langnego_test.js
Executable file
|
|
@ -0,0 +1,59 @@
|
|||
/* global assert:true, it, describe, beforeEach */
|
||||
/* global navigator */
|
||||
'use strict';
|
||||
|
||||
if (typeof navigator !== 'undefined') {
|
||||
var L10n = navigator.mozL10n._getInternalAPI();
|
||||
var Context = L10n.Context;
|
||||
} else {
|
||||
var assert = require('assert');
|
||||
var Context = require('../../../src/lib/context').Context;
|
||||
}
|
||||
|
||||
describe('Language negotiation without arguments', function() {
|
||||
var ctx;
|
||||
|
||||
beforeEach(function(done) {
|
||||
ctx = new Context();
|
||||
ctx.once(done);
|
||||
ctx.registerLocales('en-US');
|
||||
ctx.requestLocales('en-US');
|
||||
});
|
||||
|
||||
it('used the en-US locale', function() {
|
||||
assert.strictEqual(ctx.supportedLocales.length, 1);
|
||||
assert.strictEqual(ctx.supportedLocales[0], 'en-US');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Language negotiation with arguments', function() {
|
||||
var ctx;
|
||||
|
||||
beforeEach(function(done) {
|
||||
ctx = new Context();
|
||||
ctx.once(done);
|
||||
ctx.registerLocales('en-US', ['pl']);
|
||||
ctx.requestLocales('pl');
|
||||
});
|
||||
|
||||
it('sets the correct fallback chain', function() {
|
||||
assert.strictEqual(ctx.supportedLocales.length, 2);
|
||||
assert.strictEqual(ctx.supportedLocales[0], 'pl');
|
||||
assert.strictEqual(ctx.supportedLocales[1], 'en-US');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Language negotiation with arguments and unsupported', function() {
|
||||
var ctx;
|
||||
beforeEach(function(done) {
|
||||
ctx = new Context();
|
||||
ctx.once(done);
|
||||
ctx.registerLocales('en-US', ['pl']);
|
||||
ctx.requestLocales('fr', 'pl');
|
||||
});
|
||||
it('sets the correct fallback chain', function() {
|
||||
assert.strictEqual(ctx.supportedLocales.length, 2);
|
||||
assert.strictEqual(ctx.supportedLocales[0], 'pl');
|
||||
assert.strictEqual(ctx.supportedLocales[1], 'en-US');
|
||||
});
|
||||
});
|
||||
145
gaia/apps/sharedtest/test/unit/l10n/lib/context/loading_test.js
Executable file
145
gaia/apps/sharedtest/test/unit/l10n/lib/context/loading_test.js
Executable file
|
|
@ -0,0 +1,145 @@
|
|||
/* global assert:true, it, describe, beforeEach */
|
||||
/* global navigator, __dirname */
|
||||
'use strict';
|
||||
|
||||
if (typeof navigator !== 'undefined') {
|
||||
var L10n = navigator.mozL10n._getInternalAPI();
|
||||
var Context = L10n.Context;
|
||||
var path =
|
||||
'app://sharedtest.gaiamobile.org/test/unit/l10n/lib/context';
|
||||
} else {
|
||||
var assert = require('assert');
|
||||
var Context = require('../../../src/lib/context').Context;
|
||||
var path = __dirname;
|
||||
}
|
||||
|
||||
describe('A non-loading context', function() {
|
||||
var ctx;
|
||||
|
||||
beforeEach(function() {
|
||||
ctx = new Context();
|
||||
ctx.resLinks.push(path + '/fixtures/{locale}.properties');
|
||||
});
|
||||
|
||||
it('should throw on get', function() {
|
||||
assert.throws(function(){
|
||||
ctx.get('dummy');
|
||||
}, /Context not ready/);
|
||||
});
|
||||
|
||||
it('should throw on getEntity', function() {
|
||||
assert.throws(function(){
|
||||
ctx.getEntity('dummy');
|
||||
}, /Context not ready/);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('A loading, non-ready context', function() {
|
||||
var ctx;
|
||||
beforeEach(function() {
|
||||
ctx = new Context();
|
||||
ctx.resLinks.push(path + '/fixtures/{locale}.properties');
|
||||
ctx.registerLocales('en-US');
|
||||
ctx.requestLocales('en-US');
|
||||
});
|
||||
|
||||
it('should throw on requestLocales', function() {
|
||||
assert.throws(function(){
|
||||
ctx.requestLocales('en-US');
|
||||
}, /Context not ready/);
|
||||
});
|
||||
|
||||
it('should throw on get', function() {
|
||||
assert.throws(function(){
|
||||
ctx.get('dummy');
|
||||
}, /Context not ready/);
|
||||
});
|
||||
|
||||
it('should throw on getEntity', function() {
|
||||
assert.throws(function(){
|
||||
ctx.getEntity('dummy');
|
||||
}, /Context not ready/);
|
||||
});
|
||||
|
||||
it('should throw on requestLocales', function() {
|
||||
assert.throws(function(){
|
||||
ctx.requestLocales('en-US');
|
||||
}, /Context not ready/);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('A loading, ready context', function() {
|
||||
var ctx;
|
||||
|
||||
beforeEach(function(done) {
|
||||
ctx = new Context();
|
||||
ctx.resLinks.push(path + '/fixtures/{locale}.properties');
|
||||
ctx.once(done);
|
||||
ctx.registerLocales('en-US');
|
||||
ctx.requestLocales('en-US');
|
||||
});
|
||||
|
||||
it('should not throw on get of a known entity', function() {
|
||||
assert.doesNotThrow(function(){
|
||||
ctx.get('foo');
|
||||
});
|
||||
assert.strictEqual(ctx.get('foo'), 'Foo en-US');
|
||||
});
|
||||
|
||||
it('should not throw on get of an unknown entity', function() {
|
||||
assert.doesNotThrow(function(){
|
||||
ctx.get('missing');
|
||||
});
|
||||
assert.strictEqual(ctx.get('missing'), '');
|
||||
});
|
||||
|
||||
it('should not throw on getEntity of a known entity', function() {
|
||||
assert.doesNotThrow(function(){
|
||||
ctx.getEntity('foo');
|
||||
});
|
||||
assert.strictEqual(ctx.getEntity('foo').value, 'Foo en-US');
|
||||
});
|
||||
|
||||
it('should not throw on getEntity of an unknown entity', function() {
|
||||
assert.doesNotThrow(function(){
|
||||
ctx.getEntity('missing');
|
||||
});
|
||||
assert.strictEqual(ctx.getEntity('missing'), '');
|
||||
});
|
||||
|
||||
it('should not throw on requestLocales', function(done) {
|
||||
ctx.once(function() {
|
||||
assert.strictEqual(ctx.isReady, true);
|
||||
done();
|
||||
});
|
||||
assert.doesNotThrow(function(){
|
||||
ctx.requestLocales('pl', 'en-US');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('A loading, ready context', function() {
|
||||
var ctx;
|
||||
|
||||
beforeEach(function(done) {
|
||||
ctx = new Context();
|
||||
ctx.resLinks.push(path + '/fixtures/{locale}.properties');
|
||||
ctx.once(done);
|
||||
ctx.registerLocales('en-US');
|
||||
ctx.requestLocales('en-US');
|
||||
});
|
||||
|
||||
it('should return translations for the built fallback chain ', function() {
|
||||
// Bug 942183 - Error when localizeNode is done quickly after
|
||||
// requestLocales https://bugzil.la/942183
|
||||
// Changing locales triggers the build process for the 'pl' locale.
|
||||
// However, synchronous methods called right after the change should still
|
||||
// return translations for the previous fallback chain
|
||||
ctx.requestLocales('pl');
|
||||
assert.strictEqual(ctx.get('foo'), 'Foo en-US');
|
||||
});
|
||||
|
||||
});
|
||||
49
gaia/apps/sharedtest/test/unit/l10n/lib/context/resources_test.js
Executable file
49
gaia/apps/sharedtest/test/unit/l10n/lib/context/resources_test.js
Executable file
|
|
@ -0,0 +1,49 @@
|
|||
/* global assert:true, it, describe, beforeEach */
|
||||
/* global navigator, __dirname */
|
||||
'use strict';
|
||||
|
||||
if (typeof navigator !== 'undefined') {
|
||||
var L10n = navigator.mozL10n._getInternalAPI();
|
||||
var Context = L10n.Context;
|
||||
var path = 'app://sharedtest.gaiamobile.org/test/unit/l10n/context';
|
||||
} else {
|
||||
var assert = require('assert');
|
||||
var Context = require('../../../src/lib/context').Context;
|
||||
var path = __dirname;
|
||||
}
|
||||
|
||||
describe('Missing resources', function() {
|
||||
var ctx;
|
||||
|
||||
beforeEach(function(done) {
|
||||
ctx = new Context();
|
||||
ctx.resLinks.push(path + '/fixtures/en-US.properties');
|
||||
ctx.resLinks.push(path + '/fixtures/missing.properties');
|
||||
ctx.once(done);
|
||||
ctx.registerLocales('en-US');
|
||||
ctx.requestLocales('en-US');
|
||||
});
|
||||
|
||||
it('should get ready', function() {
|
||||
assert.strictEqual(ctx.isReady, true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('No valid resources', function() {
|
||||
var ctx;
|
||||
|
||||
beforeEach(function(done) {
|
||||
ctx = new Context();
|
||||
ctx.resLinks.push(path + '/fixtures/missing.properties');
|
||||
ctx.resLinks.push(path + '/fixtures/another.properties');
|
||||
ctx.once(done);
|
||||
ctx.registerLocales('en-US');
|
||||
ctx.requestLocales('en-US');
|
||||
});
|
||||
|
||||
it('should get ready', function() {
|
||||
assert.strictEqual(ctx.isReady, true);
|
||||
});
|
||||
|
||||
});
|
||||
66
gaia/apps/sharedtest/test/unit/l10n/lib/context/retranslation_test.js
Executable file
66
gaia/apps/sharedtest/test/unit/l10n/lib/context/retranslation_test.js
Executable file
|
|
@ -0,0 +1,66 @@
|
|||
/* global it, describe, beforeEach */
|
||||
/* global navigator */
|
||||
'use strict';
|
||||
|
||||
if (typeof navigator !== 'undefined') {
|
||||
var L10n = navigator.mozL10n._getInternalAPI();
|
||||
var Context = L10n.Context;
|
||||
} else {
|
||||
var Context = require('../../../src/lib/context').Context;
|
||||
}
|
||||
|
||||
describe('ctx.ready', function() {
|
||||
var ctx;
|
||||
|
||||
beforeEach(function() {
|
||||
ctx = new Context();
|
||||
ctx.registerLocales('en-US', ['pl']);
|
||||
});
|
||||
|
||||
it('should fire asynchronously when context is ready', function(done) {
|
||||
ctx.ready(function() {
|
||||
done();
|
||||
});
|
||||
ctx.requestLocales('pl');
|
||||
});
|
||||
|
||||
it('should fire asynchronously when language changes', function(done) {
|
||||
var now = false;
|
||||
ctx.ready(function() {
|
||||
if (now) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
ctx.once(function() {
|
||||
now = true;
|
||||
ctx.requestLocales('pl');
|
||||
});
|
||||
ctx.requestLocales('en-US');
|
||||
});
|
||||
|
||||
it('should fire synchronously when context is ready', function(done) {
|
||||
ctx.once(function() {
|
||||
ctx.ready(function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
ctx.requestLocales('en-US');
|
||||
});
|
||||
|
||||
it('should fire synchronously when language changes', function(done) {
|
||||
var now = false;
|
||||
ctx.once(function() {
|
||||
ctx.ready(function() {
|
||||
if (now) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
setTimeout(function() {
|
||||
now = true;
|
||||
ctx.requestLocales('pl');
|
||||
});
|
||||
});
|
||||
ctx.requestLocales('en-US');
|
||||
});
|
||||
|
||||
});
|
||||
91
gaia/apps/sharedtest/test/unit/l10n/lib/parser/properties/dos_test.js
Executable file
91
gaia/apps/sharedtest/test/unit/l10n/lib/parser/properties/dos_test.js
Executable file
|
|
@ -0,0 +1,91 @@
|
|||
/* global assert:true, it, beforeEach, describe */
|
||||
/* global navigator */
|
||||
/* jshint -W101 */
|
||||
'use strict';
|
||||
|
||||
if (typeof navigator !== 'undefined') {
|
||||
var L10n = navigator.mozL10n._getInternalAPI();
|
||||
} else {
|
||||
var assert = require('assert');
|
||||
var L10n = {
|
||||
PropertiesParser: require('../../../../src/lib/format/properties/parser')
|
||||
};
|
||||
}
|
||||
|
||||
describe('Quadratic Blowup', function(){
|
||||
var source;
|
||||
|
||||
beforeEach(function() {
|
||||
source = [
|
||||
'# Project Gutenberg\'s Alice\'s Adventures in Wonderland,',
|
||||
'# by Lewis Carroll',
|
||||
'#',
|
||||
'# This eBook is for the use of anyone anywhere at no cost and with',
|
||||
'# almost no restrictions whatsoever. You may copy it, give it away',
|
||||
'# or re-use it under the terms of the Project Gutenberg License',
|
||||
'# included with this eBook or online at www.gutenberg.org',
|
||||
|
||||
'alice=\\',
|
||||
|
||||
' CHAPTER I. Down the Rabbit-Hole\\',
|
||||
|
||||
' Alice was beginning to get very tired of sitting by her sister on\\',
|
||||
' the bank, and of having nothing to do: once or twice she had peeped\\',
|
||||
' into the book her sister was reading, but it had no pictures or\\',
|
||||
' conversations in it, \'and what is the use of a book,\' thought\\',
|
||||
' Alice \'without pictures or conversation?\'\\',
|
||||
|
||||
' So she was considering in her own mind (as well as she could, for\\',
|
||||
' the hot day made her feel very sleepy and stupid), whether the\\',
|
||||
' pleasure of making a daisy-chain would be worth the trouble of\\',
|
||||
' getting up and picking the daisies, when suddenly a White Rabbit\\',
|
||||
' with pink eyes ran close by her.\\',
|
||||
|
||||
' There was nothing so VERY remarkable in that; nor did Alice think\\',
|
||||
' it so VERY much out of the way to hear the Rabbit say to itself,\\',
|
||||
' \'Oh dear! Oh dear! I shall be late!\' (when she thought it over\\',
|
||||
' afterwards, it occurred to her that she ought to have wondered at\\',
|
||||
' this, but at the time it all seemed quite natural); but when the\\',
|
||||
' Rabbit actually TOOK A WATCH OUT OF ITS WAISTCOAT-POCKET, and\\',
|
||||
' looked at it, and then hurried on, Alice started to her feet, for\\',
|
||||
' it flashed across her mind that she had never before seen a rabbit\\',
|
||||
' with either a waistcoat-pocket, or a watch to take out of it, and\\',
|
||||
' burning with curiosity, she ran across the field after it, and\\',
|
||||
' fortunately was just in time to see it pop down a large rabbit-hole\\',
|
||||
' under the hedge.\\',
|
||||
|
||||
' In another moment down went Alice after it, never once considering\\',
|
||||
' how in the world she was to get out again.\\',
|
||||
|
||||
' The rabbit-hole went straight on like a tunnel for some way, and\\',
|
||||
' then dipped suddenly down, so suddenly that Alice had not a moment\\',
|
||||
' to think about stopping herself before she found herself falling\\',
|
||||
' down a very deep well.',
|
||||
|
||||
'malice=\\',
|
||||
' {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} \\',
|
||||
' {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} \\',
|
||||
' {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} \\',
|
||||
' {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} \\',
|
||||
' {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} \\',
|
||||
' {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} \\',
|
||||
' {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} \\',
|
||||
' {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} \\',
|
||||
' {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} \\',
|
||||
' {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} \\',
|
||||
' {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} \\',
|
||||
' {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} \\',
|
||||
' {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} \\',
|
||||
' {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} \\',
|
||||
' {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} \\',
|
||||
' {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} \\',
|
||||
' {{alice}} {{alice}} {{alice}} {{alice}} {{alice}} {{alice}}'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('throws', function() {
|
||||
assert.throws(function() {
|
||||
L10n.PropertiesParser.parse(null, source);
|
||||
}, /too many placeables/i);
|
||||
});
|
||||
});
|
||||
134
gaia/apps/sharedtest/test/unit/l10n/lib/parser/properties/parser_test.js
Executable file
134
gaia/apps/sharedtest/test/unit/l10n/lib/parser/properties/parser_test.js
Executable file
|
|
@ -0,0 +1,134 @@
|
|||
/* global it, assert:true, describe */
|
||||
/* global navigator */
|
||||
'use strict';
|
||||
|
||||
if (typeof navigator !== 'undefined') {
|
||||
var L10n = navigator.mozL10n._getInternalAPI();
|
||||
} else {
|
||||
var assert = require('assert');
|
||||
var L10n = {
|
||||
PropertiesParser: require('../../../../src/lib/format/properties/parser')
|
||||
};
|
||||
}
|
||||
|
||||
describe('L10n Parser', function() {
|
||||
|
||||
it('string value', function() {
|
||||
var ast = L10n.PropertiesParser.parse(null, 'id = string');
|
||||
assert.strictEqual(ast[0].$v, 'string');
|
||||
});
|
||||
|
||||
it('empty value', function() {
|
||||
var ast = L10n.PropertiesParser.parse(null, 'id =');
|
||||
assert.equal(ast[0].$v, '');
|
||||
});
|
||||
|
||||
it('empty value with white spaces', function() {
|
||||
var ast = L10n.PropertiesParser.parse(null, 'id = ');
|
||||
assert.equal(ast[0].$v, '');
|
||||
});
|
||||
|
||||
it('basic errors', function() {
|
||||
var strings = [
|
||||
'',
|
||||
'id',
|
||||
'id ',
|
||||
'+id',
|
||||
'=id',
|
||||
];
|
||||
|
||||
for (var i in strings) {
|
||||
if (strings.hasOwnProperty(i)) {
|
||||
var ast = L10n.PropertiesParser.parse(null, strings[i]);
|
||||
assert.equal(Object.keys(ast).length, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('basic attributes', function() {
|
||||
var ast = L10n.PropertiesParser.parse(null, 'id.attr1 = foo');
|
||||
assert.equal(ast[0].attr1, 'foo');
|
||||
});
|
||||
|
||||
it('empty attribute', function() {
|
||||
var ast = L10n.PropertiesParser.parse(null, 'id.attr1 =');
|
||||
assert.equal(ast[0].attr1, '');
|
||||
});
|
||||
|
||||
it('empty attribute with white spaces', function() {
|
||||
var ast = L10n.PropertiesParser.parse(null, 'id.attr1 = ');
|
||||
assert.equal(ast[0].attr1, '');
|
||||
});
|
||||
|
||||
it('attribute errors', function() {
|
||||
var strings = [
|
||||
['key.foo.bar = foo', /Nested attributes are not supported./],
|
||||
['key.$attr = foo', /Attribute can't start/],
|
||||
];
|
||||
|
||||
for (var i in strings) {
|
||||
if (strings.hasOwnProperty(i)) {
|
||||
|
||||
/* jshint -W083 */
|
||||
assert.throws(function() {
|
||||
L10n.PropertiesParser.parse(null, strings[i][0]);
|
||||
}, strings[i][1]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('plural macro', function() {
|
||||
var ast = L10n.PropertiesParser.parse(null,
|
||||
'id = {[ plural(m) ]} \nid[one] = foo');
|
||||
assert.ok(ast[0].$v instanceof Object);
|
||||
assert.equal(ast[0].$v.one, 'foo');
|
||||
assert.equal(ast[0].$x.length, 2);
|
||||
assert.equal(ast[0].$x[0].t, 'idOrVar');
|
||||
assert.equal(ast[0].$x[0].v, 'plural');
|
||||
assert.equal(ast[0].$x[1], 'm');
|
||||
});
|
||||
|
||||
it('plural macro errors', function() {
|
||||
var strings = [
|
||||
'id = {[ plural(m) ] \nid[one] = foo',
|
||||
'id = {[ plural(m) \nid[one] = foo',
|
||||
'id = { plural(m) ]} \nid[one] = foo',
|
||||
'id = plural(m) ]} \nid[one] = foo',
|
||||
'id = {[ plural(m ]} \nid[one] = foo',
|
||||
'id = {[ pluralm) ]} \nid[one] = foo',
|
||||
|
||||
];
|
||||
var errorsThrown = 0;
|
||||
|
||||
for (var i in strings) {
|
||||
if (strings.hasOwnProperty(i)) {
|
||||
try {
|
||||
L10n.PropertiesParser.parse(null, strings[i]);
|
||||
} catch (e) {
|
||||
errorsThrown += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert.equal(errorsThrown, strings.length);
|
||||
});
|
||||
|
||||
it('comment', function() {
|
||||
var ast = L10n.PropertiesParser.parse(null, '#test');
|
||||
assert.equal(Object.keys(ast).length, 0);
|
||||
});
|
||||
|
||||
it('comment errors', function() {
|
||||
var strings = [
|
||||
' # foo',
|
||||
' ## foo',
|
||||
'f# foo',
|
||||
];
|
||||
for (var i in strings) {
|
||||
if (strings.hasOwnProperty(i)) {
|
||||
var ast = L10n.PropertiesParser.parse(null, strings[i]);
|
||||
assert.equal(Object.keys(ast).length, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
198
gaia/apps/sharedtest/test/unit/l10n/lib/pseudo_test.js
Executable file
198
gaia/apps/sharedtest/test/unit/l10n/lib/pseudo_test.js
Executable file
|
|
@ -0,0 +1,198 @@
|
|||
/* global it, assert:true, describe, before, beforeEach */
|
||||
/* global navigator */
|
||||
'use strict';
|
||||
|
||||
var assert;
|
||||
var PropertiesParser;
|
||||
|
||||
describe('pseudo strategy', function() {
|
||||
var PSEUDO, walkContent, strategy, source, ast, walked;
|
||||
|
||||
before(function(done) {
|
||||
if (typeof navigator !== 'undefined') {
|
||||
require('/shared/js/l10n.js', function() {
|
||||
PSEUDO = navigator.mozL10n.qps;
|
||||
var L10n = navigator.mozL10n._getInternalAPI();
|
||||
PropertiesParser = L10n.PropertiesParser;
|
||||
walkContent = L10n.walkContent;
|
||||
done();
|
||||
});
|
||||
} else {
|
||||
assert = require('assert');
|
||||
PSEUDO = require('../../src/lib/pseudo').PSEUDO;
|
||||
walkContent = require('../../src/lib/util').walkContent;
|
||||
PropertiesParser =
|
||||
require('../../src/lib/format/properties/parser');
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(function() {
|
||||
ast = PropertiesParser.parse(null, source);
|
||||
walked = pseudolocalize(ast, strategy);
|
||||
});
|
||||
|
||||
function pseudolocalize(arr, strategy) {
|
||||
var obj = {};
|
||||
arr.forEach(function(val) {
|
||||
obj[val.$i] = walkContent(val, strategy);
|
||||
});
|
||||
return obj;
|
||||
}
|
||||
|
||||
var foo = {t: 'idOrVar', v: 'foo'};
|
||||
var bar = {t: 'idOrVar', v: 'bar'};
|
||||
|
||||
describe('accented English', function(){
|
||||
|
||||
before(function() {
|
||||
strategy = PSEUDO['fr-x-psaccent'].translate;
|
||||
source = [
|
||||
'foo=Foo',
|
||||
|
||||
'bar={[ plural(n) ]}',
|
||||
'bar[one]=One',
|
||||
'bar[two]=Two',
|
||||
'bar[few]=Few',
|
||||
'bar[many]=Many',
|
||||
'bar[other]=Other',
|
||||
|
||||
'baz.attr=An attribute',
|
||||
'baz.attrComplex=An attribute referencing {{ foo }}',
|
||||
|
||||
'templateVar={name} wrote',
|
||||
'dateFormat=%A, %b %Eb',
|
||||
'twoPlaceables1={{ foo }} {{ bar }}',
|
||||
'twoPlaceables2=Foo {{ foo }} and bar {{ bar }}',
|
||||
'parens1=({{ foo }}) {{ bar }}',
|
||||
'parens2=Foo ({{foo}}) [and/or {{bar}}]',
|
||||
'parens3=Foo (and) bar',
|
||||
'unicode=Foo \u0066\u006f\u006f\u0020',
|
||||
'nonascii=Naïve coöperation résumé dæmon phœnix',
|
||||
'html1=visit <a>url</a>',
|
||||
'html2=type <input placeholder="your name"/>',
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('walks the value', function(){
|
||||
assert.strictEqual(walked.foo.$v, 'Ƒǿǿǿǿ');
|
||||
|
||||
assert.strictEqual(walked.bar.$v.one, 'Ǿǿƞḗḗ');
|
||||
assert.strictEqual(walked.bar.$v.two, 'Ŧẇǿǿ');
|
||||
assert.strictEqual(walked.bar.$v.few, 'Ƒḗḗẇ');
|
||||
assert.strictEqual(walked.bar.$v.many, 'Ḿȧȧƞẏ');
|
||||
assert.strictEqual(walked.bar.$v.other, 'Ǿǿŧħḗḗř');
|
||||
|
||||
assert.strictEqual(walked.baz.attr, 'Ȧȧƞ ȧȧŧŧřīīƀŭŭŧḗḗ');
|
||||
assert.deepEqual(
|
||||
walked.baz.attrComplex, ['Ȧȧƞ ȧȧŧŧřīīƀŭŭŧḗḗ řḗḗƒḗḗřḗḗƞƈīīƞɠ ', foo]);
|
||||
|
||||
assert.strictEqual(walked.templateVar.$v, '{name} ẇřǿǿŧḗḗ');
|
||||
assert.strictEqual(walked.dateFormat.$v, '%A, %b %Eb');
|
||||
assert.deepEqual(walked.twoPlaceables1.$v, [foo, ' ', bar]);
|
||||
assert.deepEqual(
|
||||
walked.twoPlaceables2.$v, ['Ƒǿǿǿǿ ', foo, ' ȧȧƞḓ ƀȧȧř ', bar]);
|
||||
assert.deepEqual(
|
||||
walked.parens1.$v, ['(', foo, ') ', bar]);
|
||||
assert.deepEqual(
|
||||
walked.parens2.$v, ['Ƒǿǿǿǿ (', foo, ') [ȧȧƞḓ/ǿǿř ', bar, ']']);
|
||||
assert.strictEqual(walked.parens3.$v, 'Ƒǿǿǿǿ (ȧȧƞḓ) ƀȧȧř');
|
||||
assert.strictEqual(walked.unicode.$v, 'Ƒǿǿǿǿ ƒǿǿǿǿ ');
|
||||
assert.strictEqual(
|
||||
walked.nonascii.$v,
|
||||
'Ƞȧȧïṽḗḗ ƈǿǿöƥḗḗřȧȧŧīīǿǿƞ řéşŭŭḿé ḓæḿǿǿƞ ƥħœƞīīẋ');
|
||||
assert.strictEqual(walked.html1.$v, 'ṽīīşīīŧ <a>ŭŭřŀ</a>');
|
||||
assert.strictEqual(
|
||||
walked.html2.$v, 'ŧẏƥḗḗ <input placeholder="your name"/>');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('bidi English', function(){
|
||||
/* jshint -W100 */
|
||||
|
||||
before(function() {
|
||||
strategy = PSEUDO['ar-x-psbidi'].translate;
|
||||
source = [
|
||||
'foo=Foo',
|
||||
|
||||
'bar={[ plural(n) ]}',
|
||||
'bar[one]=One',
|
||||
'bar[two]=Two',
|
||||
'bar[few]=Few',
|
||||
'bar[many]=Many',
|
||||
'bar[other]=Other',
|
||||
|
||||
'baz.attr=An attribute',
|
||||
'baz.attrComplex=An attribute referencing {{ foo }}',
|
||||
|
||||
'templateVar={name} wrote',
|
||||
'dateFormat=%A, %b %Eb',
|
||||
'twoPlaceables1={{ foo }} {{ bar }}',
|
||||
'twoPlaceables2=Foo {{ foo }} and bar {{ bar }}',
|
||||
'parens1=({{ foo }}) {{ bar }}',
|
||||
'parens2=Foo ({{foo}}) [and/or {{bar}}]',
|
||||
'parens3=Foo (and) bar',
|
||||
'unicode=Foo \u0066\u006f\u006f\u0020',
|
||||
'nonascii=Naïve coöperation résumé dæmon phœnix',
|
||||
'html1=visit <a>url</a>',
|
||||
'html2=type <input placeholder="your name"/>',
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('walks the value', function(){
|
||||
assert.strictEqual(walked.foo.$v, 'ɟoo');
|
||||
|
||||
assert.strictEqual(walked.bar.$v.one, 'Ouǝ');
|
||||
assert.strictEqual(walked.bar.$v.two, '⊥ʍo');
|
||||
assert.strictEqual(walked.bar.$v.few, 'ɟǝʍ');
|
||||
assert.strictEqual(walked.bar.$v.many, 'Wɐuʎ');
|
||||
assert.strictEqual(walked.bar.$v.other, 'Oʇɥǝɹ');
|
||||
|
||||
assert.strictEqual(
|
||||
walked.baz.attr, '∀u ɐʇʇɹıqnʇǝ');
|
||||
assert.deepEqual(
|
||||
walked.baz.attrComplex,
|
||||
['∀u ɐʇʇɹıqnʇǝ ɹǝɟǝɹǝuɔıuƃ ',
|
||||
foo]);
|
||||
|
||||
assert.strictEqual(walked.templateVar.$v, '{name} ʍɹoʇǝ');
|
||||
assert.strictEqual(walked.dateFormat.$v, '%A, %b %Eb');
|
||||
assert.deepEqual(walked.twoPlaceables1.$v, [foo, ' ', bar]);
|
||||
assert.deepEqual(
|
||||
walked.twoPlaceables2.$v,
|
||||
['ɟoo ', foo, ' ɐup qɐɹ ',
|
||||
bar]);
|
||||
assert.deepEqual(
|
||||
walked.parens1.$v, ['(', foo, ') ', bar]);
|
||||
assert.deepEqual(
|
||||
walked.parens2.$v,
|
||||
['ɟoo (', foo, ') [ɐup/oɹ ',
|
||||
bar, ']']);
|
||||
assert.strictEqual(
|
||||
walked.parens3.$v,
|
||||
'ɟoo (ɐup) qɐɹ');
|
||||
assert.strictEqual(
|
||||
walked.unicode.$v,
|
||||
'ɟoo ɟoo ');
|
||||
assert.strictEqual(
|
||||
walked.html1.$v, 'ʌısıʇ <a>nɹʅ</a>');
|
||||
assert.strictEqual(
|
||||
walked.html2.$v,
|
||||
'ʇʎdǝ <input placeholder="your name"/>');
|
||||
});
|
||||
|
||||
// XXX this requires Unicode support for JavaSript RegExp objects
|
||||
// https://bugzil.la/258974
|
||||
it.skip('walks the value', function(){
|
||||
var walked = walkContent(ast, strategy);
|
||||
assert.strictEqual(
|
||||
walked.nonascii,
|
||||
'Nɐïʌǝ ɔoödǝɹɐʇıou ' +
|
||||
'ɹésnɯé pæɯou ' +
|
||||
'dɥœuıx');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
127
gaia/apps/sharedtest/test/unit/l10n/lib/resolver/attributes_test.js
Executable file
127
gaia/apps/sharedtest/test/unit/l10n/lib/resolver/attributes_test.js
Executable file
|
|
@ -0,0 +1,127 @@
|
|||
/* global assert:true, it, before, beforeEach, describe, requireApp */
|
||||
'use strict';
|
||||
|
||||
if (typeof navigator !== 'undefined') {
|
||||
requireApp('sharedtest/test/unit/l10n/lib/resolver/header.js');
|
||||
} else {
|
||||
var assert = require('assert');
|
||||
var Resolver = require('./header').Resolver;
|
||||
var createEntries = require('./header').createEntries;
|
||||
}
|
||||
|
||||
describe('Attributes', function(){
|
||||
var source, env;
|
||||
|
||||
beforeEach(function() {
|
||||
env = createEntries(source);
|
||||
});
|
||||
|
||||
describe('with string values', function(){
|
||||
|
||||
before(function() {
|
||||
source = [
|
||||
'foo=Foo',
|
||||
'foo.attr=An attribute',
|
||||
'foo.attrComplex=An attribute referencing {{ bar }}',
|
||||
'bar=Bar'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('returns the value', function(){
|
||||
var formatted = Resolver.format(null, env.foo.attrs.attr);
|
||||
assert.strictEqual(formatted[1], 'An attribute');
|
||||
});
|
||||
|
||||
it('returns the value with a placeable', function(){
|
||||
var formatted = Resolver.format(null, env.foo.attrs.attrComplex);
|
||||
assert.strictEqual(formatted[1], 'An attribute referencing Bar');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('with hash values', function(){
|
||||
|
||||
before(function() {
|
||||
source = [
|
||||
'update=Update',
|
||||
'update.title={[ plural(n) ]}',
|
||||
'update.title[one]=One update available'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('returns the value of the entity', function(){
|
||||
var formatted = Resolver.format(null, env.update);
|
||||
assert.strictEqual(formatted[1], 'Update');
|
||||
});
|
||||
|
||||
it('returns the value of the attribute\'s member', function(){
|
||||
var formatted = Resolver.format({n: 1}, env.update.attrs.title);
|
||||
assert.strictEqual(formatted[1], 'One update available');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('with hash values, on entities with hash values ', function(){
|
||||
|
||||
before(function() {
|
||||
source = [
|
||||
'update={[ plural(n) ]}',
|
||||
'update[one]=One update',
|
||||
'update[other]={{ n }} updates',
|
||||
'update.title={[ plural(k) ]}',
|
||||
'update.title[one]=One update title',
|
||||
'update.title[other]={{ k }} updates title'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('returns the value of the entity', function(){
|
||||
var formatted = Resolver.format({n: 1, k: 2}, env.update);
|
||||
assert.strictEqual(formatted[1], 'One update');
|
||||
});
|
||||
|
||||
it('returns the value of the attribute', function(){
|
||||
var formatted = Resolver.format({n: 1, k: 2}, env.update.attrs.title);
|
||||
assert.strictEqual(formatted[1], '2 updates title');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('with relative self-references', function(){
|
||||
|
||||
before(function() {
|
||||
source = [
|
||||
'brandName=Firefox',
|
||||
'brandName.title=Mozilla {{ brandName }}'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('returns the value of the entity', function(){
|
||||
var value = Resolver.format(null, env.brandName)[1];
|
||||
assert.strictEqual(value, 'Firefox');
|
||||
});
|
||||
|
||||
it('returns the value of the attribute', function(){
|
||||
var attr = Resolver.format(null, env.brandName.attrs.title)[1];
|
||||
assert.strictEqual(attr, 'Mozilla Firefox');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('with cyclic self-references', function(){
|
||||
|
||||
before(function() {
|
||||
source = [
|
||||
'brandName=Firefox',
|
||||
'brandName.title=Mozilla {{ brandName.title }}'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('returns the raw string of the attribute', function(){
|
||||
var attr = Resolver.format(null, env.brandName.attrs.title)[1];
|
||||
assert.strictEqual(attr, 'Mozilla {{ brandName.title }}');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
345
gaia/apps/sharedtest/test/unit/l10n/lib/resolver/ctxdata_test.js
Executable file
345
gaia/apps/sharedtest/test/unit/l10n/lib/resolver/ctxdata_test.js
Executable file
|
|
@ -0,0 +1,345 @@
|
|||
/* global assert:true, it, before, beforeEach, describe, requireApp */
|
||||
'use strict';
|
||||
|
||||
if (typeof navigator !== 'undefined') {
|
||||
requireApp('sharedtest/test/unit/l10n/lib/resolver/header.js');
|
||||
} else {
|
||||
var assert = require('assert');
|
||||
var Resolver = require('./header').Resolver;
|
||||
var createEntries = require('./header').createEntries;
|
||||
}
|
||||
|
||||
describe('Context data', function(){
|
||||
var source, ctxdata, env;
|
||||
|
||||
beforeEach(function() {
|
||||
env = createEntries(source);
|
||||
});
|
||||
|
||||
describe('in entities', function(){
|
||||
|
||||
before(function() {
|
||||
ctxdata = {
|
||||
unreadNotifications: 3,
|
||||
foo: 'Foo'
|
||||
};
|
||||
source = [
|
||||
'unread=Unread notifications: {{ unreadNotifications }}',
|
||||
'unreadPlural={[ plural(unreadNotifications) ]}',
|
||||
'unreadPlural[one]=One unread notification',
|
||||
'unreadPlural[other]={{ unreadNotifications}} unread notifications',
|
||||
'foo=Bar',
|
||||
'useFoo={{ foo }}'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('can be referenced from strings', function() {
|
||||
var value = Resolver.format(ctxdata, env.unread)[1];
|
||||
assert.strictEqual(value, 'Unread notifications: 3');
|
||||
});
|
||||
|
||||
it('can be passed as argument to a macro', function() {
|
||||
var value = Resolver.format(ctxdata, env.unreadPlural)[1];
|
||||
assert.strictEqual(value, '3 unread notifications');
|
||||
});
|
||||
|
||||
it('takes priority over entities of the same name', function() {
|
||||
var value = Resolver.format(ctxdata, env.useFoo)[1];
|
||||
assert.strictEqual(value, 'Foo');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('and simple errors', function(){
|
||||
|
||||
before(function() {
|
||||
ctxdata = {
|
||||
nested: {
|
||||
}
|
||||
};
|
||||
source = [
|
||||
'missingReference={{ missing }}',
|
||||
'nestedReference={{ nested }}',
|
||||
'watchReference={{ watch }}',
|
||||
'hasOwnPropertyReference={{ hasOwnProperty }}',
|
||||
'isPrototypeOfReference={{ isPrototypeOf }}',
|
||||
'toStringReference={{ toString }}',
|
||||
'protoReference={{ __proto__ }}',
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('returns the raw string when a missing property of ctxdata is ' +
|
||||
'referenced', function(){
|
||||
var value = Resolver.format(ctxdata, env.missingReference)[1];
|
||||
assert.strictEqual(value, '{{ missing }}');
|
||||
});
|
||||
|
||||
it('returns the raw string when an object is referenced', function(){
|
||||
var value = Resolver.format(ctxdata, env.nestedReference)[1];
|
||||
assert.strictEqual(value, '{{ nested }}');
|
||||
});
|
||||
|
||||
it('returns the raw string when watch is referenced', function(){
|
||||
var value = Resolver.format(ctxdata, env.watchReference)[1];
|
||||
assert.strictEqual(value, '{{ watch }}');
|
||||
});
|
||||
|
||||
it('returns the raw string when hasOwnProperty is referenced', function(){
|
||||
var value = Resolver.format(ctxdata, env.hasOwnPropertyReference)[1];
|
||||
assert.strictEqual(value, '{{ hasOwnProperty }}');
|
||||
});
|
||||
|
||||
it('returns the raw string when isPrototypeOf is referenced', function(){
|
||||
var value = Resolver.format(ctxdata, env.isPrototypeOfReference)[1];
|
||||
assert.strictEqual(value, '{{ isPrototypeOf }}');
|
||||
});
|
||||
|
||||
it('returns the raw string when toString is referenced', function(){
|
||||
var value = Resolver.format(ctxdata, env.toStringReference)[1];
|
||||
assert.strictEqual(value, '{{ toString }}');
|
||||
});
|
||||
|
||||
it('returns the raw string when __proto__ is referenced', function(){
|
||||
var value = Resolver.format(ctxdata, env.protoReference)[1];
|
||||
assert.strictEqual(value, '{{ __proto__ }}');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('and strings', function(){
|
||||
|
||||
before(function() {
|
||||
ctxdata = {
|
||||
str: 'string',
|
||||
num: '1'
|
||||
};
|
||||
source = [
|
||||
'stringProp={{ str }}',
|
||||
'stringIndex={[ plural(str) ]}',
|
||||
'stringIndex[one]=One',
|
||||
'stringNumProp={{ num }}',
|
||||
'stringNumIndex={[ plural(num) ]}',
|
||||
'stringNumIndex[one]=One'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('returns a string value', function(){
|
||||
assert.strictEqual(
|
||||
Resolver.format(ctxdata, env.stringProp)[1], 'string');
|
||||
});
|
||||
|
||||
it('throws when used in a macro', function(){
|
||||
assert.throws(function() {
|
||||
Resolver.format(ctxdata, env.stringIndex);
|
||||
}, 'Unresolvable value');
|
||||
});
|
||||
|
||||
it('digit returns a string value', function(){
|
||||
assert.strictEqual(
|
||||
Resolver.format(ctxdata, env.stringNumProp)[1], '1');
|
||||
});
|
||||
|
||||
it('digit throws when used in a macro', function(){
|
||||
assert.throws(function() {
|
||||
Resolver.format(ctxdata, env.stringNumIndex);
|
||||
}, 'Unresolvable value');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('and numbers', function(){
|
||||
|
||||
before(function() {
|
||||
ctxdata = {
|
||||
num: 1,
|
||||
nan: NaN
|
||||
};
|
||||
source = [
|
||||
'numProp={{ num }}',
|
||||
'numIndex={[ plural(num) ]}',
|
||||
'numIndex[one]=One',
|
||||
'nanProp={{ nan }}',
|
||||
'nanIndex={[ plural(nan) ]}',
|
||||
'nanIndex[one]=One'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('returns a number value', function(){
|
||||
assert.strictEqual(Resolver.format(ctxdata, env.numProp)[1], '1');
|
||||
});
|
||||
|
||||
it('returns a value when used in macro', function(){
|
||||
assert.strictEqual(Resolver.format(ctxdata, env.numIndex)[1], 'One');
|
||||
});
|
||||
|
||||
it('returns the raw string when NaN is referenced', function(){
|
||||
var value = Resolver.format(ctxdata, env.nanProp)[1];
|
||||
assert.strictEqual(value, '{{ nan }}');
|
||||
});
|
||||
|
||||
it('is undefined when NaN is used in macro', function(){
|
||||
assert.throws(function() {
|
||||
Resolver.format(ctxdata, env.nanIndex);
|
||||
}, 'Arg must be a string or a number: nan');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('and bools', function(){
|
||||
|
||||
before(function() {
|
||||
ctxdata = {
|
||||
bool: true
|
||||
};
|
||||
source = [
|
||||
'boolProp={{ bool }}',
|
||||
'boolIndex={[ plural(bool) ]}',
|
||||
'boolIndex[one]=One'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('returns the raw string when referenced', function(){
|
||||
var value = Resolver.format(ctxdata, env.boolProp)[1];
|
||||
assert.strictEqual(value, '{{ bool }}');
|
||||
});
|
||||
|
||||
it('is undefined when used in a macro', function(){
|
||||
assert.throws(function() {
|
||||
Resolver.format(ctxdata, env.boolIndex);
|
||||
}, 'Arg must be a string or a number: bool');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('and undefined', function(){
|
||||
|
||||
before(function() {
|
||||
ctxdata = {
|
||||
undef: undefined
|
||||
};
|
||||
source = [
|
||||
'undefProp={{ undef }}',
|
||||
'undefIndex={[ plural(undef) ]}',
|
||||
'undefIndex[one]=One'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('returns the raw string when referenced', function(){
|
||||
var value = Resolver.format(ctxdata, env.undefProp)[1];
|
||||
assert.strictEqual(value, '{{ undef }}');
|
||||
});
|
||||
|
||||
it('is undefined when used in a macro', function(){
|
||||
assert.throws(function() {
|
||||
Resolver.format(ctxdata, env.undefIndex);
|
||||
}, 'Arg must be a string or a number: undef');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('and null', function(){
|
||||
|
||||
before(function() {
|
||||
ctxdata = {
|
||||
nullable: null
|
||||
};
|
||||
source = [
|
||||
'nullProp={{ nullable }}',
|
||||
'nullIndex={[ plural(nullable) ]}',
|
||||
'nullIndex[one]=One'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('returns the raw string', function(){
|
||||
var value = Resolver.format(ctxdata, env.nullProp)[1];
|
||||
assert.strictEqual(value, '{{ nullable }}');
|
||||
});
|
||||
|
||||
it('is undefined when used in a macro', function(){
|
||||
assert.throws(function() {
|
||||
Resolver.format(ctxdata, env.nullIndex);
|
||||
}, 'Arg must be a string or a number: nullable');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('and arrays where first element is number', function(){
|
||||
|
||||
before(function() {
|
||||
ctxdata = {
|
||||
arr: [1, 2]
|
||||
};
|
||||
source = [
|
||||
'arrProp={{ arr }}',
|
||||
'arrIndex={[ plural(arr) ]}',
|
||||
'arrIndex[one]=One'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('returns the raw string', function(){
|
||||
var value = Resolver.format(ctxdata, env.arrProp)[1];
|
||||
assert.strictEqual(value, '{{ arr }}');
|
||||
});
|
||||
|
||||
it('is undefined when used in a macro', function(){
|
||||
assert.throws(function() {
|
||||
Resolver.format(ctxdata, env.arrIndex);
|
||||
}, 'Arg must be a string or a number: arr');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('and arrays where first element is not a number', function(){
|
||||
|
||||
before(function() {
|
||||
ctxdata = {
|
||||
arr: ['a', 'b']
|
||||
};
|
||||
source = [
|
||||
'arrProp={{ arr }}',
|
||||
'arrIndex={[ plural(arr) ]}',
|
||||
'arrIndex[one]=One'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('returns the raw string', function(){
|
||||
var value = Resolver.format(ctxdata, env.arrProp)[1];
|
||||
assert.strictEqual(value, '{{ arr }}');
|
||||
});
|
||||
|
||||
it('is undefined when used in a macro', function(){
|
||||
assert.throws(function() {
|
||||
Resolver.format(ctxdata, env.arrIndex);
|
||||
}, 'Arg must be a string or a number: arr');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('and objects', function(){
|
||||
|
||||
before(function() {
|
||||
ctxdata = {
|
||||
obj: {
|
||||
key: 'value'
|
||||
}
|
||||
};
|
||||
source = [
|
||||
'objProp={{ obj }}',
|
||||
'objIndex={[ plural(obj) ]}',
|
||||
'objIndex[one]=One'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('returns the raw string', function(){
|
||||
var value = Resolver.format(ctxdata, env.objProp)[1];
|
||||
assert.strictEqual(value, '{{ obj }}');
|
||||
});
|
||||
|
||||
it('throws used in a macro', function(){
|
||||
assert.throws(function() {
|
||||
Resolver.format(ctxdata, env.objIndex);
|
||||
}, 'Arg must be a string or a number: obj');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
38
gaia/apps/sharedtest/test/unit/l10n/lib/resolver/dos_test.js
Executable file
38
gaia/apps/sharedtest/test/unit/l10n/lib/resolver/dos_test.js
Executable file
|
|
@ -0,0 +1,38 @@
|
|||
/* global assert:true, it, beforeEach, describe, requireApp */
|
||||
/* jshint -W101 */
|
||||
'use strict';
|
||||
|
||||
if (typeof navigator !== 'undefined') {
|
||||
requireApp('sharedtest/test/unit/l10n/lib/resolver/header.js');
|
||||
} else {
|
||||
var assert = require('assert');
|
||||
var Resolver = require('./header.js').Resolver;
|
||||
var createEntries = require('./header.js').createEntries;
|
||||
}
|
||||
|
||||
// Bug 803931 - Compiler is vulnerable to the billion laughs attack
|
||||
|
||||
describe('Billion Laughs', function(){
|
||||
var entries;
|
||||
|
||||
beforeEach(function() {
|
||||
var source = [
|
||||
'lol0=LOL',
|
||||
'lol1={{lol0}} {{lol0}} {{lol0}} {{lol0}} {{lol0}} {{lol0}} {{lol0}} {{lol0}} {{lol0}} {{lol0}}',
|
||||
'lol2={{lol1}} {{lol1}} {{lol1}} {{lol1}} {{lol1}} {{lol1}} {{lol1}} {{lol1}} {{lol1}} {{lol1}}',
|
||||
'lol3={{lol2}} {{lol2}} {{lol2}} {{lol2}} {{lol2}} {{lol2}} {{lol2}} {{lol2}} {{lol2}} {{lol2}}',
|
||||
// normally, we'd continue with lol4 through lol9 in the same manner,
|
||||
// but this would make the test itself dangerous if the guards in the
|
||||
// compiler fail. Given MAX_PLACEABLE_LENGTH of 2500, lol3 is enough
|
||||
// to test this.
|
||||
'lolz={{ lol3 }}'
|
||||
].join('\n');
|
||||
entries = createEntries(source);
|
||||
});
|
||||
|
||||
it('Resolver.format() throws', function() {
|
||||
assert.throws(function() {
|
||||
Resolver.format(null, entries.lolz);
|
||||
}, /too many characters in placeable/i);
|
||||
});
|
||||
});
|
||||
101
gaia/apps/sharedtest/test/unit/l10n/lib/resolver/errors_test.js
Executable file
101
gaia/apps/sharedtest/test/unit/l10n/lib/resolver/errors_test.js
Executable file
|
|
@ -0,0 +1,101 @@
|
|||
/* global assert:true, it, before, beforeEach, describe, requireApp */
|
||||
'use strict';
|
||||
|
||||
if (typeof navigator !== 'undefined') {
|
||||
requireApp('sharedtest/test/unit/l10n/lib/resolver/header.js');
|
||||
} else {
|
||||
var assert = require('assert');
|
||||
var Resolver = require('./header.js').Resolver;
|
||||
var createEntries = require('./header.js').createEntries;
|
||||
}
|
||||
|
||||
describe('Compiler errors:', function(){
|
||||
var source, env;
|
||||
beforeEach(function() {
|
||||
env = createEntries(source);
|
||||
});
|
||||
|
||||
describe('A complex string referencing an existing entity', function(){
|
||||
|
||||
before(function() {
|
||||
source = [
|
||||
'file=File',
|
||||
'prompt={[ plural(n) ]}',
|
||||
'prompt[one]=One {{ file }}',
|
||||
'prompt[other]=Files'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('works with the default index', function(){
|
||||
assert.strictEqual(
|
||||
Resolver.format({n: 1}, env.prompt)[1], 'One File');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('A complex string referencing a missing entity', function(){
|
||||
|
||||
before(function() {
|
||||
source = [
|
||||
'prompt={[ plural(n) ]}',
|
||||
'prompt[one]=One {{ file }}',
|
||||
'prompt[other]=Files'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('returns the raw string', function(){
|
||||
var value = Resolver.format({n: 1}, env.prompt)[1];
|
||||
assert.strictEqual(value, 'One {{ file }}');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('A ctxdata variable in the index, with "other"', function(){
|
||||
|
||||
before(function() {
|
||||
source = [
|
||||
'file=File',
|
||||
'prompt={[ plural(n) ]}',
|
||||
'prompt[one]=One {{ file }}',
|
||||
'prompt[other]=Files'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('is found', function(){
|
||||
assert.strictEqual(
|
||||
Resolver.format({n: 1}, env.prompt)[1], 'One File');
|
||||
});
|
||||
|
||||
it('throws an IndexError if n is not defined', function(){
|
||||
assert.throws(function() {
|
||||
Resolver.format(null, env.prompt);
|
||||
}, 'Unknown reference: n');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('A ctxdata variable in the index, without "other"', function(){
|
||||
|
||||
before(function() {
|
||||
source = [
|
||||
'file=File',
|
||||
'prompt={[ plural(n) ]}',
|
||||
'prompt[one]=One {{ file }}',
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('is found', function(){
|
||||
assert.strictEqual(
|
||||
Resolver.format({n: 1}, env.prompt)[1], 'One File');
|
||||
});
|
||||
|
||||
it('throws an IndexError if n is not defined', function(){
|
||||
assert.throws(function() {
|
||||
Resolver.format(null, env.prompt);
|
||||
}, 'Unknown reference: n');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
33
gaia/apps/sharedtest/test/unit/l10n/lib/resolver/header.js
Executable file
33
gaia/apps/sharedtest/test/unit/l10n/lib/resolver/header.js
Executable file
|
|
@ -0,0 +1,33 @@
|
|||
/* global navigator, exports */
|
||||
/* exported Resolver */
|
||||
|
||||
'use strict';
|
||||
|
||||
if (typeof navigator !== 'undefined') {
|
||||
var L10n = navigator.mozL10n._getInternalAPI();
|
||||
var Resolver = L10n.Resolver;
|
||||
} else {
|
||||
var L10n = {
|
||||
PropertiesParser:
|
||||
require('../../../src/lib/format/properties/parser'),
|
||||
Resolver: require('../../../src/lib/resolver'),
|
||||
getPluralRule: require('../../../src/lib/plurals').getPluralRule
|
||||
};
|
||||
|
||||
exports.createEntries = createEntries;
|
||||
exports.Resolver = L10n.Resolver;
|
||||
}
|
||||
|
||||
|
||||
function createEntries(source) {
|
||||
/* jshint -W089 */
|
||||
var entries = Object.create(null);
|
||||
var ast = L10n.PropertiesParser.parse(null, source);
|
||||
|
||||
for (var i = 0, len = ast.length; i < len; i++) {
|
||||
entries[ast[i].$i] = L10n.Resolver.createEntry(ast[i], entries);
|
||||
}
|
||||
|
||||
entries.__plural = L10n.getPluralRule('en-US');
|
||||
return entries;
|
||||
}
|
||||
86
gaia/apps/sharedtest/test/unit/l10n/lib/resolver/indexes_test.js
Executable file
86
gaia/apps/sharedtest/test/unit/l10n/lib/resolver/indexes_test.js
Executable file
|
|
@ -0,0 +1,86 @@
|
|||
/* global assert:true, it, before, beforeEach, describe, requireApp */
|
||||
'use strict';
|
||||
|
||||
if (typeof navigator !== 'undefined') {
|
||||
requireApp('sharedtest/test/unit/l10n/lib/resolver/header.js');
|
||||
} else {
|
||||
var assert = require('assert');
|
||||
var Resolver = require('./header.js').Resolver;
|
||||
var createEntries = require('./header.js').createEntries;
|
||||
}
|
||||
|
||||
describe('Index', function(){
|
||||
var source, env;
|
||||
|
||||
beforeEach(function() {
|
||||
env = createEntries(source);
|
||||
});
|
||||
|
||||
describe('Different values of index', function(){
|
||||
|
||||
before(function() {
|
||||
source = [
|
||||
'foo=one',
|
||||
'indexEntity={[ foo ]}',
|
||||
'indexEntity[one]=One entity',
|
||||
'indexUncalledMacro={[ plural ]}',
|
||||
'indexUncalledMacro[one]=One uncalled macro',
|
||||
'indexCalledMacro={[ plural(n) ]}',
|
||||
'indexCalledMacro[one]=One called macro',
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('works when the index is a regular entity', function() {
|
||||
var value = Resolver.format({n: 1}, env.indexEntity)[1];
|
||||
assert.strictEqual(value, 'One entity');
|
||||
});
|
||||
it('throws when the index is an uncalled macro', function() {
|
||||
assert.throws(function() {
|
||||
Resolver.format({n: 1}, env.indexUncalledMacro);
|
||||
}, 'Unresolvable value');
|
||||
});
|
||||
it('works when the index is a called macro', function() {
|
||||
var value = Resolver.format({n: 1}, env.indexCalledMacro)[1];
|
||||
assert.strictEqual(value, 'One called macro');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Cyclic reference to the same entity', function(){
|
||||
|
||||
before(function() {
|
||||
source = [
|
||||
'foo={[ plural(foo) ]}',
|
||||
'foo[one]=One'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('is undefined', function() {
|
||||
assert.throws(function() {
|
||||
Resolver.format(null, env.foo);
|
||||
}, 'Cyclic reference detected: foo');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Reference from an attribute to the value of the same ' +
|
||||
'entity', function(){
|
||||
|
||||
before(function() {
|
||||
source = [
|
||||
'foo=Foo',
|
||||
'foo.attr={[ plural(foo) ]}',
|
||||
'foo.attr[one]=One'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('value of the attribute is undefined', function() {
|
||||
assert.strictEqual(Resolver.format(null, env.foo)[1], 'Foo');
|
||||
assert.throws(function() {
|
||||
Resolver.format(null, env.foo.attrs.attr);
|
||||
}, 'Unresolvable value');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
357
gaia/apps/sharedtest/test/unit/l10n/lib/resolver/macros_test.js
Executable file
357
gaia/apps/sharedtest/test/unit/l10n/lib/resolver/macros_test.js
Executable file
|
|
@ -0,0 +1,357 @@
|
|||
/* global assert:true, it, before, beforeEach, describe, requireApp */
|
||||
'use strict';
|
||||
|
||||
if (typeof navigator !== 'undefined') {
|
||||
requireApp('sharedtest/test/unit/l10n/lib/resolver/header.js');
|
||||
} else {
|
||||
var assert = require('assert');
|
||||
var Resolver = require('./header.js').Resolver;
|
||||
var createEntries = require('./header.js').createEntries;
|
||||
}
|
||||
|
||||
describe('Macros', function(){
|
||||
var source, ctxdata, env;
|
||||
beforeEach(function() {
|
||||
env = createEntries(source);
|
||||
});
|
||||
|
||||
describe('referencing macros', function(){
|
||||
|
||||
before(function() {
|
||||
ctxdata = {
|
||||
n: 1
|
||||
};
|
||||
source = [
|
||||
'placeMacro={{ plural }}',
|
||||
'placeRealMacro={{ __plural }}'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('throws when resolving (not calling) a macro in a complex ' +
|
||||
'string', function() {
|
||||
assert.strictEqual(
|
||||
Resolver.format(ctxdata, env.placeMacro)[1], '{{ plural }}');
|
||||
assert.strictEqual(
|
||||
Resolver.format(ctxdata, env.placeRealMacro)[1], '{{ __plural }}');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('passing arguments', function(){
|
||||
|
||||
before(function() {
|
||||
ctxdata = {
|
||||
n: 1
|
||||
};
|
||||
source = [
|
||||
'foo=Foo',
|
||||
'useFoo={{ foo }}',
|
||||
'bar={[ plural(n) ]}',
|
||||
'bar[one]=One',
|
||||
'bar.attr=Attr',
|
||||
|
||||
'passFoo={[ plural(foo) ]}',
|
||||
'passFoo[one]=One',
|
||||
|
||||
'passUseFoo={[ plural(useFoo) ]}',
|
||||
'passUseFoo[one]=One',
|
||||
|
||||
'passBar={[ plural(bar) ]}',
|
||||
'passBar[one]=One',
|
||||
|
||||
'passPlural={[ plural(plural) ]}',
|
||||
'passPlural[one]=One',
|
||||
|
||||
'passMissing={[ plural(missing) ]}',
|
||||
'passMissing[one]=One',
|
||||
|
||||
'passWatch={[ plural(watch) ]}',
|
||||
'passWatch[one]=One',
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('throws if an entity is passed', function() {
|
||||
assert.throws(function() {
|
||||
Resolver.format(ctxdata, env.passFoo);
|
||||
}, 'Unresolvable value');
|
||||
});
|
||||
|
||||
it('throws if a complex entity is passed', function() {
|
||||
assert.throws(function() {
|
||||
Resolver.format(ctxdata, env.passUseFoo);
|
||||
}, 'Unresolvable value');
|
||||
});
|
||||
|
||||
it('throws if a hash entity is passed', function() {
|
||||
assert.throws(function() {
|
||||
Resolver.format(ctxdata, env.passBar);
|
||||
}, 'Unresolvable value');
|
||||
});
|
||||
|
||||
it('throws if a macro is passed', function() {
|
||||
assert.throws(function() {
|
||||
Resolver.format(ctxdata, env.passPlural);
|
||||
}, 'Unresolvable value');
|
||||
});
|
||||
|
||||
it('throws if a missing entry is passed', function() {
|
||||
assert.throws(function() {
|
||||
Resolver.format(ctxdata, env.passMissing);
|
||||
}, 'Unknown reference: missing');
|
||||
});
|
||||
|
||||
it('throws if a native function is passed', function() {
|
||||
assert.throws(function() {
|
||||
Resolver.format(ctxdata, env.passWatch);
|
||||
}, 'Unknown reference: watch');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('A simple plural macro', function(){
|
||||
var source, env;
|
||||
|
||||
beforeEach(function() {
|
||||
env = createEntries(source);
|
||||
env.__plural = function() {
|
||||
return 'other';
|
||||
};
|
||||
});
|
||||
|
||||
before(function() {
|
||||
source = [
|
||||
'foo={[ plural(n) ]}',
|
||||
'foo[zero]=Zero',
|
||||
'foo[one]=One',
|
||||
'foo[two]=Two',
|
||||
'foo[few]=Few',
|
||||
'foo[many]=Many',
|
||||
'foo[other]=Other'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('returns zero for 0', function() {
|
||||
var value = Resolver.format({n: 0}, env.foo)[1];
|
||||
assert.strictEqual(value, 'Zero');
|
||||
});
|
||||
|
||||
it('returns one for 1', function() {
|
||||
var value = Resolver.format({n: 1}, env.foo)[1];
|
||||
assert.strictEqual(value, 'One');
|
||||
});
|
||||
|
||||
it('returns two for 2', function() {
|
||||
var value = Resolver.format({n: 2}, env.foo)[1];
|
||||
assert.strictEqual(value, 'Two');
|
||||
});
|
||||
|
||||
it('returns other for 3', function() {
|
||||
var value = Resolver.format({n: 3}, env.foo)[1];
|
||||
assert.strictEqual(value, 'Other');
|
||||
});
|
||||
|
||||
it('throws for no arg', function() {
|
||||
assert.throws(function() {
|
||||
Resolver.format(null, env.foo);
|
||||
}, 'Unknown reference: n');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('A more complex plural macro', function(){
|
||||
var source, env;
|
||||
|
||||
beforeEach(function() {
|
||||
env = createEntries(source);
|
||||
env.__plural = function(n) {
|
||||
// a made-up plural rule:
|
||||
// [0, 1) -> other
|
||||
// [1, Inf) -> many
|
||||
return (n >= 0 && n < 1) ? 'other' : 'many';
|
||||
};
|
||||
});
|
||||
|
||||
describe('an entity with all plural forms defined', function(){
|
||||
|
||||
before(function() {
|
||||
source = [
|
||||
'foo={[ plural(n) ]}',
|
||||
'foo[zero]=Zero',
|
||||
'foo[one]=One',
|
||||
'foo[two]=Two',
|
||||
'foo[few]=Few',
|
||||
'foo[many]=Many',
|
||||
'foo[other]=Other'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('returns zero for 0', function() {
|
||||
var value = Resolver.format({n: 0}, env.foo)[1];
|
||||
assert.strictEqual(value, 'Zero');
|
||||
});
|
||||
|
||||
it('returns one for 1', function() {
|
||||
var value = Resolver.format({n: 1}, env.foo)[1];
|
||||
assert.strictEqual(value, 'One');
|
||||
});
|
||||
|
||||
it('returns two for 2', function() {
|
||||
var value = Resolver.format({n: 2}, env.foo)[1];
|
||||
assert.strictEqual(value, 'Two');
|
||||
});
|
||||
|
||||
it('returns many for 3', function() {
|
||||
var value = Resolver.format({n: 3}, env.foo)[1];
|
||||
assert.strictEqual(value, 'Many');
|
||||
});
|
||||
|
||||
it('returns many for 5', function() {
|
||||
var value = Resolver.format({n: 5}, env.foo)[1];
|
||||
assert.strictEqual(value, 'Many');
|
||||
});
|
||||
|
||||
it('returns other for 0.5', function() {
|
||||
var value = Resolver.format({n: 0.5}, env.foo)[1];
|
||||
assert.strictEqual(value, 'Other');
|
||||
});
|
||||
|
||||
it('throws for no arg', function() {
|
||||
assert.throws(function() {
|
||||
Resolver.format(null, env.foo);
|
||||
}, 'Unknown reference: n');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('an entity without the zero, one and two forms', function(){
|
||||
|
||||
before(function() {
|
||||
source = [
|
||||
'foo={[ plural(n) ]}',
|
||||
'foo[many]=Many',
|
||||
'foo[other]=Other'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('returns other for 0', function() {
|
||||
var value = Resolver.format({n: 0}, env.foo)[1];
|
||||
assert.strictEqual(value, 'Other');
|
||||
});
|
||||
|
||||
it('returns many for 1', function() {
|
||||
var value = Resolver.format({n: 1}, env.foo)[1];
|
||||
assert.strictEqual(value, 'Many');
|
||||
});
|
||||
|
||||
it('returns many for 2', function() {
|
||||
var value = Resolver.format({n: 2}, env.foo)[1];
|
||||
assert.strictEqual(value, 'Many');
|
||||
});
|
||||
|
||||
it('returns many for 3', function() {
|
||||
var value = Resolver.format({n: 3}, env.foo)[1];
|
||||
assert.strictEqual(value, 'Many');
|
||||
});
|
||||
|
||||
it('returns many for 5', function() {
|
||||
var value = Resolver.format({n: 5}, env.foo)[1];
|
||||
assert.strictEqual(value, 'Many');
|
||||
});
|
||||
|
||||
it('returns other for 0.5', function() {
|
||||
var value = Resolver.format({n: 0.5}, env.foo)[1];
|
||||
assert.strictEqual(value, 'Other');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('an entity without the many form', function(){
|
||||
|
||||
before(function() {
|
||||
source = [
|
||||
'foo={[ plural(n) ]}',
|
||||
'foo[other]=Other'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('returns other for 0', function() {
|
||||
var value = Resolver.format({n: 0}, env.foo)[1];
|
||||
assert.strictEqual(value, 'Other');
|
||||
});
|
||||
|
||||
it('returns other for 1', function() {
|
||||
var value = Resolver.format({n: 1}, env.foo)[1];
|
||||
assert.strictEqual(value, 'Other');
|
||||
});
|
||||
|
||||
it('returns other for 2', function() {
|
||||
var value = Resolver.format({n: 2}, env.foo)[1];
|
||||
assert.strictEqual(value, 'Other');
|
||||
});
|
||||
|
||||
it('returns other for 3', function() {
|
||||
var value = Resolver.format({n: 3}, env.foo)[1];
|
||||
assert.strictEqual(value, 'Other');
|
||||
});
|
||||
|
||||
it('returns other for 5', function() {
|
||||
var value = Resolver.format({n: 5}, env.foo)[1];
|
||||
assert.strictEqual(value, 'Other');
|
||||
});
|
||||
|
||||
it('returns other for 0.5', function() {
|
||||
var value = Resolver.format({n: 0.5}, env.foo)[1];
|
||||
assert.strictEqual(value, 'Other');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('an entity without the other form, but with the one ' +
|
||||
'form', function(){
|
||||
|
||||
before(function() {
|
||||
source = [
|
||||
'foo={[ plural(n) ]}',
|
||||
'foo[one]=One'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('throws for 0', function() {
|
||||
assert.throws(function() {
|
||||
Resolver.format({n: 0}, env.foo);
|
||||
}, 'Unresolvable value');
|
||||
});
|
||||
|
||||
it('returns one for 1', function() {
|
||||
var value = Resolver.format({n: 1}, env.foo)[1];
|
||||
assert.strictEqual(value, 'One');
|
||||
});
|
||||
|
||||
it('throws for 2', function() {
|
||||
assert.throws(function() {
|
||||
Resolver.format({n: 2}, env.foo);
|
||||
}, 'Unresolvable value');
|
||||
});
|
||||
|
||||
it('throws for 3', function() {
|
||||
assert.throws(function() {
|
||||
Resolver.format({n: 3}, env.foo);
|
||||
}, 'Unresolvable value');
|
||||
});
|
||||
|
||||
it('throws for 5', function() {
|
||||
assert.throws(function() {
|
||||
Resolver.format({n: 5}, env.foo);
|
||||
}, 'Unresolvable value');
|
||||
});
|
||||
|
||||
it('throws for 0.5', function() {
|
||||
assert.throws(function() {
|
||||
Resolver.format({n: 0.5}, env.foo);
|
||||
}, 'Unresolvable value');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
136
gaia/apps/sharedtest/test/unit/l10n/lib/resolver/primitives_test.js
Executable file
136
gaia/apps/sharedtest/test/unit/l10n/lib/resolver/primitives_test.js
Executable file
|
|
@ -0,0 +1,136 @@
|
|||
/* global assert:true, it, before, beforeEach, describe, requireApp */
|
||||
'use strict';
|
||||
|
||||
if (typeof navigator !== 'undefined') {
|
||||
requireApp('sharedtest/test/unit/l10n/lib/resolver/header.js');
|
||||
} else {
|
||||
var assert = require('assert');
|
||||
var Resolver = require('./header.js').Resolver;
|
||||
var createEntries = require('./header.js').createEntries;
|
||||
}
|
||||
|
||||
describe('Primitives:', function(){
|
||||
var source, env;
|
||||
beforeEach(function() {
|
||||
env = createEntries(source);
|
||||
});
|
||||
|
||||
describe('Simple string value', function(){
|
||||
|
||||
before(function() {
|
||||
source = [
|
||||
'foo=Foo'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('returns the value', function(){
|
||||
assert.strictEqual(Resolver.format(null, env.foo)[1], 'Foo');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Complex string value', function(){
|
||||
|
||||
before(function() {
|
||||
source = [
|
||||
'foo=Foo',
|
||||
'bar={{ foo }} Bar',
|
||||
'baz={{ missing }}',
|
||||
'qux={{ malformed }'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('returns the value', function(){
|
||||
var value = Resolver.format(null, env.bar)[1];
|
||||
assert.strictEqual(value, 'Foo Bar');
|
||||
});
|
||||
|
||||
it('returns the raw string if the referenced entity is ' +
|
||||
'not found', function(){
|
||||
var value = Resolver.format(null, env.baz)[1];
|
||||
assert.strictEqual(value, '{{ missing }}');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Complex string referencing an entity with null value', function(){
|
||||
|
||||
before(function() {
|
||||
source = [
|
||||
'foo.attr=Foo',
|
||||
'bar={{ foo }} Bar',
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('returns the null value', function(){
|
||||
var value = Resolver.format(null, env.foo)[1];
|
||||
assert.strictEqual(value, null);
|
||||
});
|
||||
|
||||
it('returns the attribute', function(){
|
||||
var attr = Resolver.format(null, env.foo.attrs.attr)[1];
|
||||
assert.strictEqual(attr, 'Foo');
|
||||
});
|
||||
|
||||
it('returns the raw string when the referenced entity has ' +
|
||||
'null value', function(){
|
||||
var value = Resolver.format(null, env.bar)[1];
|
||||
assert.strictEqual(value, '{{ foo }} Bar');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Cyclic reference', function(){
|
||||
|
||||
before(function() {
|
||||
source = [
|
||||
'foo={{ bar }}',
|
||||
'bar={{ foo }}'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('returns the raw string', function(){
|
||||
var value = Resolver.format(null, env.foo)[1];
|
||||
assert.strictEqual(value, '{{ foo }}');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Cyclic self-reference', function(){
|
||||
|
||||
before(function() {
|
||||
source = [
|
||||
'foo={{ foo }}'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('returns the raw string', function(){
|
||||
var value = Resolver.format(null, env.foo)[1];
|
||||
assert.strictEqual(value, '{{ foo }}');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Cyclic self-reference in a hash', function(){
|
||||
|
||||
before(function() {
|
||||
source = [
|
||||
'foo={[ plural(n) ]}',
|
||||
'foo[one]={{ foo }}',
|
||||
'foo[two]=Bar',
|
||||
'bar={{ foo }}'
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('returns the raw string', function(){
|
||||
var value = Resolver.format({n: 1}, env.foo)[1];
|
||||
assert.strictEqual(value, '{{ foo }}');
|
||||
});
|
||||
|
||||
it('returns the valid value if requested directly', function(){
|
||||
var value = Resolver.format({n: 2}, env.bar)[1];
|
||||
assert.strictEqual(value, 'Bar');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
157
gaia/apps/sharedtest/test/unit/l10n/lib/util_test.js
Executable file
157
gaia/apps/sharedtest/test/unit/l10n/lib/util_test.js
Executable file
|
|
@ -0,0 +1,157 @@
|
|||
/* global it, assert:true, describe, before, beforeEach */
|
||||
/* global navigator */
|
||||
'use strict';
|
||||
|
||||
var assert, PropertiesParser;
|
||||
|
||||
var reVowels = /[AEIOUaeiou]/;
|
||||
|
||||
function digest(str) {
|
||||
var cur = 0;
|
||||
var i = str.length;
|
||||
while (i) {
|
||||
if (reVowels.test(str[--i])) {
|
||||
cur++;
|
||||
}
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
|
||||
describe('walkContent', function() {
|
||||
var L10n, source, ast;
|
||||
|
||||
before(function(done) {
|
||||
if (typeof navigator !== 'undefined') {
|
||||
require('/build/l10n.js', function() {
|
||||
L10n = navigator.mozL10n._getInternalAPI();
|
||||
PropertiesParser = L10n.PropertiesParser;
|
||||
done();
|
||||
});
|
||||
} else {
|
||||
assert = require('assert');
|
||||
L10n = {};
|
||||
L10n.walkContent = require('../../src/lib/util').walkContent;
|
||||
PropertiesParser = require('../../src/lib/format/properties/parser');
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
beforeEach(function() {
|
||||
ast = PropertiesParser.parse(null, source);
|
||||
});
|
||||
|
||||
describe('simple strings and attributes', function(){
|
||||
|
||||
before(function() {
|
||||
source = [
|
||||
'foo=Foo',
|
||||
'foo.attr=An attribute',
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('walks the value', function(){
|
||||
var walked = L10n.walkContent(ast[0], digest);
|
||||
assert.strictEqual(walked.$v, 2);
|
||||
});
|
||||
|
||||
it('walks the attribute', function(){
|
||||
var walked = L10n.walkContent(ast[0], digest);
|
||||
assert.strictEqual(walked.attr, 5);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('one-level-deep dict', function(){
|
||||
|
||||
before(function() {
|
||||
source = [
|
||||
'foo={[ plural(n) ]}',
|
||||
'foo[one]=One',
|
||||
'foo[two]=Two',
|
||||
'foo[few]=Few',
|
||||
'foo[many]=Many',
|
||||
'foo[other]=Other',
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('walks the values', function(){
|
||||
var walked = L10n.walkContent(ast[0], digest);
|
||||
assert.strictEqual(walked.$v.one, 2);
|
||||
assert.strictEqual(walked.$v.two, 1);
|
||||
assert.strictEqual(walked.$v.few, 1);
|
||||
assert.strictEqual(walked.$v.many, 1);
|
||||
assert.strictEqual(walked.$v.other, 2);
|
||||
});
|
||||
|
||||
it('does not modify the index', function(){
|
||||
var walked = L10n.walkContent(ast[0], digest);
|
||||
assert.deepEqual(walked.$x, [{t: 'idOrVar', v: 'plural'}, 'n']);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// XXX parser currently doesn't support nested dicts
|
||||
describe.skip('two-level-deep dict', function(){
|
||||
|
||||
before(function() {
|
||||
source = [
|
||||
'foo={[ plural(n) ]}',
|
||||
'foo[other]={[ plural(m) ]}',
|
||||
'foo[other][one]=One',
|
||||
'foo[other][two]=Two',
|
||||
'foo[other][few]=Few',
|
||||
'foo[other][many]=Many',
|
||||
'foo[other][other]=Other',
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('walks the values', function(){
|
||||
var walked = L10n.walkContent(ast[0], digest);
|
||||
assert.strictEqual(walked.$v.other.one, 2);
|
||||
assert.strictEqual(walked.$v.other.two, 1);
|
||||
assert.strictEqual(walked.$v.other.few, 1);
|
||||
assert.strictEqual(walked.$v.other.many, 1);
|
||||
assert.strictEqual(walked.$v.other.other, 2);
|
||||
});
|
||||
|
||||
it('does not modify the indexes', function(){
|
||||
var walked = L10n.walkContent(ast[0], digest);
|
||||
assert.deepEqual(
|
||||
walked.$x, [{t: 'idOrVar', v: 'plural'}, 'n']);
|
||||
assert.deepEqual(
|
||||
walked.$v.other.$x, [{t: 'idOrVar', v: 'plural'}, 'n']);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('attribute that is a one-level-deep dict', function(){
|
||||
|
||||
before(function() {
|
||||
source = [
|
||||
'foo.attr={[ plural(n) ]}',
|
||||
'foo.attr[one]=One',
|
||||
'foo.attr[two]=Two',
|
||||
'foo.attr[few]=Few',
|
||||
'foo.attr[many]=Many',
|
||||
'foo.attr[other]=Other',
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
it('walks the values', function(){
|
||||
var walked = L10n.walkContent(ast[0], digest);
|
||||
assert.strictEqual(walked.attr.$v.one, 2);
|
||||
assert.strictEqual(walked.attr.$v.two, 1);
|
||||
assert.strictEqual(walked.attr.$v.few, 1);
|
||||
assert.strictEqual(walked.attr.$v.many, 1);
|
||||
assert.strictEqual(walked.attr.$v.other, 2);
|
||||
});
|
||||
|
||||
it('does not modify the indexes', function(){
|
||||
var walked = L10n.walkContent(ast[0], digest);
|
||||
assert.deepEqual(walked.attr.$x, [{t: 'idOrVar', v: 'plural'}, 'n']);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
284
gaia/apps/sharedtest/test/unit/language_list_test.js
Executable file
284
gaia/apps/sharedtest/test/unit/language_list_test.js
Executable file
|
|
@ -0,0 +1,284 @@
|
|||
'use strict';
|
||||
/* global LanguageList */
|
||||
|
||||
require('/shared/js/language_list.js');
|
||||
|
||||
suite('Language List', function() {
|
||||
|
||||
suite('get', function() {
|
||||
|
||||
suiteTeardown(function() {
|
||||
LanguageList._languages = null;
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
this.sinon.stub(LanguageList, '_readFile', function() {
|
||||
return Promise.resolve({
|
||||
'en-US': 'English (US)'
|
||||
});
|
||||
});
|
||||
this.sinon.stub(LanguageList, '_readSetting', function(name) {
|
||||
switch (name) {
|
||||
case 'langpack.channel':
|
||||
return Promise.resolve('2.2');
|
||||
case 'language.current':
|
||||
return Promise.resolve('en-US');
|
||||
case 'devtools.pseudolocalization.enabled':
|
||||
return Promise.resolve('false');
|
||||
}
|
||||
});
|
||||
this.sinon.stub(
|
||||
navigator.mozApps, 'getAdditionalLanguages', function() {
|
||||
return Promise.resolve({
|
||||
de: [{
|
||||
name: 'Deutsch',
|
||||
target: '2.2',
|
||||
version: 201501191234
|
||||
}]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('the first time loads languages from file', function(done) {
|
||||
LanguageList.get(function() {
|
||||
assert.isTrue(LanguageList._readFile.called,
|
||||
'_readFile is called');
|
||||
assert.isTrue(LanguageList._readSetting.called,
|
||||
'_readSetting is called');
|
||||
assert.isTrue(navigator.mozApps.getAdditionalLanguages.called,
|
||||
'mozApps.getAdditionalLanguages is called');
|
||||
assert.isNotNull(LanguageList._languages);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('on consecutive calls loads languages from cache', function(done) {
|
||||
LanguageList.get(function() {
|
||||
assert.isFalse(LanguageList._readFile.called,
|
||||
'_readFile is not called');
|
||||
assert.isTrue(LanguageList._readSetting.called,
|
||||
'_readSetting is called');
|
||||
assert.isTrue(navigator.mozApps.getAdditionalLanguages.called,
|
||||
'mozApps.getAdditionalLanguages is called');
|
||||
assert.isNotNull(LanguageList._languages);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
suite('wrap bidi', function() {
|
||||
test('wrap bidi', function() {
|
||||
assert.equal(LanguageList.wrapBidi('fr-x-psaccent', 'LTR'),
|
||||
'\u202aLTR\u202c');
|
||||
assert.equal(LanguageList.wrapBidi('ar-x-psbidi', 'RTL'),
|
||||
'\u202bRTL\u202c');
|
||||
});
|
||||
});
|
||||
|
||||
suite('extend language list with pseudolanguages', function() {
|
||||
setup(function() {
|
||||
document.l10n = {
|
||||
pseudo: {
|
||||
'fr-x-psaccent': {
|
||||
getName: function() {return Promise.resolve('Pseudo');}
|
||||
},
|
||||
}
|
||||
};
|
||||
});
|
||||
test('regular current language, pseudolocalization disabled',
|
||||
function(done) {
|
||||
var langs = {
|
||||
en: 'English'
|
||||
};
|
||||
LanguageList._extendPseudo(langs, 'en', false).then(langs => {
|
||||
assert.isTrue('en' in langs, 'en is listed');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('regular current language, pseudolocalization enabled',
|
||||
function(done) {
|
||||
var langs = {
|
||||
en: 'English'
|
||||
};
|
||||
LanguageList._extendPseudo(langs, 'en', true).then(langs => {
|
||||
assert.isTrue('en' in langs, 'en is listed');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('regular non-current language, pseudolocalization disabled',
|
||||
function(done) {
|
||||
var langs = {
|
||||
en: 'English',
|
||||
pl: 'Polski'
|
||||
};
|
||||
LanguageList._extendPseudo(langs, 'en', false).then(langs => {
|
||||
assert.isTrue('en' in langs, 'en is listed');
|
||||
assert.isTrue('pl' in langs, 'pl is listed');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('regular non-current language, pseudolocalization enabled',
|
||||
function(done) {
|
||||
var langs = {
|
||||
en: 'English',
|
||||
pl: 'Polski'
|
||||
};
|
||||
LanguageList._extendPseudo(langs, 'en', true).then(langs => {
|
||||
assert.isTrue('en' in langs, 'en is listed');
|
||||
assert.isTrue('pl' in langs, 'pl is listed');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('pseudo current language, pseudolocalization disabled',
|
||||
function(done) {
|
||||
var langs = {
|
||||
en: 'English'
|
||||
};
|
||||
LanguageList._extendPseudo(langs, 'fr-x-psaccent', false).then(
|
||||
langs => {
|
||||
assert.isTrue('en' in langs, 'en is listed');
|
||||
assert.isTrue('fr-x-psaccent' in langs, 'fr-x-psaccent is listed');
|
||||
}
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('pseudo current language, pseudolocalization enabled',
|
||||
function(done) {
|
||||
var langs = {
|
||||
en: 'English'
|
||||
};
|
||||
LanguageList._extendPseudo(langs, 'fr-x-psaccent', true).then(
|
||||
langs => {
|
||||
assert.isTrue('en' in langs, 'en is listed');
|
||||
assert.isTrue('fr-x-psaccent' in langs, 'fr-x-psaccent is listed');
|
||||
}
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('pseudo non-current language, pseudolocalization disabled',
|
||||
function(done) {
|
||||
var langs = {
|
||||
en: 'English'
|
||||
};
|
||||
LanguageList._extendPseudo(langs, 'en', false).then(langs => {
|
||||
assert.isTrue('en' in langs, 'en is listed');
|
||||
assert.isFalse('fr-x-psaccent' in langs,
|
||||
'fr-x-psaccent is not listed');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('pseudo non-current language, pseudolocalization enabled',
|
||||
function(done) {
|
||||
var langs = {
|
||||
en: 'English'
|
||||
};
|
||||
LanguageList._extendPseudo(langs, 'en', true).then(langs => {
|
||||
assert.isTrue('en' in langs, 'en is listed');
|
||||
assert.isTrue('fr-x-psaccent' in langs, 'fr-x-psaccent is listed');
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('remove buildtime pseudolanguages', function(done) {
|
||||
test('regular current language, pseudolocalization enabled', function() {
|
||||
var langs = {
|
||||
en: 'English',
|
||||
'fr-x-psaccent': 'Ȧȧƈƈḗḗƞŧḗḗḓ Ḗḗƞɠŀīīşħ'
|
||||
};
|
||||
LanguageList._extendPseudo(langs, 'en', true).then(langs => {
|
||||
assert.isTrue('en' in langs, 'en is listed');
|
||||
assert.isTrue('fr-x-psaccent' in langs, 'fr-x-psaccent is listed');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('regular current language, pseudolocalization disabled',
|
||||
function(done) {
|
||||
var langs = {
|
||||
en: 'English',
|
||||
'fr-x-psaccent': 'Ȧȧƈƈḗḗƞŧḗḗḓ Ḗḗƞɠŀīīşħ'
|
||||
};
|
||||
LanguageList._extendPseudo(langs, 'en', false).then(langs => {
|
||||
assert.isTrue('en' in langs, 'en is listed');
|
||||
assert.isFalse('fr-x-psaccent' in langs,
|
||||
'fr-x-psaccent is not listed');
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('pseudo current language, pseudolocalization enabled',
|
||||
function(done) {
|
||||
var langs = {
|
||||
en: 'English',
|
||||
'fr-x-psaccent': 'Ȧȧƈƈḗḗƞŧḗḗḓ Ḗḗƞɠŀīīşħ'
|
||||
};
|
||||
LanguageList._extendPseudo(langs, 'fr-x-psaccent', true).then(
|
||||
langs => {
|
||||
assert.isTrue('en' in langs, 'en is listed');
|
||||
assert.isTrue('fr-x-psaccent' in langs, 'fr-x-psaccent is listed');
|
||||
}
|
||||
).then(done, done);
|
||||
});
|
||||
|
||||
test('pseudo current language, pseudolocalization disabled',
|
||||
function(done) {
|
||||
var langs = {
|
||||
en: 'English',
|
||||
'fr-x-psaccent': 'Ȧȧƈƈḗḗƞŧḗḗḓ Ḗḗƞɠŀīīşħ'
|
||||
};
|
||||
LanguageList._extendPseudo(langs, 'fr-x-psaccent', false).then(
|
||||
langs => {
|
||||
assert.isTrue('en' in langs, 'en is listed');
|
||||
assert.isTrue('fr-x-psaccent' in langs, 'fr-x-psaccent is listed');
|
||||
}
|
||||
).then(done, done);
|
||||
});
|
||||
});
|
||||
|
||||
suite('extend language list with additional languages', function() {
|
||||
|
||||
test('a langpack matching the current version exists', function() {
|
||||
var langs = {
|
||||
en: 'English'
|
||||
};
|
||||
var additional = {
|
||||
de: [
|
||||
{ name: 'Deutsch', target: '2.1' },
|
||||
{ name: 'Deutsch', target: '2.2' }
|
||||
],
|
||||
pl: [
|
||||
{ name: 'Polski', target: '2.2' },
|
||||
{ name: 'Polski', target: '2.3' }
|
||||
]
|
||||
};
|
||||
LanguageList._extendAdditional(langs, '2.2', additional);
|
||||
assert.isTrue('en' in langs, 'en is listed');
|
||||
assert.isTrue('de' in langs, 'de is listed');
|
||||
assert.equal(langs.de, 'Deutsch');
|
||||
assert.isTrue('pl' in langs, 'pl is listed');
|
||||
assert.equal(langs.pl, 'Polski');
|
||||
});
|
||||
|
||||
test('no langpack matching the current version exists', function() {
|
||||
var langs = {
|
||||
en: 'English'
|
||||
};
|
||||
var additional = {
|
||||
de: [
|
||||
{ name: 'Deutsch', target: '2.1' },
|
||||
{ name: 'Deutsch', target: '2.2' }
|
||||
],
|
||||
pl: [
|
||||
{ name: 'Polski', target: '2.2' },
|
||||
{ name: 'Polski', target: '2.3' }
|
||||
]
|
||||
};
|
||||
LanguageList._extendAdditional(langs, '2.3', additional);
|
||||
assert.isTrue('en' in langs, 'en is listed');
|
||||
assert.isFalse('de' in langs, 'de is not listed');
|
||||
assert.isTrue('pl' in langs, 'pl is listed');
|
||||
assert.equal(langs.pl, 'Polski');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
191
gaia/apps/sharedtest/test/unit/lazy_loader_test.js
Executable file
191
gaia/apps/sharedtest/test/unit/lazy_loader_test.js
Executable file
|
|
@ -0,0 +1,191 @@
|
|||
/* global LazyLoader */
|
||||
|
||||
'use strict';
|
||||
|
||||
require('/shared/js/lazy_loader.js');
|
||||
require('/shared/js/html_imports.js');
|
||||
|
||||
suite('lazy loader', function() {
|
||||
|
||||
function countStyles() {
|
||||
return document.querySelectorAll('link').length;
|
||||
}
|
||||
|
||||
function addNodeToLazyLoad() {
|
||||
document.body.innerHTML = [
|
||||
'<div id="parent">',
|
||||
'<!-- ',
|
||||
'<div id="lazyload-me">',
|
||||
'content',
|
||||
'</div>',
|
||||
'-->',
|
||||
'</div>'
|
||||
].join('');
|
||||
}
|
||||
|
||||
suiteSetup(function() {
|
||||
window.jsCount = 0;
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
delete window.jsCount;
|
||||
});
|
||||
|
||||
var stub;
|
||||
setup(function() {
|
||||
stub = sinon.stub();
|
||||
});
|
||||
|
||||
test('everything looks ok', function() {
|
||||
assert.equal(window.jsCount, 0);
|
||||
assert.typeOf(LazyLoader, 'object');
|
||||
assert.typeOf(LazyLoader.load, 'function');
|
||||
assert.typeOf(LazyLoader.getJSON, 'function');
|
||||
});
|
||||
|
||||
test('load json', function(done) {
|
||||
LazyLoader.getJSON('/apps/sharedtest/test/unit/support/test.json')
|
||||
.then(function(json) {
|
||||
done(function() {
|
||||
assert.notStrictEqual(json, null);
|
||||
assert.equal('org' in json, true);
|
||||
assert.equal(json.org, 'Mozilla');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('load malformed json', function(done) {
|
||||
LazyLoader.getJSON('/apps/sharedtest/test/unit/support/malformedJson')
|
||||
.then(function(json) {
|
||||
done(new Error('A resolve promise was returned.' +
|
||||
' Expected Reject promise'));
|
||||
}, function(error) {
|
||||
done(function() {
|
||||
assert.instanceOf(error, Error);
|
||||
assert.equal(error.message,
|
||||
'No valid JSON object was found (200 OK)');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('load inexisting json', function(done) {
|
||||
LazyLoader.getJSON('/apps/sharedtest/test/unit/support/non_existant.json')
|
||||
.then(function(json) {
|
||||
done(new Error('Resolve promise was returned.' +
|
||||
' Expected Reject promise'));
|
||||
}, function(error) {
|
||||
done(function() {
|
||||
assert.instanceOf(error, Error);
|
||||
assert.equal(error.message,
|
||||
'No valid JSON object was found (404 Not Found)');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('append single js script', function(done) {
|
||||
LazyLoader.load('support/inc.js', stub).then(function() {
|
||||
assert.equal(window.jsCount, 1);
|
||||
sinon.assert.calledOnce(stub);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('append the same js script', function(done) {
|
||||
LazyLoader.load('support/inc.js', stub).then(function() {
|
||||
assert.equal(window.jsCount, 1);
|
||||
sinon.assert.calledOnce(stub);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('append css script', function(done) {
|
||||
var numStyles = countStyles();
|
||||
LazyLoader.load('support/styles.css', stub).then(function() {
|
||||
assert.equal(countStyles(), (numStyles + 1));
|
||||
sinon.assert.calledOnce(stub);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('lazyload HTML node', function(done) {
|
||||
addNodeToLazyLoad();
|
||||
var parent = document.getElementById('parent');
|
||||
LazyLoader.load(parent, stub).then(function() {
|
||||
assert.equal(
|
||||
document.getElementById('lazyload-me').toString(),
|
||||
'[object HTMLDivElement]'
|
||||
);
|
||||
sinon.assert.calledOnce(stub);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('lazyload the same HTML node', function(done) {
|
||||
var parent = document.getElementById('parent');
|
||||
var child = document.getElementById('lazyload-me');
|
||||
|
||||
child.innerHTML = 'New Content';
|
||||
LazyLoader.load(parent, stub).then(function() {
|
||||
assert.equal(child.innerHTML, 'New Content');
|
||||
sinon.assert.calledOnce(stub);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('append the same css script', function(done) {
|
||||
var numStyles = countStyles();
|
||||
LazyLoader.load('support/styles.css', stub).then(function() {
|
||||
assert.equal(countStyles(), numStyles);
|
||||
sinon.assert.calledOnce(stub);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('Loaded callback is invoked correctly if node already exists on the DOM',
|
||||
function(done) {
|
||||
Promise.all([
|
||||
LazyLoader.load(['support/long_load.js'], stub),
|
||||
LazyLoader.load(['support/long_load.js'], stub)
|
||||
]).then(function() {
|
||||
assert.equal(window.totalResult, 10000);
|
||||
sinon.assert.calledTwice(stub);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('appending multiple nodes works', function(done) {
|
||||
var numStyles = countStyles();
|
||||
|
||||
// Manually reset LazyLoader._loaded
|
||||
LazyLoader._loaded = {};
|
||||
|
||||
// Remove nodes from DOM
|
||||
var style = document.querySelector('link[href="support/styles.css"]');
|
||||
style.parentNode.removeChild(style);
|
||||
var script = document.querySelector('script[src="support/inc.js"]');
|
||||
script.parentNode.removeChild(script);
|
||||
addNodeToLazyLoad();
|
||||
var parent = document.getElementById('parent');
|
||||
|
||||
LazyLoader.load([
|
||||
'support/styles.css',
|
||||
'support/inc.js',
|
||||
parent
|
||||
], stub).then(function() {
|
||||
assert.equal(window.jsCount, 2);
|
||||
assert.equal(countStyles(), numStyles);
|
||||
assert.equal(
|
||||
document.getElementById('lazyload-me').toString(),
|
||||
'[object HTMLDivElement]'
|
||||
);
|
||||
sinon.assert.calledOnce(stub);
|
||||
}).then(done, done);
|
||||
});
|
||||
|
||||
test('populate web components', function(done) {
|
||||
document.body.innerHTML = [
|
||||
'<link rel="import" href="/test/unit/support/elements.html"> ',
|
||||
'<div id="wrapper" is="custom-content"></div>'
|
||||
].join('');
|
||||
|
||||
var wrapper = document.getElementById('wrapper');
|
||||
|
||||
LazyLoader.load(wrapper, stub).then(function() {
|
||||
assert.equal(wrapper.innerHTML, 'hello.');
|
||||
sinon.assert.calledOnce(stub);
|
||||
}).then(done, done);
|
||||
});
|
||||
});
|
||||
581
gaia/apps/sharedtest/test/unit/lockscreen_conn_info_manager_test.js
Executable file
581
gaia/apps/sharedtest/test/unit/lockscreen_conn_info_manager_test.js
Executable file
|
|
@ -0,0 +1,581 @@
|
|||
/* globals LockScreenConnInfoManager, MobileOperator, MockMobileconnection,
|
||||
MockMobileOperator, MockNavigatorMozIccManager,
|
||||
MockNavigatorSettings, MockSIMSlot, MockSIMSlotManager, MocksHelper,
|
||||
SIMSlotManager */
|
||||
'use strict';
|
||||
|
||||
require('/shared/test/unit/mocks/mock_l10n.js');
|
||||
require('/shared/test/unit/mocks/mock_simslot.js');
|
||||
require('/shared/test/unit/mocks/mock_simslot_manager.js');
|
||||
require('/shared/test/unit/mocks/mock_navigator_moz_mobile_connections.js');
|
||||
require('/shared/test/unit/mocks/mock_navigator_moz_icc_manager.js');
|
||||
require('/shared/test/unit/mocks/mock_mobile_operator.js');
|
||||
require('/shared/test/unit/mocks/mock_navigator_moz_settings.js');
|
||||
require('/shared/test/unit/mocks/mock_settings_listener.js');
|
||||
require('/shared/js/lockscreen_connection_info_manager.js');
|
||||
|
||||
var mocksHelperForLockScreenConnInfoManager = new MocksHelper([
|
||||
'MobileOperator',
|
||||
'SettingsListener',
|
||||
'SIMSlotManager'
|
||||
]).init();
|
||||
|
||||
suite('system/LockScreenConnInfoManager >', function() {
|
||||
var subject;
|
||||
var realL10n;
|
||||
var realIccManager;
|
||||
var realMozSettings;
|
||||
var domConnStates;
|
||||
var DUMMYTEXT1 = 'foo';
|
||||
|
||||
mocksHelperForLockScreenConnInfoManager.attachTestHelpers();
|
||||
|
||||
suiteSetup(function() {
|
||||
realL10n = navigator.mozL10n;
|
||||
navigator.mozL10n = window.MockL10n;
|
||||
|
||||
realIccManager = navigator.mozIccManager;
|
||||
navigator.mozIccManager = MockNavigatorMozIccManager;
|
||||
|
||||
realMozSettings = window.navigator.mozSettings;
|
||||
navigator.mozSettings = MockNavigatorSettings;
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
navigator.mozL10n = realL10n;
|
||||
navigator.mozIccManager = realIccManager;
|
||||
navigator.mozSettings = realMozSettings;
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
MockNavigatorSettings.mSetup();
|
||||
|
||||
domConnStates = document.createElement('div');
|
||||
domConnStates.id = 'lockscreen-conn-states';
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
MockNavigatorMozIccManager.mTeardown();
|
||||
MockNavigatorSettings.mTeardown();
|
||||
});
|
||||
|
||||
suite('Initialization', function() {
|
||||
setup(function() {
|
||||
subject = new LockScreenConnInfoManager();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
subject.teardown();
|
||||
});
|
||||
|
||||
test('Ensure that the UI is immediately updated when starting', function() {
|
||||
this.sinon.spy(subject, 'updateConnStates');
|
||||
subject._initialize(domConnStates);
|
||||
sinon.assert.calledOnce(subject.updateConnStates);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Update conn states when events', function() {
|
||||
var mockMobileConnection;
|
||||
var iccObj;
|
||||
|
||||
suiteSetup(function() {
|
||||
mockMobileConnection = MockMobileconnection();
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
MockMobileOperator.mOperator = 'operator';
|
||||
MockMobileOperator.mCarrier = 'carrier';
|
||||
MockMobileOperator.mRegion = 'region';
|
||||
|
||||
MockSIMSlotManager.mInstances =
|
||||
[new MockSIMSlot(mockMobileConnection, 0)];
|
||||
iccObj = MockSIMSlotManager.mInstances[0].simCard;
|
||||
|
||||
// add a sim card
|
||||
mockMobileConnection.iccId = 'iccid1';
|
||||
mockMobileConnection.voice = {};
|
||||
MockNavigatorMozIccManager.addIcc('iccid1');
|
||||
|
||||
subject = new LockScreenConnInfoManager();
|
||||
subject._initialize(domConnStates);
|
||||
|
||||
this.sinon.stub(MockSIMSlotManager, 'isMultiSIM').returns(false);
|
||||
this.sinon.stub(MockSIMSlotManager, 'noSIMCardOnDevice').returns(false);
|
||||
|
||||
this.sinon.stub(subject, 'updateConnStates');
|
||||
this.sinon.stub(subject, 'updateConnState');
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
subject.teardown();
|
||||
mockMobileConnection.mTeardown();
|
||||
subject.updateConnStates.restore();
|
||||
subject.updateConnState.restore();
|
||||
});
|
||||
|
||||
// sim related changes
|
||||
test('voicechange', function() {
|
||||
mockMobileConnection.triggerEventListeners('voicechange', {});
|
||||
sinon.assert.called(subject.updateConnState);
|
||||
});
|
||||
|
||||
['simslot-cardstatechange',
|
||||
'simslot-iccinfochange'].forEach(function(eventName) {
|
||||
test(eventName, function() {
|
||||
var simInfo = {
|
||||
conn: mockMobileConnection,
|
||||
index: 0
|
||||
};
|
||||
window.dispatchEvent(new CustomEvent(eventName, { detail: simInfo }));
|
||||
sinon.assert.calledWith(subject.updateConnState, simInfo);
|
||||
});
|
||||
});
|
||||
|
||||
test('cellbroadcastmsgchanged', function() {
|
||||
var testLabelName = 'testLabelName';
|
||||
window.dispatchEvent(new CustomEvent('cellbroadcastmsgchanged', {
|
||||
detail: testLabelName
|
||||
}));
|
||||
assert.isTrue(subject.updateConnStates.called);
|
||||
assert.equal(subject._cellbroadcastLabel, testLabelName);
|
||||
});
|
||||
|
||||
test('ril.radio.disabled', function() {
|
||||
var airplaneModeEnabled = true;
|
||||
MockNavigatorSettings.createLock().set({
|
||||
'ril.radio.disabled': airplaneModeEnabled
|
||||
});
|
||||
assert.isTrue(subject.updateConnStates.called);
|
||||
assert.equal(subject._airplaneMode, airplaneModeEnabled);
|
||||
});
|
||||
|
||||
test('ril.telephony.defaultServiceId', function() {
|
||||
var defaultServiceId = 'iccid1';
|
||||
MockNavigatorSettings.createLock().set({
|
||||
'ril.telephony.defaultServiceId': defaultServiceId
|
||||
});
|
||||
assert.isTrue(subject.updateConnStates.called);
|
||||
assert.equal(subject._telephonyDefaultServiceId, defaultServiceId);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Single sim devices', function() {
|
||||
var mockMobileConnection;
|
||||
var domConnstateIDLine;
|
||||
var domConnstateL1;
|
||||
var domConnstateL2;
|
||||
var iccObj;
|
||||
|
||||
suiteSetup(function() {
|
||||
mockMobileConnection = MockMobileconnection();
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
MockMobileOperator.mOperator = 'operator';
|
||||
MockMobileOperator.mCarrier = 'carrier';
|
||||
MockMobileOperator.mRegion = 'region';
|
||||
|
||||
MockSIMSlotManager.mInstances =
|
||||
[new MockSIMSlot(mockMobileConnection, 0)];
|
||||
iccObj = MockSIMSlotManager.mInstances[0].simCard;
|
||||
|
||||
// add a sim card
|
||||
mockMobileConnection.iccId = 'iccid1';
|
||||
mockMobileConnection.voice = {};
|
||||
MockNavigatorMozIccManager.addIcc('iccid1');
|
||||
|
||||
subject = new LockScreenConnInfoManager();
|
||||
subject._initialize(domConnStates);
|
||||
|
||||
var domConnState = domConnStates.children[0];
|
||||
domConnstateIDLine = domConnState.children[0];
|
||||
domConnstateL1 = domConnState.children[1];
|
||||
domConnstateL2 = domConnState.children[2];
|
||||
|
||||
this.sinon.stub(MockSIMSlotManager, 'isMultiSIM').returns(false);
|
||||
this.sinon.stub(MockSIMSlotManager, 'noSIMCardOnDevice').returns(false);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
subject.teardown();
|
||||
mockMobileConnection.mTeardown();
|
||||
});
|
||||
|
||||
test('2G Mode: should update cell broadcast info on connstate Line 2',
|
||||
function() {
|
||||
mockMobileConnection.voice = {
|
||||
connected: true,
|
||||
type: 'gsm'
|
||||
};
|
||||
|
||||
subject._cellbroadcastLabel = DUMMYTEXT1;
|
||||
subject.updateConnStates();
|
||||
assert.equal(domConnstateL2.textContent, DUMMYTEXT1);
|
||||
|
||||
subject._cellbroadcastLabel = null;
|
||||
});
|
||||
|
||||
test('3G Mode: should update carrier and region info on connstate Line 2',
|
||||
function() {
|
||||
mockMobileConnection.voice = {
|
||||
connected: true,
|
||||
type: 'wcdma'
|
||||
};
|
||||
|
||||
var carrier = 'TIM';
|
||||
var region = 'SP';
|
||||
MobileOperator.mCarrier = carrier;
|
||||
MobileOperator.mRegion = region;
|
||||
|
||||
subject._cellbroadcastLabel = DUMMYTEXT1;
|
||||
|
||||
var l10nSpy = sinon.spy(navigator.mozL10n, 'setAttributes');
|
||||
|
||||
var l10nArgs = {
|
||||
carrier: carrier,
|
||||
region: region
|
||||
};
|
||||
|
||||
subject.updateConnStates();
|
||||
|
||||
assert.ok(l10nSpy.calledWith(domConnstateL2,
|
||||
'operator-info',
|
||||
l10nArgs));
|
||||
|
||||
navigator.mozL10n.setAttributes.restore();
|
||||
|
||||
subject._cellbroadcastLabel = null;
|
||||
});
|
||||
|
||||
test('Show no network', function() {
|
||||
mockMobileConnection.voice = {
|
||||
connected: true,
|
||||
state: 'notSearching'
|
||||
};
|
||||
subject.updateConnStates();
|
||||
assert.equal(domConnstateL1.dataset.l10nId, 'noNetwork');
|
||||
});
|
||||
|
||||
test('Show searching', function() {
|
||||
mockMobileConnection.voice = {
|
||||
connected: false,
|
||||
emergencyCallsOnly: false
|
||||
};
|
||||
subject.updateConnStates();
|
||||
assert.equal(domConnstateL1.dataset.l10nId, 'searching');
|
||||
});
|
||||
|
||||
test('Show roaming', function() {
|
||||
mockMobileConnection.voice = {
|
||||
connected: true,
|
||||
emergencyCallsOnly: false,
|
||||
roaming: true
|
||||
};
|
||||
|
||||
var l10nSpy = sinon.spy(navigator.mozL10n, 'setAttributes');
|
||||
|
||||
var l10nArgs = {
|
||||
operator: MockMobileOperator.mOperator
|
||||
};
|
||||
|
||||
subject.updateConnStates();
|
||||
|
||||
assert.ok(l10nSpy.calledWith(domConnstateL1,
|
||||
'roaming',
|
||||
l10nArgs));
|
||||
|
||||
navigator.mozL10n.setAttributes.restore();
|
||||
});
|
||||
|
||||
test('Show localized roaming',
|
||||
function() {
|
||||
mockMobileConnection.voice = {
|
||||
connected: true,
|
||||
emergencyCallsOnly: false,
|
||||
roaming: true
|
||||
};
|
||||
|
||||
var l10nArgs = {
|
||||
operator: 'operator'
|
||||
};
|
||||
|
||||
var l10nSpy = this.sinon.spy(navigator.mozL10n, 'setAttributes');
|
||||
subject.updateConnStates();
|
||||
assert.ok(l10nSpy.calledWith(domConnstateL1, 'roaming', l10nArgs),
|
||||
'Roaming network name displayed localized with proper string');
|
||||
|
||||
navigator.mozL10n.setAttributes.restore();
|
||||
});
|
||||
|
||||
suite('Show correct card states when emergency calls only', function() {
|
||||
test('unknown', function() {
|
||||
mockMobileConnection.voice = {
|
||||
connected: false,
|
||||
emergencyCallsOnly: true
|
||||
};
|
||||
iccObj.cardState = 'unknown';
|
||||
|
||||
subject.updateConnStates();
|
||||
assert.equal(domConnstateL1.dataset.l10nId, 'emergencyCallsOnly');
|
||||
assert.equal(domConnstateL2.dataset.l10nId,
|
||||
'emergencyCallsOnly-unknownSIMState');
|
||||
});
|
||||
|
||||
test('other card state', function() {
|
||||
mockMobileConnection.voice = {
|
||||
connected: false,
|
||||
emergencyCallsOnly: true
|
||||
};
|
||||
iccObj.cardState = 'otherCardState';
|
||||
|
||||
subject.updateConnStates();
|
||||
assert.equal(domConnstateL1.dataset.l10nId, 'emergencyCallsOnly');
|
||||
assert.isFalse(domConnstateL2.hasAttribute('data-l10n-id'));
|
||||
});
|
||||
|
||||
['pinRequired', 'pukRequired', 'networkLocked',
|
||||
'serviceProviderLocked', 'corporateLocked', 'network1Locked',
|
||||
'network2Locked', 'hrpdNetworkLocked', 'ruimCorporateLocked',
|
||||
'ruimServiceProviderLocked'].forEach(function(cardState) {
|
||||
test(cardState, function() {
|
||||
mockMobileConnection.voice = {
|
||||
connected: false,
|
||||
emergencyCallsOnly: true
|
||||
};
|
||||
iccObj.cardState = cardState;
|
||||
|
||||
subject.updateConnStates();
|
||||
assert.equal(domConnstateL1.dataset.l10nId, 'emergencyCallsOnly');
|
||||
assert.equal(domConnstateL2.dataset.l10nId,
|
||||
'emergencyCallsOnly-' + cardState);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('Multiple sims devices', function() {
|
||||
var domConnStateList;
|
||||
var mockMobileConnections = [];
|
||||
var iccObj1;
|
||||
var iccObj2;
|
||||
|
||||
suiteSetup(function() {
|
||||
mockMobileConnections = [
|
||||
MockMobileconnection(),
|
||||
MockMobileconnection()
|
||||
];
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
MockMobileOperator.mOperator = 'operator';
|
||||
MockMobileOperator.mCarrier = 'carrier';
|
||||
MockMobileOperator.mRegion = 'region';
|
||||
|
||||
MockSIMSlotManager.mInstances =
|
||||
[new MockSIMSlot(mockMobileConnections[0], 0),
|
||||
new MockSIMSlot(mockMobileConnections[1], 1)];
|
||||
|
||||
iccObj1 = MockSIMSlotManager.mInstances[0].simCard;
|
||||
iccObj2 = MockSIMSlotManager.mInstances[1].simCard;
|
||||
|
||||
mockMobileConnections[0].iccId = 'iccid1';
|
||||
mockMobileConnections[0].voice = {};
|
||||
MockNavigatorMozIccManager.addIcc('iccid1');
|
||||
|
||||
mockMobileConnections[1].iccId = 'iccid2';
|
||||
mockMobileConnections[1].voice = {};
|
||||
MockNavigatorMozIccManager.addIcc('iccid2');
|
||||
|
||||
subject = new LockScreenConnInfoManager();
|
||||
subject._initialize(domConnStates);
|
||||
|
||||
domConnStateList = [];
|
||||
Array.prototype.forEach.call(domConnStates.children,
|
||||
function(domConnState) {
|
||||
domConnState.domConnstateIDLine = domConnState.children[0];
|
||||
domConnState.domConnstateL1 = domConnState.children[1];
|
||||
domConnState.domConnstateL2 = domConnState.children[2];
|
||||
domConnStateList.push(domConnState);
|
||||
});
|
||||
|
||||
this.sinon.stub(SIMSlotManager, 'isMultiSIM').returns(true);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
subject.teardown();
|
||||
mockMobileConnections[0].mTeardown();
|
||||
mockMobileConnections[1].mTeardown();
|
||||
});
|
||||
|
||||
suite('No sim card', function() {
|
||||
setup(function() {
|
||||
this.sinon.stub(SIMSlotManager, 'noSIMCardOnDevice').returns(true);
|
||||
});
|
||||
|
||||
test('Should only show one conn state', function() {
|
||||
subject.updateConnStates();
|
||||
|
||||
assert.equal(domConnStateList[0].domConnstateL1.dataset.l10nId,
|
||||
'emergencyCallsOnly-noSIM');
|
||||
assert.isFalse(
|
||||
domConnStateList[1].domConnstateL1.hasAttribute('data-l10n-id'));
|
||||
assert.isFalse(
|
||||
domConnStateList[1].domConnstateL2.hasAttribute('data-l10n-id'));
|
||||
});
|
||||
|
||||
test('Should show emergency call text', function() {
|
||||
mockMobileConnections[0].voice.emergencyCallsOnly = true;
|
||||
subject.updateConnStates();
|
||||
|
||||
assert.equal(domConnStateList[0].domConnstateL1.dataset.l10nId,
|
||||
'emergencyCallsOnly');
|
||||
assert.equal(domConnStateList[0].domConnstateL2.dataset.l10nId,
|
||||
'emergencyCallsOnly-noSIM');
|
||||
assert.isFalse(
|
||||
domConnStateList[1].domConnstateL1.hasAttribute('data-l10n-id'));
|
||||
assert.isFalse(
|
||||
domConnStateList[1].domConnstateL2.hasAttribute('data-l10n-id'));
|
||||
});
|
||||
});
|
||||
|
||||
suite('One sim card inserted', function() {
|
||||
suiteSetup(function() {
|
||||
mockMobileConnections[0].voice = {
|
||||
connected: true,
|
||||
type: 'gsm'
|
||||
};
|
||||
mockMobileConnections[1].voice = {};
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
navigator.mozIccManager.removeIcc('iccid2');
|
||||
MockSIMSlotManager.mInstances[1].isAbsent = function() { return true; };
|
||||
});
|
||||
|
||||
test('Should show sim ID', function() {
|
||||
subject.updateConnStates();
|
||||
|
||||
var simIDLine = domConnStateList[0].domConnstateIDLine;
|
||||
assert.isFalse(simIDLine.hidden);
|
||||
|
||||
assert.equal(simIDLine.dataset.l10nId, 'lockscreen-sim-id');
|
||||
assert.deepEqual(JSON.parse(simIDLine.dataset.l10nArgs), {'n': 1});
|
||||
});
|
||||
|
||||
test('Should show only one conn state', function() {
|
||||
subject.updateConnStates();
|
||||
|
||||
assert.isFalse(domConnStateList[0].hidden);
|
||||
assert.isTrue(domConnStateList[1].hidden);
|
||||
});
|
||||
|
||||
test('Should show airplane mode on connstate 1 Line 1 when in ' +
|
||||
'airplane mode', function() {
|
||||
subject._airplaneMode = true;
|
||||
subject.updateConnStates();
|
||||
|
||||
assert.isFalse(domConnStateList[0].hidden);
|
||||
assert.isTrue(domConnStateList[0].domConnstateIDLine.hidden);
|
||||
assert.equal(domConnStateList[0].domConnstateL1.dataset.l10nId,
|
||||
'airplaneMode');
|
||||
assert.isFalse(
|
||||
domConnStateList[0].domConnstateL2.hasAttribute('data-l10n-id'));
|
||||
|
||||
subject._airplaneMode = false;
|
||||
});
|
||||
});
|
||||
|
||||
suite('Two sim cards inserted', function() {
|
||||
setup(function() {
|
||||
MockSIMSlotManager.mInstances[0].conn.voice = {
|
||||
connected: true,
|
||||
type: 'gsm'
|
||||
};
|
||||
MockSIMSlotManager.mInstances[1].conn.voice = {
|
||||
connected: true,
|
||||
type: 'gsm'
|
||||
};
|
||||
this.sinon.stub(SIMSlotManager, 'noSIMCardOnDevice').returns(false);
|
||||
});
|
||||
|
||||
test('Should show sim IDs', function() {
|
||||
subject.updateConnStates();
|
||||
|
||||
var simIDLine1 = domConnStateList[0].domConnstateIDLine;
|
||||
var simIDLine2 = domConnStateList[1].domConnstateIDLine;
|
||||
assert.isFalse(simIDLine1.hidden);
|
||||
assert.isFalse(simIDLine2.hidden);
|
||||
|
||||
assert.equal(simIDLine1.dataset.l10nId, 'lockscreen-sim-id');
|
||||
assert.deepEqual(JSON.parse(simIDLine1.dataset.l10nArgs), {'n': 1});
|
||||
assert.equal(simIDLine2.dataset.l10nId, 'lockscreen-sim-id');
|
||||
assert.deepEqual(JSON.parse(simIDLine2.dataset.l10nArgs), {'n': 2});
|
||||
});
|
||||
|
||||
test('Should hide sim IDs if all sim cards are not connected to networks',
|
||||
function() {
|
||||
sinon.stub(SIMSlotManager, 'noSIMCardConnectedToNetwork')
|
||||
.returns(true);
|
||||
subject.updateConnStates();
|
||||
|
||||
var simIDLine1 = domConnStateList[0].domConnstateIDLine;
|
||||
var connState1line1 = domConnStateList[0].domConnstateL1;
|
||||
var simIDLine2 = domConnStateList[1].domConnstateIDLine;
|
||||
assert.isTrue(simIDLine1.hidden);
|
||||
assert.isTrue(simIDLine2.hidden);
|
||||
assert.equal(
|
||||
connState1line1.getAttribute('data-l10n-id'),
|
||||
'emergencyCallsOnly');
|
||||
SIMSlotManager.noSIMCardConnectedToNetwork.restore();
|
||||
});
|
||||
|
||||
test('Should show operator names on Line 1', function() {
|
||||
subject.updateConnStates();
|
||||
|
||||
var connState1line1 = domConnStateList[0].domConnstateL1;
|
||||
var connState2line1 = domConnStateList[1].domConnstateL1;
|
||||
assert.equal(connState1line1.textContent, MockMobileOperator.mOperator);
|
||||
assert.equal(connState2line1.textContent, MockMobileOperator.mOperator);
|
||||
});
|
||||
|
||||
test('Should show carrier and region on Line 2', function() {
|
||||
var l10nSpy = sinon.spy(navigator.mozL10n, 'setAttributes');
|
||||
|
||||
var l10nArgs = {
|
||||
carrier: MockMobileOperator.mCarrier,
|
||||
region: MockMobileOperator.mRegion
|
||||
};
|
||||
|
||||
subject.updateConnStates();
|
||||
|
||||
assert.ok(l10nSpy.calledWith(domConnStateList[0].domConnstateL2,
|
||||
'operator-info',
|
||||
l10nArgs));
|
||||
|
||||
assert.ok(l10nSpy.calledWith(domConnStateList[1].domConnstateL2,
|
||||
'operator-info',
|
||||
l10nArgs));
|
||||
});
|
||||
|
||||
test('Should display "emergency calls only" if target sim is the ' +
|
||||
'primary one and is emergency calls only', function() {
|
||||
mockMobileConnections[0].voice.emergencyCallsOnly = true;
|
||||
subject._telephonyDefaultServiceId = 0;
|
||||
subject.updateConnStates();
|
||||
|
||||
var connState1line1 = domConnStateList[0].domConnstateL1;
|
||||
assert.equal(connState1line1.dataset.l10nId, 'emergencyCallsOnly');
|
||||
});
|
||||
|
||||
test('Should hide the conn state of the target sim if it is not the ' +
|
||||
'primary one but is emergency calls only', function() {
|
||||
mockMobileConnections[0].voice.emergencyCallsOnly = true;
|
||||
subject._telephonyDefaultServiceId = 1;
|
||||
subject.updateConnStates();
|
||||
|
||||
assert.isTrue(domConnStateList[0].hidden);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
64
gaia/apps/sharedtest/test/unit/lockscreen_slide_test.js
Executable file
64
gaia/apps/sharedtest/test/unit/lockscreen_slide_test.js
Executable file
|
|
@ -0,0 +1,64 @@
|
|||
/* global LockScreenSlide */
|
||||
|
||||
'use strict';
|
||||
|
||||
require('/shared/js/lockscreen_slide.js');
|
||||
require('/shared/js/screen_layout.js');
|
||||
|
||||
suite('LockScreenSlide', function() {
|
||||
var lockscreenSlide, unlockButton, cameraButton;
|
||||
|
||||
suiteSetup(function() {
|
||||
document.body.innerHTML =
|
||||
'<div id="lockscreen">' +
|
||||
'<div id="lockscreen-area"></div>' +
|
||||
'<div id="lockscreen-area-camera" role="button"></div>' +
|
||||
'<div id="lockscreen-area-unlock" role="button"></div>' +
|
||||
'<div id="lockscreen-canvas-wrapper">' +
|
||||
'<canvas id="lockscreen-canvas"></canvas>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
unlockButton = document.getElementById('lockscreen-area-unlock');
|
||||
cameraButton = document.getElementById('lockscreen-area-camera');
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
suite('Instance', function() {
|
||||
suiteSetup(function() {
|
||||
lockscreenSlide = new LockScreenSlide({});
|
||||
});
|
||||
|
||||
test('OptionMenu', function() {
|
||||
assert.ok(LockScreenSlide);
|
||||
assert.ok(LockScreenSlide.prototype.publish);
|
||||
});
|
||||
|
||||
suite('Accessibility', function() {
|
||||
var stubPublish;
|
||||
|
||||
function dispatchAccessibleClick(elem) {
|
||||
// A synthesized mouse event will trigger the button, but a physical one
|
||||
// won't. Because both this script and the screenreader show an
|
||||
// event.mozInputSource of UNKNOWN.
|
||||
elem.dispatchEvent(new MouseEvent('click'));
|
||||
}
|
||||
|
||||
setup(function() {
|
||||
stubPublish = this.sinon.stub(lockscreenSlide, 'publish');
|
||||
});
|
||||
|
||||
test('Click unlock button', function() {
|
||||
dispatchAccessibleClick(unlockButton);
|
||||
sinon.assert.calledWith(stubPublish, 'lockscreenslide-activate-right');
|
||||
});
|
||||
|
||||
test('Click camera button', function() {
|
||||
dispatchAccessibleClick(cameraButton);
|
||||
sinon.assert.calledWith(stubPublish, 'lockscreenslide-activate-left');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
28
gaia/apps/sharedtest/test/unit/manifest_helper_test.js
Executable file
28
gaia/apps/sharedtest/test/unit/manifest_helper_test.js
Executable file
|
|
@ -0,0 +1,28 @@
|
|||
'use strict';
|
||||
|
||||
/* global ManifestHelper */
|
||||
|
||||
require('/shared/js/manifest_helper.js');
|
||||
|
||||
suite('Manifest Helper', function() {
|
||||
|
||||
var manifest = {
|
||||
'name': 'Long Name'
|
||||
};
|
||||
|
||||
var manifestWithShortName = {
|
||||
'name': 'Long Name',
|
||||
'short_name': 'Short Name'
|
||||
};
|
||||
|
||||
test('displayName with short name', function() {
|
||||
var helper = new ManifestHelper(manifestWithShortName);
|
||||
assert.equal('Short Name', helper.displayName);
|
||||
});
|
||||
|
||||
test('displayName with no short name', function() {
|
||||
var helper = new ManifestHelper(manifest);
|
||||
assert.equal('Long Name', helper.displayName);
|
||||
});
|
||||
|
||||
});
|
||||
331
gaia/apps/sharedtest/test/unit/media_frame_test.js
Executable file
331
gaia/apps/sharedtest/test/unit/media_frame_test.js
Executable file
|
|
@ -0,0 +1,331 @@
|
|||
/*
|
||||
* Unit tests for the MediaFrame class from shared/js/media/media_frame.js
|
||||
*/
|
||||
/* global Promise */
|
||||
'use strict';
|
||||
|
||||
// This mock needs to be in place when we load MediaFrame, so it is here
|
||||
// rather than in suiteSetup. We remove it in suiteTeardown.
|
||||
var realGetFeature = navigator.getFeature;
|
||||
navigator.getFeature = function(feature) {
|
||||
if (feature === 'hardware.memory') {
|
||||
return Promise.resolve(4096); // 4gb of memory
|
||||
}
|
||||
else {
|
||||
return Promise.reject();
|
||||
}
|
||||
};
|
||||
|
||||
var Downsample = require('/shared/js/media/downsample.js');
|
||||
var MediaFrame = require('/shared/js/media/media_frame.js');
|
||||
|
||||
suite('Media Frame Unit Tests', function() {
|
||||
|
||||
var mockDeviceStorage = false;
|
||||
|
||||
suiteSetup(function() {
|
||||
this.clock = sinon.useFakeTimers();
|
||||
|
||||
if (!navigator.getDeviceStorage) {
|
||||
// create dummy getDeviceStorage for sinon overriding.
|
||||
navigator.getDeviceStorage = function() {};
|
||||
mockDeviceStorage = true;
|
||||
}
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
this.clock.restore();
|
||||
|
||||
if (mockDeviceStorage) {
|
||||
delete navigator.getDeviceStorage;
|
||||
}
|
||||
|
||||
navigator.getFeature = realGetFeature;
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
this.sandbox = sinon.sandbox.create();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
this.sandbox.restore();
|
||||
});
|
||||
|
||||
|
||||
suite('Runtime memory detection', function() {
|
||||
test('navigator.getFeature() is called', function() {
|
||||
// We're faking 4gb of memory, so we should get 4 * 10mb of decode size
|
||||
assert.equal(MediaFrame.maxImageDecodeSize, 40 * 1024 * 1024);
|
||||
});
|
||||
|
||||
test('computeMaxImageDecodeSize() works correctly', function() {
|
||||
assert.equal(MediaFrame.computeMaxImageDecodeSize(128), 2 * 1024 * 1024);
|
||||
assert.equal(MediaFrame.computeMaxImageDecodeSize(512), 5 * 1024 * 1024);
|
||||
assert.equal(
|
||||
MediaFrame.computeMaxImageDecodeSize(1024), 10 * 1024 * 1024
|
||||
);
|
||||
|
||||
// For low-memory devices, the image size may also depend on
|
||||
// screen size
|
||||
var size = MediaFrame.computeMaxImageDecodeSize(256);
|
||||
assert.ok(size === 3 * 1024 * 1024 || size == 2.5 * 1024 * 1024);
|
||||
});
|
||||
});
|
||||
|
||||
suite('#displayImage', function() {
|
||||
var frame;
|
||||
var dummyDiv;
|
||||
var dummyJPEGBlob;
|
||||
var dummyPNGBlob;
|
||||
|
||||
setup(function() {
|
||||
dummyJPEGBlob = new Blob(['empty-image'], {'type': 'image/jpeg'});
|
||||
dummyPNGBlob = new Blob(['empty-image'], {'type': 'image/png'});
|
||||
dummyDiv = document.createElement('div');
|
||||
frame = new MediaFrame(dummyDiv, false);
|
||||
});
|
||||
|
||||
// The tests that follow use unrealistically large images sizes
|
||||
// because mediaFrame uses window.innerWidth and window.innerHeight
|
||||
// and window.devicePixelRatio. If I could force those to 320, 480 and 1
|
||||
// the tests could use smaller image sizes. Instead, we use really big
|
||||
// fake image sizes to that things work as expected even if we're
|
||||
// running on a large screen.
|
||||
test('=> displayImage big JPEG image without preview', function() {
|
||||
frame.displayImage(dummyJPEGBlob, 8000, 6000, null, 0, false);
|
||||
assert.isTrue(frame.displayingImage);
|
||||
assert.isTrue(frame.displayingPreview);
|
||||
assert.isNull(frame.previewurl); // no separate preview image
|
||||
assert.notEqual(frame.previewSampleSize, Downsample.NONE);
|
||||
frame.reset();
|
||||
assert.isTrue(frame.displayingPreview);
|
||||
assert.isTrue(frame.displayingImage);
|
||||
assert.isNull(frame.previewurl);
|
||||
});
|
||||
|
||||
test('=> displayImage big PNG image without preview', function() {
|
||||
frame.displayImage(dummyPNGBlob, 8000, 6000, null, 0, false);
|
||||
assert.isTrue(frame.displayingImage);
|
||||
assert.isFalse(frame.displayingPreview);
|
||||
assert.isNull(frame.previewurl);
|
||||
frame.reset();
|
||||
assert.isFalse(frame.displayingPreview);
|
||||
assert.isTrue(frame.displayingImage);
|
||||
assert.isNull(frame.previewurl);
|
||||
});
|
||||
|
||||
test('=> displayImage small without preview', function() {
|
||||
frame.displayImage(dummyJPEGBlob, 160, 120, null, 0, false);
|
||||
assert.isTrue(frame.displayingImage);
|
||||
assert.isFalse(frame.displayingPreview);
|
||||
assert.isNull(frame.previewurl);
|
||||
frame.reset();
|
||||
assert.isFalse(frame.displayingPreview);
|
||||
assert.isTrue(frame.displayingImage);
|
||||
assert.isNull(frame.previewurl);
|
||||
});
|
||||
|
||||
test('=> displayImage embedded preview, bigEnough=false', function() {
|
||||
var preview = {
|
||||
'start': 1,
|
||||
'end': 2,
|
||||
'width': 40,
|
||||
'height': 30
|
||||
};
|
||||
frame.displayImage(dummyJPEGBlob, 8000, 6000, preview, 0, false);
|
||||
assert.isTrue(frame.displayingImage);
|
||||
assert.isTrue(frame.displayingPreview);
|
||||
assert.isNull(frame.previewurl);
|
||||
assert.notEqual(frame.previewSampleSize, Downsample.NONE);
|
||||
frame.reset();
|
||||
assert.isTrue(frame.displayingPreview);
|
||||
assert.isTrue(frame.displayingImage);
|
||||
assert.isNull(frame.previewurl);
|
||||
});
|
||||
|
||||
test('=> displayImage preview big enough, aspect ratio wrong', function() {
|
||||
var preview = {
|
||||
'start': 1,
|
||||
'end': 2,
|
||||
'width': 800,
|
||||
'height': 800
|
||||
};
|
||||
frame.displayImage(dummyJPEGBlob, 8000, 6000, preview, 0, false);
|
||||
assert.isTrue(frame.displayingPreview);
|
||||
assert.isTrue(frame.displayingImage);
|
||||
assert.isNull(frame.previewurl);
|
||||
assert.notEqual(frame.previewSampleSize, Downsample.NONE);
|
||||
frame.reset();
|
||||
assert.isTrue(frame.displayingPreview);
|
||||
assert.isTrue(frame.displayingImage);
|
||||
assert.isNull(frame.previewurl);
|
||||
});
|
||||
|
||||
test('=> displayImage preview big enough, aspect ratio ok', function() {
|
||||
var preview = {
|
||||
'start': 1,
|
||||
'end': 2,
|
||||
'width': 4000,
|
||||
'height': 3000
|
||||
};
|
||||
frame.displayImage(dummyJPEGBlob, 8000, 6000, preview, 0, false);
|
||||
assert.isTrue(frame.displayingPreview);
|
||||
assert.isTrue(frame.displayingImage);
|
||||
assert.ok(frame.previewurl);
|
||||
frame.reset();
|
||||
assert.isTrue(frame.displayingPreview);
|
||||
assert.isTrue(frame.displayingImage);
|
||||
assert.ok(frame.previewurl);
|
||||
});
|
||||
|
||||
test('=> displayImage preview file, get success', function() {
|
||||
var preview = {
|
||||
'filename': 'dummyFilename',
|
||||
'width': 4000,
|
||||
'height': 3000
|
||||
};
|
||||
|
||||
this.sandbox.stub(navigator, 'getDeviceStorage', function(type) {
|
||||
assert.equal(type, 'pictures');
|
||||
|
||||
function dummyGet(filename) {
|
||||
assert.equal(filename, 'dummyFilename');
|
||||
var retObj = {};
|
||||
|
||||
setTimeout(function() {
|
||||
retObj.result = new Blob(['empty-image'], {'type': 'image/jpeg'});
|
||||
retObj.onsuccess();
|
||||
assert.isTrue(frame.displayingPreview);
|
||||
assert.isTrue(frame.displayingImage);
|
||||
assert.ok(frame.previewurl);
|
||||
frame.reset();
|
||||
});
|
||||
|
||||
return retObj;
|
||||
}
|
||||
|
||||
return {
|
||||
get: dummyGet
|
||||
};
|
||||
});
|
||||
|
||||
frame.displayImage(dummyJPEGBlob, 8000, 6000, preview, 0, false);
|
||||
|
||||
// Move time forwards so
|
||||
// stub callback fires
|
||||
this.clock.tick(1);
|
||||
});
|
||||
|
||||
test('=> displayImage preview file, get error', function() {
|
||||
var preview = {
|
||||
'filename': 'dummyFilename',
|
||||
'width': 4000,
|
||||
'height': 3000
|
||||
};
|
||||
|
||||
this.sandbox.stub(navigator, 'getDeviceStorage', function(type) {
|
||||
assert.equal(type, 'pictures');
|
||||
|
||||
function dummyGet(filename) {
|
||||
assert.equal(filename, 'dummyFilename');
|
||||
var retObj = {};
|
||||
|
||||
setTimeout(function() {
|
||||
retObj.onerror();
|
||||
assert.isFalse(frame.displayingPreview);
|
||||
assert.isNull(frame.previewurl);
|
||||
assert.isTrue(frame.displayingImage);
|
||||
frame.reset();
|
||||
});
|
||||
|
||||
return retObj;
|
||||
}
|
||||
|
||||
return {
|
||||
get: dummyGet
|
||||
};
|
||||
});
|
||||
|
||||
frame.displayImage(dummyPNGBlob, 8000, 6000, preview, 0, false);
|
||||
|
||||
// Move time forwards so
|
||||
// stub callback fires
|
||||
this.clock.tick(1);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Max decode size', function() {
|
||||
var frame;
|
||||
var dummyDiv;
|
||||
var dummyJPEGBlob;
|
||||
|
||||
setup(function() {
|
||||
dummyJPEGBlob = new Blob(['empty-image'], {'type': 'image/jpeg'});
|
||||
dummyDiv = document.createElement('div');
|
||||
});
|
||||
|
||||
test('no decode limits', function() {
|
||||
// Even if the image is really big, we will decode it full size
|
||||
// if no limits are set
|
||||
MediaFrame.maxImageDecodeSize = 0;
|
||||
frame = new MediaFrame(dummyDiv, false);
|
||||
frame.displayImage(dummyJPEGBlob, 10000, 10000);
|
||||
assert.deepEqual(frame.fullSampleSize, Downsample.NONE);
|
||||
});
|
||||
|
||||
test('limit decode size with constructor', function() {
|
||||
MediaFrame.maxImageDecodeSize = 0;
|
||||
// With a 1mp limit, a big image will be downsampled
|
||||
frame = new MediaFrame(dummyDiv, false, 1024 * 1024);
|
||||
frame.displayImage(dummyJPEGBlob, 10000, 10000);
|
||||
assert.notEqual(frame.fullSampleSize, Downsample.NONE);
|
||||
|
||||
// With a 1mp limit, a small image will not be downsampled
|
||||
frame = new MediaFrame(dummyDiv, false, 1024 * 1024);
|
||||
frame.displayImage(dummyJPEGBlob, 1000, 1000);
|
||||
assert.deepEqual(frame.fullSampleSize, Downsample.NONE);
|
||||
});
|
||||
|
||||
test('limit decode size with memory', function() {
|
||||
// Set a 1mp limit
|
||||
MediaFrame.maxImageDecodeSize = 1024*1024;
|
||||
|
||||
// Big images will be downsampled
|
||||
frame = new MediaFrame(dummyDiv, false);
|
||||
frame.displayImage(dummyJPEGBlob, 10000, 10000);
|
||||
assert.notEqual(frame.fullSampleSize, Downsample.NONE);
|
||||
|
||||
// Small images will not be downsampled
|
||||
frame.displayImage(dummyJPEGBlob, 1000, 1000);
|
||||
assert.deepEqual(frame.fullSampleSize, Downsample.NONE);
|
||||
});
|
||||
|
||||
test('memory limit lower than constructor', function() {
|
||||
// Set a 1mp limit based on memory
|
||||
MediaFrame.maxImageDecodeSize = 1024*1024;
|
||||
|
||||
// 4mp images will be downsampled even though constructor allows 5mp
|
||||
frame = new MediaFrame(dummyDiv, false, 5 * 1024 * 1024);
|
||||
frame.displayImage(dummyJPEGBlob, 2000, 2000);
|
||||
assert.notEqual(frame.fullSampleSize, Downsample.NONE);
|
||||
|
||||
// Small images will not be downsampled
|
||||
frame.displayImage(dummyJPEGBlob, 1000, 1000);
|
||||
assert.deepEqual(frame.fullSampleSize, Downsample.NONE);
|
||||
});
|
||||
|
||||
test('constructor limit lower than memory', function() {
|
||||
// Set a 5mp limit based on memory
|
||||
MediaFrame.maxImageDecodeSize = 5*1024*1024;
|
||||
|
||||
// 4mp images will be downsampled even though memory allows 5mp
|
||||
frame = new MediaFrame(dummyDiv, false, 1024 * 1024);
|
||||
frame.displayImage(dummyJPEGBlob, 2000, 2000);
|
||||
assert.notEqual(frame.fullSampleSize, Downsample.NONE);
|
||||
|
||||
// Small images will not be downsampled
|
||||
frame.displayImage(dummyJPEGBlob, 1000, 1000);
|
||||
assert.deepEqual(frame.fullSampleSize, Downsample.NONE);
|
||||
});
|
||||
});
|
||||
});
|
||||
505
gaia/apps/sharedtest/test/unit/mediadb_test.js
Executable file
505
gaia/apps/sharedtest/test/unit/mediadb_test.js
Executable file
|
|
@ -0,0 +1,505 @@
|
|||
'use strict';
|
||||
|
||||
require('/shared/js/device_storage/enumerate_all.js');
|
||||
require('/shared/js/mediadb.js');
|
||||
|
||||
suite('MediaDB', function() {
|
||||
if (navigator.getDeviceStorage('pictures') == null) {
|
||||
test('navigator.getDeviceStorage() returns null on this platform; ' +
|
||||
'skipping all MediaDB tests',
|
||||
function() {});
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
// These tests are currently failing and have been temporarily disabled as per
|
||||
// Bug 838993. They should be fixed and re-enabled as soon as possible as per
|
||||
// Bug 840493.
|
||||
var directory = 'mediadbtests/1/';
|
||||
|
||||
// These are the file names, and file content we'll use
|
||||
var files = {
|
||||
'1.png': {word: 'first', number: 9, x: 1},
|
||||
'2.png': {word: 'second', number: 8, x: 1},
|
||||
'3.png': {word: 'tri', number: 7, x: 0},
|
||||
'4.png': {word: 'quad', number: 6, x: 0},
|
||||
'111.png': {word: 'one hundred and eleven', number: 5, x: 0},
|
||||
'sub/1.png': {word: 'subdirectory', x: 0}
|
||||
};
|
||||
|
||||
// Create a fake png file
|
||||
function createFile(directory, name, content, callback) {
|
||||
var storage = navigator.getDeviceStorage('pictures');
|
||||
var blob = new Blob([JSON.stringify(content)], {type: 'image/png'});
|
||||
storage.addNamed(blob, directory + name).onsuccess = function() {
|
||||
if (callback) callback();
|
||||
};
|
||||
}
|
||||
|
||||
function deleteFile(directory, name, callback) {
|
||||
var storage = navigator.getDeviceStorage('pictures');
|
||||
storage.delete(directory + name).onsuccess = function() {
|
||||
if (callback) callback();
|
||||
};
|
||||
}
|
||||
|
||||
// Use the contents of the file as our metadata
|
||||
function fakeMetadataParser(blob, callback) {
|
||||
var reader = new FileReader();
|
||||
reader.readAsText(blob);
|
||||
reader.onload = function() {
|
||||
callback(JSON.parse(reader.result));
|
||||
};
|
||||
}
|
||||
|
||||
// Delete all files from a directory
|
||||
function clearDirectory(directory, callback) {
|
||||
var storage = navigator.getDeviceStorage('pictures');
|
||||
var cursor = storage.enumerate(directory);
|
||||
cursor.onsuccess = function() {
|
||||
var file = cursor.result;
|
||||
if (file) {
|
||||
storage.delete(directory + cursor.result.name).onsuccess = function() {
|
||||
cursor.continue();
|
||||
};
|
||||
}
|
||||
else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
cursor.onerror = callback;
|
||||
}
|
||||
|
||||
test('API existence tests', function() {
|
||||
// Check that MediaDB exists
|
||||
assert.ok(MediaDB);
|
||||
|
||||
// Check that its constants exist
|
||||
assert.equal(MediaDB.OPENING, 'opening');
|
||||
assert.equal(MediaDB.READY, 'ready');
|
||||
assert.equal(MediaDB.NOCARD, 'nocard');
|
||||
assert.equal(MediaDB.UNMOUNTED, 'unmounted');
|
||||
assert.equal(MediaDB.CLOSED, 'closed');
|
||||
|
||||
// Check that its methods, and only its methods exist
|
||||
assert.equal(Object.keys(MediaDB.prototype)
|
||||
.filter(function(m) {
|
||||
return typeof MediaDB.prototype[m] === 'function';
|
||||
})
|
||||
.sort()
|
||||
.join(' '),
|
||||
'addEventListener addFile cancelEnumeration close count ' +
|
||||
'deleteFile enumerate getFile removeEventListener ' +
|
||||
'updateMetadata');
|
||||
|
||||
});
|
||||
|
||||
|
||||
test('events and scanning', function(done) {
|
||||
|
||||
function continueTest() {
|
||||
testGenerator.next();
|
||||
}
|
||||
|
||||
var testGenerator = (function() {
|
||||
// make sure the directory is empty
|
||||
yield clearDirectory(directory, continueTest);
|
||||
|
||||
// Create the test files one at a time
|
||||
for (var name in files) {
|
||||
// create a file and return
|
||||
yield createFile(directory, name, files[name], continueTest);
|
||||
}
|
||||
|
||||
// Once the files are in place, open the mediadb
|
||||
var mediadb = new MediaDB('pictures',
|
||||
fakeMetadataParser,
|
||||
{
|
||||
directory: directory,
|
||||
indexes: ['size',
|
||||
'metadata.word',
|
||||
'metadata.number',
|
||||
'metadata.x']
|
||||
});
|
||||
|
||||
var events = '';
|
||||
|
||||
// Set up event handlers to keep track of what it does
|
||||
mediadb.onready = function() {
|
||||
events += ' ready';
|
||||
};
|
||||
|
||||
mediadb.onscanstart = function() {
|
||||
events += ' scanstart';
|
||||
};
|
||||
|
||||
mediadb.oncreated = function(e) {
|
||||
events += ' ';
|
||||
events += e.detail.map(function(f) { return f.name }).sort().join(' ');
|
||||
};
|
||||
|
||||
mediadb.ondeleted = function(e) {
|
||||
events += ' deleted';
|
||||
};
|
||||
|
||||
mediadb.onscanend = function() {
|
||||
events += ' scanend';
|
||||
mediadb.close();
|
||||
}
|
||||
|
||||
mediadb.onunavailable = function() {
|
||||
events += ' unavailable';
|
||||
continueTest();
|
||||
}
|
||||
yield;
|
||||
|
||||
var expectedEvents = ' ready scanstart ' +
|
||||
Object.keys(files).sort().join(' ') +
|
||||
' scanend unavailable';
|
||||
assert.equal(events, expectedEvents);
|
||||
yield done();
|
||||
|
||||
}());
|
||||
continueTest();
|
||||
});
|
||||
|
||||
// Test created and deleted events when creating and deleting files
|
||||
// one at a time using device storage externally.
|
||||
// And also test enumeration methods
|
||||
test('create, count, enumerate, updateMetadata, delete', function(done) {
|
||||
|
||||
function continueTest() {
|
||||
testGenerator.next();
|
||||
}
|
||||
|
||||
var testGenerator = (function() {
|
||||
// make sure the directory is empty
|
||||
yield clearDirectory(directory, continueTest);
|
||||
|
||||
// Create a mediadb
|
||||
var mediadb = new MediaDB('pictures',
|
||||
fakeMetadataParser,
|
||||
{
|
||||
directory: directory,
|
||||
indexes: ['size',
|
||||
'metadata.word',
|
||||
'metadata.number',
|
||||
'metadata.x']
|
||||
});
|
||||
|
||||
// Wait until it has finished scanning
|
||||
yield mediadb.addEventListener('scanend', continueTest);
|
||||
|
||||
yield mediadb.count(function(n) {
|
||||
assert.equal(n, 0, 'db should be empty');
|
||||
continueTest();
|
||||
});
|
||||
|
||||
|
||||
var expectedsize = 0;
|
||||
// Create the test files one at a time
|
||||
for (var name in files) {
|
||||
// create a file and return
|
||||
createFile(directory, name, files[name]);
|
||||
|
||||
// Wait for the 'created' event
|
||||
yield mediadb.oncreated = function(e) {
|
||||
assert.equal(e.type, 'created');
|
||||
assert.equal(Array.isArray(e.detail), true);
|
||||
assert.equal(e.detail.length, 1);
|
||||
assert.equal(e.detail[0].name, name);
|
||||
continueTest();
|
||||
}
|
||||
|
||||
// Check that the size is what we expect
|
||||
expectedsize++;
|
||||
yield mediadb.count(function(n) {
|
||||
assert.equal(n, expectedsize);
|
||||
continueTest();
|
||||
});
|
||||
}
|
||||
|
||||
// Start an enumeration and then cancel it right away.
|
||||
// Fail if the callback is ever called
|
||||
var handle = mediadb.enumerate(function() {
|
||||
assert.ok(false, 'enumeration was not cancelled');
|
||||
});
|
||||
mediadb.cancelEnumeration(handle);
|
||||
|
||||
var filenames;
|
||||
|
||||
function enumerate(key, range, direction) {
|
||||
filenames = [];
|
||||
mediadb.enumerate(key, range, direction, function(fileinfo) {
|
||||
if (fileinfo)
|
||||
filenames.push(fileinfo.name);
|
||||
else
|
||||
continueTest(filenames);
|
||||
});
|
||||
}
|
||||
|
||||
// For default enumeration we expect the filenames in alphabetical order
|
||||
yield enumerate();
|
||||
assert.deepEqual(filenames,
|
||||
Object.keys(files).sort(),
|
||||
'default enumeration order');
|
||||
|
||||
yield enumerate('name', null, 'prev');
|
||||
assert.deepEqual(filenames,
|
||||
Object.keys(files).sort().reverse(),
|
||||
'reverse order');
|
||||
|
||||
|
||||
// If we enumerate by size we expect them in length order
|
||||
yield enumerate('size');
|
||||
assert.deepEqual(filenames, [
|
||||
'sub/1.png', '3.png', '4.png', '1.png', '2.png', '111.png'
|
||||
], 'size order');
|
||||
|
||||
// Enumerate by metadata word order
|
||||
yield enumerate('metadata.word');
|
||||
assert.deepEqual(filenames, [
|
||||
'1.png', '111.png', '4.png', '2.png', 'sub/1.png', '3.png'
|
||||
], 'metadata word order');
|
||||
|
||||
// Enumerate by metadata number
|
||||
yield enumerate('metadata.number');
|
||||
assert.deepEqual(filenames, [
|
||||
'111.png', '4.png', '3.png', '2.png', '1.png'
|
||||
], 'metadata number order');
|
||||
|
||||
// Update metadata.number for one of the files
|
||||
yield mediadb.updateMetadata('111.png', { number: 1000 }, continueTest);
|
||||
|
||||
// Enumerate by metadata number again and expect a different order
|
||||
yield enumerate('metadata.number');
|
||||
assert.deepEqual(filenames, [
|
||||
'4.png', '3.png', '2.png', '1.png', '111.png'
|
||||
], 'metadata number order');
|
||||
|
||||
// Search for files that have x = 1
|
||||
yield enumerate('metadata.x', 1);
|
||||
assert.equal(filenames.length, 2);
|
||||
assert.notEqual(filenames.indexOf('1.png'), -1);
|
||||
assert.notEqual(filenames.indexOf('2.png'), -1);
|
||||
|
||||
// Search for files whose word is > 'q'
|
||||
yield enumerate('metadata.word', IDBKeyRange.lowerBound('q'));
|
||||
assert.deepEqual(filenames, [
|
||||
'4.png', '2.png', 'sub/1.png', '3.png'
|
||||
], 'word > q');
|
||||
|
||||
// Same, but return in reverse order
|
||||
yield enumerate('metadata.word', IDBKeyRange.lowerBound('q'), 'prev');
|
||||
assert.deepEqual(filenames, [
|
||||
'4.png', '2.png', 'sub/1.png', '3.png'
|
||||
].reverse(), 'word > q reversed');
|
||||
|
||||
// Now delete the files, reversing what we did at the start of the test
|
||||
for (var name in files) {
|
||||
// create a file and return
|
||||
deleteFile(directory, name);
|
||||
|
||||
// Wait for the 'deleted' event
|
||||
yield mediadb.ondeleted = function(e) {
|
||||
assert.equal(e.type, 'deleted');
|
||||
assert.equal(Array.isArray(e.detail), true);
|
||||
assert.equal(e.detail.length, 1);
|
||||
assert.equal(e.detail[0], name);
|
||||
continueTest();
|
||||
}
|
||||
|
||||
// Check that the size is what we expect
|
||||
expectedsize--;
|
||||
yield mediadb.count(function(n) {
|
||||
assert.equal(n, expectedsize);
|
||||
continueTest();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
mediadb.onunavailable = function() { done(); }
|
||||
yield mediadb.close();
|
||||
}());
|
||||
|
||||
continueTest();
|
||||
});
|
||||
|
||||
// Much like the test above, but use mediadb.addFile and deleteFile()
|
||||
// instead of raw device storage, and don't repeat all the enumeration tests
|
||||
test('addFile, deleteFile', function(done) {
|
||||
|
||||
function continueTest() {
|
||||
testGenerator.next();
|
||||
}
|
||||
|
||||
var testGenerator = (function() {
|
||||
// make sure the directory is empty
|
||||
yield clearDirectory(directory, continueTest);
|
||||
|
||||
// Create a mediadb
|
||||
var mediadb = new MediaDB('pictures',
|
||||
fakeMetadataParser,
|
||||
{
|
||||
directory: directory,
|
||||
indexes: ['size',
|
||||
'metadata.word',
|
||||
'metadata.number',
|
||||
'metadata.x']
|
||||
});
|
||||
|
||||
// Wait until it has finished scanning
|
||||
yield mediadb.addEventListener('scanend', continueTest);
|
||||
|
||||
yield mediadb.count(function(n) {
|
||||
assert.equal(n, 0, 'db should be empty');
|
||||
continueTest();
|
||||
});
|
||||
|
||||
var expectedsize = 0;
|
||||
// Create the test files one at a time
|
||||
for (var name in files) {
|
||||
// create a file and return
|
||||
mediadb.addFile(name,
|
||||
new Blob([JSON.stringify(files[name])],
|
||||
{type: 'image/png'}));
|
||||
|
||||
// Wait for the 'created' event
|
||||
yield mediadb.oncreated = function(e) {
|
||||
assert.equal(e.type, 'created');
|
||||
assert.equal(Array.isArray(e.detail), true);
|
||||
assert.equal(e.detail.length, 1);
|
||||
assert.equal(e.detail[0].name, name);
|
||||
continueTest();
|
||||
}
|
||||
|
||||
// Check that the size is what we expect
|
||||
expectedsize++;
|
||||
yield mediadb.count(function(n) {
|
||||
assert.equal(n, expectedsize);
|
||||
continueTest();
|
||||
});
|
||||
}
|
||||
|
||||
var filenames;
|
||||
|
||||
function enumerate(key, range, direction) {
|
||||
filenames = [];
|
||||
mediadb.enumerate(key, range, direction, function(fileinfo) {
|
||||
if (fileinfo)
|
||||
filenames.push(fileinfo.name);
|
||||
else
|
||||
continueTest(filenames);
|
||||
});
|
||||
}
|
||||
|
||||
// For default enumeration we expect the filenames in alphabetical order
|
||||
yield enumerate();
|
||||
assert.deepEqual(filenames,
|
||||
Object.keys(files).sort(),
|
||||
'default enumeration order');
|
||||
|
||||
// Now delete the files, reversing what we did at the start of the test
|
||||
for (var name in files) {
|
||||
// create a file and return
|
||||
mediadb.deleteFile(name);
|
||||
|
||||
// Wait for the 'deleted' event
|
||||
yield mediadb.ondeleted = function(e) {
|
||||
assert.equal(e.type, 'deleted');
|
||||
assert.equal(Array.isArray(e.detail), true);
|
||||
assert.equal(e.detail.length, 1);
|
||||
assert.equal(e.detail[0], name);
|
||||
continueTest();
|
||||
}
|
||||
|
||||
// Check that the size is what we expect
|
||||
expectedsize--;
|
||||
yield mediadb.count(function(n) {
|
||||
assert.equal(n, expectedsize);
|
||||
continueTest();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
mediadb.onunavailable = function() { done(); }
|
||||
yield mediadb.close();
|
||||
}());
|
||||
|
||||
continueTest();
|
||||
});
|
||||
|
||||
// Much like the test above, but use mediadb.addFile and deleteFile()
|
||||
// instead of raw device storage, and don't repeat all the enumeration tests
|
||||
test('batched notifications', function(done) {
|
||||
|
||||
function continueTest() {
|
||||
testGenerator.next();
|
||||
}
|
||||
|
||||
var testGenerator = (function() {
|
||||
// make sure the directory is empty
|
||||
yield clearDirectory(directory, continueTest);
|
||||
|
||||
// Create a mediadb
|
||||
var mediadb = new MediaDB('pictures',
|
||||
fakeMetadataParser,
|
||||
{
|
||||
directory: directory,
|
||||
indexes: ['size',
|
||||
'metadata.word',
|
||||
'metadata.number',
|
||||
'metadata.x']
|
||||
});
|
||||
|
||||
// Wait until it has finished scanning
|
||||
yield mediadb.addEventListener('scanend', continueTest);
|
||||
|
||||
yield mediadb.count(function(n) {
|
||||
assert.equal(n, 0, 'db should be empty');
|
||||
continueTest();
|
||||
});
|
||||
|
||||
var filenames = Object.keys(files).sort();
|
||||
|
||||
// Create the test files all at once
|
||||
for (var name in files) {
|
||||
// create a file and return
|
||||
mediadb.addFile(name,
|
||||
new Blob([JSON.stringify(files[name])],
|
||||
{type: 'image/png'}));
|
||||
}
|
||||
|
||||
// Wait for the 'created' event
|
||||
yield mediadb.oncreated = function(e) {
|
||||
assert.equal(e.type, 'created');
|
||||
assert.equal(Array.isArray(e.detail), true);
|
||||
assert.equal(e.detail.length, filenames.length);
|
||||
assert.deepEqual(e.detail.map(function(f) { return f.name }).sort(),
|
||||
filenames);
|
||||
continueTest();
|
||||
}
|
||||
|
||||
// Now delete the files
|
||||
for (var name in files) {
|
||||
// create a file and return
|
||||
mediadb.deleteFile(name);
|
||||
}
|
||||
|
||||
// Wait for the 'deleted' event
|
||||
yield mediadb.ondeleted = function(e) {
|
||||
assert.equal(e.type, 'deleted');
|
||||
assert.equal(Array.isArray(e.detail), true);
|
||||
assert.equal(e.detail.length, filenames.length);
|
||||
assert.deepEqual(e.detail.sort(), filenames);
|
||||
continueTest();
|
||||
}
|
||||
|
||||
mediadb.onunavailable = function() { done(); }
|
||||
yield mediadb.close();
|
||||
}());
|
||||
|
||||
continueTest();
|
||||
});
|
||||
*/
|
||||
});
|
||||
49
gaia/apps/sharedtest/test/unit/metrics_helper_test.js
Executable file
49
gaia/apps/sharedtest/test/unit/metrics_helper_test.js
Executable file
|
|
@ -0,0 +1,49 @@
|
|||
'use strict';
|
||||
/* global MockNavigatormozApps */
|
||||
|
||||
require('/shared/test/unit/mocks/mock_navigator_moz_apps.js');
|
||||
|
||||
require('/shared/js/metrics_helper.js');
|
||||
|
||||
suite('metricshelper', function() {
|
||||
var realMozApps;
|
||||
var subject = null;
|
||||
|
||||
suiteSetup(function() {
|
||||
realMozApps = navigator.mozApps;
|
||||
navigator.mozApps = MockNavigatormozApps;
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
navigator.mozApps = realMozApps;
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
subject = new window.MetricsHelper();
|
||||
subject._portMetrics = { postMessage: function() {} };
|
||||
});
|
||||
|
||||
suite('init', function() {
|
||||
test('will call metrics init method', function() {
|
||||
subject.init();
|
||||
|
||||
MockNavigatormozApps.mTriggerLastRequestSuccess();
|
||||
assert.equal(MockNavigatormozApps.mLastConnectionKeyword,
|
||||
'app-metrics');
|
||||
MockNavigatormozApps.mLastConnectionCallback(['fakePort']);
|
||||
|
||||
assert.ok(subject._initialized);
|
||||
});
|
||||
});
|
||||
|
||||
suite('report', function() {
|
||||
test('will postMessage to system app', function() {
|
||||
var stub = this.sinon.stub(subject._portMetrics, 'postMessage');
|
||||
subject._initialized = true;
|
||||
subject.report('websearch', 'providerTest');
|
||||
|
||||
assert.ok(stub.calledWith({action: 'websearch',
|
||||
data: 'providerTest'}));
|
||||
});
|
||||
});
|
||||
});
|
||||
110
gaia/apps/sharedtest/test/unit/mime_mapper_test.js
Executable file
110
gaia/apps/sharedtest/test/unit/mime_mapper_test.js
Executable file
|
|
@ -0,0 +1,110 @@
|
|||
'use strict';
|
||||
|
||||
/* global MimeMapper */
|
||||
|
||||
require('/shared/js/mime_mapper.js');
|
||||
|
||||
suite('Shared library mime_mapper api tests', function() {
|
||||
|
||||
suite('parse extension test', function() {
|
||||
test('Filename with extension', function() {
|
||||
var ext = MimeMapper._parseExtension('test.jpg');
|
||||
assert.equal(ext, 'jpg');
|
||||
});
|
||||
|
||||
test('Filename without extension', function() {
|
||||
var ext = MimeMapper._parseExtension('test');
|
||||
assert.equal(ext, '');
|
||||
});
|
||||
|
||||
test('Filename is empty string', function() {
|
||||
var ext = MimeMapper._parseExtension('');
|
||||
assert.equal(ext, '');
|
||||
});
|
||||
});
|
||||
|
||||
suite('guess valid type from file properties test', function() {
|
||||
test('Filename/mimetype is matched', function() {
|
||||
var mimetype =
|
||||
MimeMapper.guessTypeFromFileProperties('test.jpg', 'image/jpeg');
|
||||
assert.equal(mimetype, 'image/jpeg');
|
||||
});
|
||||
|
||||
test('Filename without extension but mimetype supported', function() {
|
||||
var mimetype =
|
||||
MimeMapper.guessTypeFromFileProperties('test', 'image/jpeg');
|
||||
assert.equal(mimetype, 'image/jpeg');
|
||||
});
|
||||
|
||||
test('Extension unsupported but mimetype supported', function() {
|
||||
var mimetype =
|
||||
MimeMapper.guessTypeFromFileProperties('test.txt', 'image/jpeg');
|
||||
assert.equal(mimetype, 'image/jpeg');
|
||||
});
|
||||
|
||||
test('Filename with valid extension but mimetype unsupported', function() {
|
||||
var mimetype =
|
||||
MimeMapper.guessTypeFromFileProperties('test.jpg',
|
||||
'application/download');
|
||||
assert.equal(mimetype, 'image/jpeg');
|
||||
});
|
||||
|
||||
test('Filename and mimetype are both unsupported', function() {
|
||||
var mimetype =
|
||||
MimeMapper.guessTypeFromFileProperties('test',
|
||||
'application/download');
|
||||
assert.equal(mimetype, '');
|
||||
});
|
||||
});
|
||||
|
||||
suite('Check is filename matches type test', function() {
|
||||
test('Extension/mimetype matches', function() {
|
||||
var match = MimeMapper.isFilenameMatchesType('test.jpg', 'image/jpeg');
|
||||
assert.isTrue(match);
|
||||
});
|
||||
|
||||
test('Extension/mimetype does not match', function() {
|
||||
var match = MimeMapper.isFilenameMatchesType('test.txt', 'image/jpeg');
|
||||
assert.isFalse(match);
|
||||
});
|
||||
|
||||
test('Filename without extension', function() {
|
||||
var match = MimeMapper.isFilenameMatchesType('test', 'image/jpeg');
|
||||
assert.isFalse(match);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Ensure filename matches type test', function() {
|
||||
test('Filename/mimetype is matched', function() {
|
||||
var filename =
|
||||
MimeMapper.ensureFilenameMatchesType('test.jpg', 'image/jpeg');
|
||||
assert.equal(filename, 'test.jpg');
|
||||
});
|
||||
|
||||
test('Filename without extension but mimetype supported', function() {
|
||||
var filename =
|
||||
MimeMapper.ensureFilenameMatchesType('test', 'image/jpeg');
|
||||
assert.equal(filename, 'test.jpg');
|
||||
});
|
||||
|
||||
test('Extension unsupported but mimetype supported', function() {
|
||||
var filename =
|
||||
MimeMapper.ensureFilenameMatchesType('test.txt', 'image/jpeg');
|
||||
assert.equal(filename, 'test.txt.jpg');
|
||||
});
|
||||
|
||||
test('Filename with valid extension but mimetype unsupported', function() {
|
||||
var filename =
|
||||
MimeMapper.ensureFilenameMatchesType('test.jpg',
|
||||
'application/download');
|
||||
assert.equal(filename, 'test.jpg');
|
||||
});
|
||||
|
||||
test('Filename and mimetype are both unsupported', function() {
|
||||
var filename =
|
||||
MimeMapper.ensureFilenameMatchesType('test',
|
||||
'application/download');
|
||||
assert.equal(filename, 'test');
|
||||
});
|
||||
});
|
||||
});
|
||||
1521
gaia/apps/sharedtest/test/unit/moz_intl_test.js
Executable file
1521
gaia/apps/sharedtest/test/unit/moz_intl_test.js
Executable file
File diff suppressed because it is too large
Load diff
439
gaia/apps/sharedtest/test/unit/multi_sim_action_button_test.js
Executable file
439
gaia/apps/sharedtest/test/unit/multi_sim_action_button_test.js
Executable file
|
|
@ -0,0 +1,439 @@
|
|||
/* globals MultiSimActionButton, MocksHelper, MockL10n,
|
||||
MockNavigatorMozIccManager, MockNavigatorMozTelephony,
|
||||
MockSettingsListener, MockTelephonyHelper, CustomElementsHelper,
|
||||
ALWAYS_ASK_OPTION_VALUE
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
require('/shared/test/unit/mocks/mock_l10n.js');
|
||||
require('/shared/test/unit/mocks/mock_lazy_loader.js');
|
||||
require('/shared/test/unit/mocks/mock_navigator_moz_icc_manager.js');
|
||||
require('/shared/test/unit/mocks/mock_navigator_moz_telephony.js');
|
||||
require('/shared/test/unit/mocks/mock_settings_listener.js');
|
||||
require('/shared/test/unit/mocks/dialer/mock_telephony_helper.js');
|
||||
require(
|
||||
'/shared/test/unit/mocks/elements/gaia_sim_picker/mock_gaia_sim_picker.js');
|
||||
|
||||
require('/shared/js/multi_sim_action_button.js');
|
||||
|
||||
var mocksHelperForMultiSimActionButton = new MocksHelper([
|
||||
'LazyLoader',
|
||||
'GaiaSimPicker',
|
||||
'SettingsListener'
|
||||
]).init();
|
||||
|
||||
var customElementsHelperForMultiSimActionButton = new CustomElementsHelper([
|
||||
'GaiaSimPicker'
|
||||
]);
|
||||
|
||||
suite('multi SIM action button', function() {
|
||||
var subject;
|
||||
var realMozSettings;
|
||||
var realMozTelephony;
|
||||
var realMozL10n;
|
||||
var realMozIccManager;
|
||||
var realTelephonyHelper;
|
||||
var phoneNumber;
|
||||
var button;
|
||||
var simPicker;
|
||||
var expectedCardIndex;
|
||||
var setCardIndex;
|
||||
|
||||
mocksHelperForMultiSimActionButton.attachTestHelpers();
|
||||
|
||||
var initSubject = function(options) {
|
||||
options = options || {};
|
||||
var callback = options.callback || function() {};
|
||||
var noSettingsTriggerCallback = options.noSettingsTriggerCallback || false;
|
||||
|
||||
MockTelephonyHelper.mInUseSim = setCardIndex;
|
||||
|
||||
button = options.button || document.createElement('button');
|
||||
subject = new MultiSimActionButton(
|
||||
button,
|
||||
callback,
|
||||
'ril.telephony.defaultServiceId',
|
||||
phoneNumberGetter
|
||||
);
|
||||
|
||||
// MockSettingsListener doesn't simulate the regular behavior of triggering
|
||||
// the callback as soon as the pref is loaded, so we have to simulate it
|
||||
// once the subject is initialized. We can't alter MockSettingsListener
|
||||
// since doing so makes a ton of other tests fail.
|
||||
if (!noSettingsTriggerCallback) {
|
||||
MockSettingsListener.mTriggerCallback(
|
||||
'ril.telephony.defaultServiceId', setCardIndex);
|
||||
}
|
||||
};
|
||||
|
||||
var simulateClick = function() {
|
||||
var ev = new MouseEvent('click', {
|
||||
view: window,
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
});
|
||||
button.dispatchEvent(ev);
|
||||
};
|
||||
|
||||
var simulateContextMenu = function() {
|
||||
var ev = document.createEvent('MouseEvents');
|
||||
ev.initMouseEvent('contextmenu', true, false, window, 0, 0, 0, 0, 0,
|
||||
false, false, false, false, 2, null);
|
||||
button.dispatchEvent(ev);
|
||||
};
|
||||
|
||||
var phoneNumberGetter = function() {
|
||||
return phoneNumber;
|
||||
};
|
||||
|
||||
var shouldUsePrimarySimCard = function() {
|
||||
var callbackStub = this.sinon.stub();
|
||||
initSubject({ callback: callbackStub });
|
||||
|
||||
phoneNumber = '0145345520';
|
||||
simulateClick();
|
||||
sinon.assert.calledWith(callbackStub, phoneNumber, expectedCardIndex);
|
||||
};
|
||||
|
||||
suiteSetup(function() {
|
||||
realMozTelephony = navigator.mozTelephony;
|
||||
navigator.mozTelephony = MockNavigatorMozTelephony;
|
||||
|
||||
realMozL10n = navigator.mozL10n;
|
||||
navigator.mozL10n = MockL10n;
|
||||
|
||||
realMozIccManager = navigator.mozIccManager;
|
||||
navigator.mozIccManager = MockNavigatorMozIccManager;
|
||||
|
||||
realTelephonyHelper = window.TelephonyHelper;
|
||||
window.TelephonyHelper = null;
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
navigator.mozSettings = realMozSettings;
|
||||
navigator.mozTelephony = realMozTelephony;
|
||||
navigator.mozL10n = realMozL10n;
|
||||
navigator.mozIccManager = realMozIccManager;
|
||||
window.TelephonyHelper = realTelephonyHelper;
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
document.body.innerHTML =
|
||||
'<gaia-sim-picker id="sim-picker"></gaia-sim-picker>';
|
||||
customElementsHelperForMultiSimActionButton.resolve();
|
||||
simPicker = document.getElementById('sim-picker');
|
||||
|
||||
phoneNumber = '';
|
||||
initSubject();
|
||||
|
||||
setCardIndex = expectedCardIndex = 0;
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
document.body.innerHTML = '';
|
||||
|
||||
MockNavigatorMozTelephony.mTeardown();
|
||||
MockNavigatorMozIccManager.mTeardown();
|
||||
MockTelephonyHelper.mTeardown();
|
||||
});
|
||||
|
||||
suite('0 SIMs', function() {
|
||||
setup(function() {
|
||||
setCardIndex = undefined;
|
||||
expectedCardIndex = 0;
|
||||
initSubject();
|
||||
});
|
||||
|
||||
test('should not show SIM picker menu when long pressing', function() {
|
||||
phoneNumber = '15555555555';
|
||||
var showSpy = this.sinon.spy(simPicker, 'getOrPick');
|
||||
simulateContextMenu();
|
||||
sinon.assert.notCalled(showSpy);
|
||||
});
|
||||
|
||||
test('should use first SIM slot (which is empty)', shouldUsePrimarySimCard);
|
||||
});
|
||||
|
||||
[0, 1].forEach(function(cardIndex) {
|
||||
suite('1 SIM in slot ' + cardIndex, function() {
|
||||
setup(function() {
|
||||
navigator.mozIccManager.addIcc(cardIndex, {});
|
||||
|
||||
setCardIndex = expectedCardIndex = cardIndex;
|
||||
initSubject();
|
||||
});
|
||||
|
||||
test('should not show SIM picker menu when long pressing', function() {
|
||||
phoneNumber = '15555555555';
|
||||
var showSpy = this.sinon.spy(simPicker, 'getOrPick');
|
||||
simulateContextMenu();
|
||||
sinon.assert.notCalled(showSpy);
|
||||
});
|
||||
|
||||
test('should use the only SIM', shouldUsePrimarySimCard);
|
||||
});
|
||||
});
|
||||
|
||||
suite('2 SIMs', function() {
|
||||
setup(function() {
|
||||
navigator.mozIccManager.addIcc(0, {});
|
||||
navigator.mozIccManager.addIcc(1, {});
|
||||
});
|
||||
|
||||
suite('settings loading race conditions', function() {
|
||||
var callbackStub;
|
||||
|
||||
setup(function() {
|
||||
phoneNumber = '1234';
|
||||
|
||||
callbackStub = this.sinon.stub();
|
||||
initSubject({
|
||||
callback: callbackStub,
|
||||
noSettingsTriggerCallback: true
|
||||
});
|
||||
|
||||
this.sinon.spy(simPicker, 'getOrPick');
|
||||
});
|
||||
|
||||
test('should queue one tap until settings are loaded', function() {
|
||||
simulateClick();
|
||||
sinon.assert.notCalled(callbackStub);
|
||||
|
||||
MockSettingsListener.mTriggerCallback(
|
||||
'ril.telephony.defaultServiceId', setCardIndex);
|
||||
|
||||
sinon.assert.calledOnce(callbackStub);
|
||||
});
|
||||
|
||||
test('should ignore long taps until settings are loaded', function() {
|
||||
simulateContextMenu();
|
||||
sinon.assert.notCalled(simPicker.getOrPick);
|
||||
});
|
||||
});
|
||||
|
||||
suite('SIM 2 preferred', function() {
|
||||
setup(function() {
|
||||
setCardIndex = expectedCardIndex = 1;
|
||||
initSubject();
|
||||
});
|
||||
|
||||
test('should show SIM picker menu when long pressing', function() {
|
||||
phoneNumber = '15555555555';
|
||||
var showSpy = this.sinon.spy(simPicker, 'getOrPick');
|
||||
simulateContextMenu();
|
||||
sinon.assert.calledWith(showSpy, expectedCardIndex, phoneNumber);
|
||||
});
|
||||
|
||||
test('should fire SIM selected callback', function() {
|
||||
var showSpy = this.sinon.spy(simPicker, 'getOrPick');
|
||||
|
||||
phoneNumber = '15555555555';
|
||||
simulateContextMenu();
|
||||
subject.performAction(setCardIndex);
|
||||
sinon.assert.calledWith(showSpy, expectedCardIndex, phoneNumber);
|
||||
});
|
||||
|
||||
test('should check the connection on the primary SIM card',
|
||||
shouldUsePrimarySimCard);
|
||||
});
|
||||
|
||||
suite('always ask', function() {
|
||||
setup(function() {
|
||||
setCardIndex = expectedCardIndex = ALWAYS_ASK_OPTION_VALUE;
|
||||
initSubject();
|
||||
});
|
||||
|
||||
test('should show SIM picker when clicked', function() {
|
||||
phoneNumber = '15555555555';
|
||||
var showSpy = this.sinon.spy(simPicker, 'getOrPick');
|
||||
simulateClick();
|
||||
sinon.assert.calledWith(showSpy, expectedCardIndex, phoneNumber);
|
||||
});
|
||||
});
|
||||
|
||||
suite('with a call in progress', function() {
|
||||
suiteSetup(function() {
|
||||
window.TelephonyHelper = MockTelephonyHelper;
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
window.TelephonyHelper = null;
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
// Use different cardIndex and mInUseSim.
|
||||
setCardIndex = 0;
|
||||
expectedCardIndex = MockTelephonyHelper.mInUseSim = 1;
|
||||
initSubject();
|
||||
});
|
||||
|
||||
test('should not open SIM picker on (long) tap', function() {
|
||||
var showSpy = this.sinon.spy(simPicker, 'getOrPick');
|
||||
simulateClick();
|
||||
simulateContextMenu();
|
||||
sinon.assert.notCalled(showSpy);
|
||||
});
|
||||
|
||||
test('should return current serviceId of call', function() {
|
||||
var callbackStub = this.sinon.stub();
|
||||
initSubject({ callback: callbackStub });
|
||||
|
||||
phoneNumber = '0123456789';
|
||||
simulateClick();
|
||||
|
||||
sinon.assert.calledWith(callbackStub, phoneNumber,
|
||||
MockTelephonyHelper.mInUseSim);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('UI tests', function(){
|
||||
var setAttributesSpy;
|
||||
var simIndication;
|
||||
|
||||
var initWithIndicationElement = function() {
|
||||
document.body.className = '';
|
||||
document.body.innerHTML =
|
||||
'<div id="container">' +
|
||||
'<gaia-sim-picker id="sim-picker"></gaia-sim-picker>' +
|
||||
'<div class="js-sim-indication"></div>' +
|
||||
'</div>';
|
||||
button = document.getElementById('container');
|
||||
simIndication = button.querySelector('.js-sim-indication');
|
||||
customElementsHelperForMultiSimActionButton.resolve();
|
||||
simPicker = document.getElementById('sim-picker');
|
||||
initSubject({ button: button });
|
||||
};
|
||||
|
||||
var shouldNotShowAnIndicator = function() {
|
||||
var setAttributesSpy = this.sinon.spy(MockL10n, 'setAttributes');
|
||||
initSubject();
|
||||
sinon.assert.notCalled(setAttributesSpy);
|
||||
};
|
||||
|
||||
setup(function() {
|
||||
setCardIndex = expectedCardIndex = 0;
|
||||
|
||||
navigator.mozIccManager.addIcc(0, {});
|
||||
navigator.mozIccManager.addIcc(1, {});
|
||||
});
|
||||
|
||||
suite('with SIM indication', function() {
|
||||
setup(function() {
|
||||
setAttributesSpy = this.sinon.spy(MockL10n, 'setAttributes');
|
||||
initWithIndicationElement();
|
||||
});
|
||||
|
||||
test('body should have has-preferred-sim class', function() {
|
||||
assert.isTrue(document.body.classList.contains('has-preferred-sim'));
|
||||
});
|
||||
|
||||
test('has a default localized SIM indicator', function() {
|
||||
sinon.assert.calledWith(setAttributesSpy,
|
||||
simIndication,
|
||||
'gaia-sim-picker-button',
|
||||
{n: expectedCardIndex+1});
|
||||
});
|
||||
|
||||
test('has a custom localized SIM indicator', function() {
|
||||
simIndication.dataset.l10nId = 'expected';
|
||||
// Re-create the button taking into account the new l10n-id.
|
||||
initSubject({ button: button });
|
||||
sinon.assert.calledWith(setAttributesSpy, simIndication, 'expected',
|
||||
{n: expectedCardIndex+1});
|
||||
});
|
||||
|
||||
test('indicator changes when settings change', function() {
|
||||
MockSettingsListener.mTriggerCallback(
|
||||
'ril.telephony.defaultServiceId', 1);
|
||||
|
||||
sinon.assert.calledWith(setAttributesSpy,
|
||||
simIndication,
|
||||
'gaia-sim-picker-button',
|
||||
{n: 2});
|
||||
});
|
||||
|
||||
test('should hide indicators when changing to always ask', function() {
|
||||
MockSettingsListener.mTriggerCallback(
|
||||
'ril.telephony.defaultServiceId', ALWAYS_ASK_OPTION_VALUE);
|
||||
|
||||
assert.isFalse(document.body.classList.contains('has-preferred-sim'));
|
||||
});
|
||||
|
||||
suite('with a call in progress', function() {
|
||||
suiteSetup(function() {
|
||||
window.TelephonyHelper = MockTelephonyHelper;
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
window.TelephonyHelper = null;
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
setCardIndex = 0;
|
||||
initSubject();
|
||||
});
|
||||
|
||||
test('should show SIM indicator with in-use serviceId', function() {
|
||||
MockTelephonyHelper.mInUseSim = 1;
|
||||
MockNavigatorMozTelephony.mTriggerEvent({type: 'callschanged'});
|
||||
|
||||
sinon.assert.calledWith(setAttributesSpy, simIndication,
|
||||
'gaia-sim-picker-button', {n: 2});
|
||||
});
|
||||
|
||||
test('SIM indicator should go back to default serviceId when call over',
|
||||
function() {
|
||||
MockTelephonyHelper.mInUseSim = 1;
|
||||
MockNavigatorMozTelephony.mTriggerEvent({type: 'callschanged'});
|
||||
setAttributesSpy.reset();
|
||||
MockTelephonyHelper.mTeardown();
|
||||
MockNavigatorMozTelephony.mTriggerEvent({type: 'callschanged'});
|
||||
|
||||
sinon.assert.calledWith(setAttributesSpy, simIndication,
|
||||
'gaia-sim-picker-button', {n: 1});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('without SIM indication', function() {
|
||||
setup(function() {
|
||||
document.body.className = '';
|
||||
document.body.innerHTML =
|
||||
'<div id="container">' +
|
||||
'<gaia-sim-picker id="sim-picker"></gaia-sim-picker>' +
|
||||
'</div>';
|
||||
button = document.getElementById('container');
|
||||
simIndication = button.querySelector('.js-sim-indication');
|
||||
customElementsHelperForMultiSimActionButton.resolve();
|
||||
simPicker = document.getElementById('sim-picker');
|
||||
|
||||
initSubject();
|
||||
});
|
||||
|
||||
test('body should have has-preferred-sim class', function() {
|
||||
assert.isTrue(document.body.classList.contains('has-preferred-sim'));
|
||||
});
|
||||
|
||||
test('should not show a current SIM indicator', shouldNotShowAnIndicator);
|
||||
});
|
||||
|
||||
suite('<= 1 SIMs', function() {
|
||||
setup(function() {
|
||||
document.body.className = '';
|
||||
|
||||
navigator.mozIccManager.mTeardown();
|
||||
navigator.mozIccManager.addIcc(0, {});
|
||||
|
||||
initWithIndicationElement();
|
||||
});
|
||||
|
||||
test('body should not have has-preferred-sim class', function() {
|
||||
assert.isFalse(document.body.classList.contains('has-preferred-sim'));
|
||||
});
|
||||
|
||||
test('should not show a current SIM indicator', shouldNotShowAnIndicator);
|
||||
});
|
||||
});
|
||||
});
|
||||
231
gaia/apps/sharedtest/test/unit/option_menu_test.js
Executable file
231
gaia/apps/sharedtest/test/unit/option_menu_test.js
Executable file
|
|
@ -0,0 +1,231 @@
|
|||
/*global OptionMenu, MockL10n, TransitionEvent */
|
||||
|
||||
'use strict';
|
||||
|
||||
require('/shared/test/unit/mocks/mock_l10n.js');
|
||||
|
||||
require('/shared/js/option_menu.js');
|
||||
|
||||
suite('OptionMenu', function() {
|
||||
var options, menu, formHeader, formSection, realL10n;
|
||||
|
||||
suiteSetup(function() {
|
||||
options = {
|
||||
id: 'menu-fixture',
|
||||
header: 'Text Header',
|
||||
section: 'Text Section',
|
||||
classes: ['class1', 'class2'],
|
||||
items: [
|
||||
{
|
||||
name: 'test',
|
||||
method: function(param) {
|
||||
|
||||
},
|
||||
params: ['foo']
|
||||
},
|
||||
{
|
||||
l10nId: 'cancel',
|
||||
l10nArgs: { test: true }
|
||||
}
|
||||
]
|
||||
};
|
||||
realL10n = navigator.mozL10n;
|
||||
navigator.mozL10n = MockL10n;
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
navigator.mozL10n = realL10n;
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
this.sinon.spy(navigator.mozL10n, 'setAttributes');
|
||||
|
||||
menu = new OptionMenu(options);
|
||||
|
||||
formHeader = menu.form.querySelector('header');
|
||||
formSection = menu.form.querySelector('section');
|
||||
});
|
||||
teardown(function() {
|
||||
var fixture = document.getElementById('menu-fixture');
|
||||
|
||||
if (fixture) {
|
||||
document.body.removeChild(fixture);
|
||||
}
|
||||
});
|
||||
|
||||
suite('Instance', function() {
|
||||
|
||||
test('OptionMenu', function() {
|
||||
assert.ok(OptionMenu);
|
||||
assert.ok(OptionMenu.prototype.show);
|
||||
assert.ok(OptionMenu.prototype.hide);
|
||||
});
|
||||
|
||||
suite('menu.show()', function() {
|
||||
setup(function() {
|
||||
this.sinon.stub(menu.form, 'focus');
|
||||
this.sinon.stub(HTMLElement.prototype, 'blur');
|
||||
menu.show();
|
||||
});
|
||||
|
||||
test('appends element to body', function() {
|
||||
assert.equal(
|
||||
menu.form, document.body.lastElementChild
|
||||
);
|
||||
});
|
||||
|
||||
test('Redundant shows have no effect', function() {
|
||||
sinon.spy(document.body, 'appendChild');
|
||||
menu.show();
|
||||
sinon.assert.notCalled(document.body.appendChild);
|
||||
});
|
||||
|
||||
test('Prevent pointer events and focus', function() {
|
||||
menu.show();
|
||||
sinon.assert.called(HTMLElement.prototype.blur);
|
||||
assert.isTrue(document.body.classList.contains('dialog-animating'));
|
||||
var transitionend = new TransitionEvent('transitionend', {
|
||||
bubbles: true,
|
||||
propertyName: 'transform'
|
||||
});
|
||||
menu.form.dispatchEvent(transitionend);
|
||||
assert.isFalse(document.body.classList.contains('dialog-animating'));
|
||||
sinon.assert.called(menu.form.focus);
|
||||
});
|
||||
|
||||
suite('menu.hide()', function() {
|
||||
setup(function() {
|
||||
menu.hide();
|
||||
});
|
||||
|
||||
test('hiding is delayed by animation', function() {
|
||||
assert.notEqual(menu.form.parentElement, null);
|
||||
});
|
||||
|
||||
test('removes element from DOM after transitionend', function() {
|
||||
var transitionend =
|
||||
new CustomEvent('transitionend', { target: menu.form });
|
||||
menu.form.dispatchEvent(transitionend);
|
||||
assert.equal(menu.form.parentElement, null);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('Display', function() {
|
||||
test('header: text', function() {
|
||||
assert.equal(
|
||||
formHeader.textContent, 'Text Header'
|
||||
);
|
||||
});
|
||||
|
||||
test('header: element', function() {
|
||||
options.header = document.createElement('header');
|
||||
options.header.textContent = 'Element Header';
|
||||
|
||||
menu = new OptionMenu(options);
|
||||
formHeader = menu.form.firstElementChild;
|
||||
|
||||
assert.equal(
|
||||
formHeader.textContent, 'Element Header'
|
||||
);
|
||||
});
|
||||
|
||||
test('header: none', function() {
|
||||
options.header = null;
|
||||
menu = new OptionMenu(options);
|
||||
|
||||
assert.isNull(menu.form.querySelector('header'));
|
||||
});
|
||||
|
||||
|
||||
test('section: text', function() {
|
||||
assert.equal(
|
||||
formSection.textContent, 'Text Section'
|
||||
);
|
||||
});
|
||||
|
||||
test('section: element', function() {
|
||||
options.section = document.createElement('section');
|
||||
options.section.textContent = 'Element Section';
|
||||
|
||||
menu = new OptionMenu(options);
|
||||
formSection = menu.form.firstElementChild;
|
||||
|
||||
assert.equal(
|
||||
formSection.textContent, 'Element Section'
|
||||
);
|
||||
});
|
||||
|
||||
test('section: none', function() {
|
||||
options.section = null;
|
||||
menu = new OptionMenu(options);
|
||||
|
||||
assert.isNull(menu.form.querySelector('section'));
|
||||
});
|
||||
|
||||
test('style doesn\'t affect confirm dialogs', function(next) {
|
||||
var style = document.createElement('style');
|
||||
style.textContent = '@import "../../../../shared/style/option_menu.css"';
|
||||
var loaded = setInterval(function() {
|
||||
try {
|
||||
// We're waiting for an @import stylesheet to load, so wait for the
|
||||
// inner set of cssRules.
|
||||
style.sheet.cssRules[0].styleSheet.cssRules;
|
||||
clearInterval(loaded);
|
||||
} catch (e) {
|
||||
// Waiting for stylesheet to load
|
||||
} finally {
|
||||
// Clear the interval again just in case we threw before calling
|
||||
// clearInterval() the first time. This is necessary so we don't call
|
||||
// the done() function multiple times.
|
||||
clearInterval(loaded);
|
||||
var form = document.createElement('form');
|
||||
form.setAttribute('role', 'dialog');
|
||||
form.setAttribute('data-type', 'confirm');
|
||||
document.body.appendChild(form);
|
||||
var compStyle = window.getComputedStyle(form);
|
||||
assert.notEqual(compStyle.visibility, 'hidden');
|
||||
next();
|
||||
}
|
||||
}, 10);
|
||||
document.body.appendChild(style);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
suite('Options', function() {
|
||||
var buttons;
|
||||
setup(function() {
|
||||
buttons = menu.form.querySelectorAll('button');
|
||||
});
|
||||
test('Buttons', function() {
|
||||
Array.forEach(buttons, (button) => {
|
||||
assert.equal(button.type, 'button');
|
||||
});
|
||||
assert.equal(buttons.length, 2);
|
||||
});
|
||||
test('Button Text', function() {
|
||||
assert.equal(buttons[0].textContent, options.items[0].name);
|
||||
});
|
||||
test('Localized button', function() {
|
||||
sinon.assert.calledWith(navigator.mozL10n.setAttributes, buttons[1],
|
||||
options.items[1].l10nId, options.items[1].l10nArgs);
|
||||
});
|
||||
test('classes', function() {
|
||||
assert.ok(menu.form.classList.contains('class1'));
|
||||
assert.ok(menu.form.classList.contains('class2'));
|
||||
});
|
||||
});
|
||||
|
||||
suite('Behaviours', function() {
|
||||
test('Fat fingering does not hide menu', function() {
|
||||
menu.show();
|
||||
menu.form.click();
|
||||
assert.equal(
|
||||
menu.form, document.body.lastElementChild
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
94
gaia/apps/sharedtest/test/unit/passcode_helper_test.js
Executable file
94
gaia/apps/sharedtest/test/unit/passcode_helper_test.js
Executable file
|
|
@ -0,0 +1,94 @@
|
|||
/* globals MockNavigatorSettings, PasscodeHelper */
|
||||
'use strict';
|
||||
|
||||
require('/shared/test/unit/mocks/mock_navigator_moz_settings.js');
|
||||
require('/shared/js/passcode_helper.js');
|
||||
|
||||
suite('PasscodeHelper:', function() {
|
||||
var realMozSettings;
|
||||
suiteSetup(function(done) {
|
||||
realMozSettings = navigator.mozSettings;
|
||||
navigator.mozSettings = MockNavigatorSettings;
|
||||
MockNavigatorSettings.mSetup();
|
||||
const SET_DIGEST_VALUE = 'lockscreen.passcode-lock.digest.value';
|
||||
const SET_DIGEST_SALT = 'lockscreen.passcode-lock.digest.salt';
|
||||
const SET_DIGEST_ITERATIONS = 'lockscreen.passcode-lock.digest.iterations';
|
||||
const SET_DIGEST_ALGORITHM = 'lockscreen.passcode-lock.digest.algorithm';
|
||||
|
||||
var phSettings = {};
|
||||
// digest for PIN '1337' with salt as below.
|
||||
var digestNums = [40, 160, 226, 54, 163, 95, 75, 200,
|
||||
40, 227, 57, 44, 141, 118, 230, 64,
|
||||
0, 101, 103, 249];
|
||||
var digest1337 = new Uint8Array(digestNums);
|
||||
phSettings[SET_DIGEST_VALUE] = digest1337;
|
||||
// constant salt for easier testing
|
||||
phSettings[SET_DIGEST_SALT] = new Uint8Array([108, 85, 195, 7,
|
||||
89, 243, 87, 87]);
|
||||
// picking 100 iterations, to test that a follow-up set() will
|
||||
// properly upgrade to a stronger default.
|
||||
phSettings[SET_DIGEST_ITERATIONS] = 100;
|
||||
phSettings[SET_DIGEST_ALGORITHM] = 'SHA-1';
|
||||
|
||||
var lock = navigator.mozSettings.createLock();
|
||||
var request = lock.set(phSettings);
|
||||
request.then(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
navigator.mozSettings = realMozSettings;
|
||||
MockNavigatorSettings.mTeardown();
|
||||
});
|
||||
|
||||
test('check Passcode with constant salt', function(done) {
|
||||
var promise = PasscodeHelper.check('1337');
|
||||
assert.equal(typeof promise.then, 'function',
|
||||
'Passcode.check returns a then-able');
|
||||
function onSuccess(bool) {
|
||||
assert.isBoolean(bool, 'Expecting a boolean');
|
||||
assert.equal(bool, true, 'Expecting a boolean true');
|
||||
//done();
|
||||
}
|
||||
function onError(err) {
|
||||
assert.fail(err, true, 'Passcode.check has thrown');
|
||||
//done();
|
||||
}
|
||||
promise.then(onSuccess).catch(onError).then(done).catch(done);
|
||||
});
|
||||
|
||||
test('Passcode.set() & Passcode.check() for new password', function(done) {
|
||||
var setPromise = PasscodeHelper.set('0000');
|
||||
assert.equal(typeof setPromise.then, 'function',
|
||||
'Passcode.set() returns a then-able');
|
||||
function onSet() {
|
||||
var checkPromise = PasscodeHelper.check('0000');
|
||||
assert.equal(typeof checkPromise.then, 'function',
|
||||
'Passcode.check() returns a then-able');
|
||||
function onCheck(bool) {
|
||||
assert.ok(bool, 'check() was OK');
|
||||
|
||||
// setting and checking worked. let's see if this
|
||||
// updated security to 1000 iterations.
|
||||
var lock = navigator.mozSettings.createLock();
|
||||
var req = lock.get('lockscreen.passcode-lock.digest.iterations');
|
||||
req.then((result) => {
|
||||
var iter = result['lockscreen.passcode-lock.digest.iterations'];
|
||||
assert.equal(iter, 1000, 'new passcode set()s update properly');
|
||||
done();
|
||||
});
|
||||
}
|
||||
function onCheckError(err) {
|
||||
assert.fail(err, true, 'Checking the passcode has thrown');
|
||||
done();
|
||||
}
|
||||
checkPromise.then(onCheck, onCheckError);
|
||||
}
|
||||
function onSetError(err) {
|
||||
assert.fail(err, '(no exception)', 'Setting the passcode has thrown');
|
||||
done();
|
||||
}
|
||||
setPromise.then(onSet, onSetError);
|
||||
});
|
||||
});
|
||||
36
gaia/apps/sharedtest/test/unit/sanitizer_test.js
Executable file
36
gaia/apps/sharedtest/test/unit/sanitizer_test.js
Executable file
|
|
@ -0,0 +1,36 @@
|
|||
/* global Sanitizer */
|
||||
|
||||
'use strict';
|
||||
|
||||
require('/shared/js/sanitizer.js');
|
||||
|
||||
suite('tagged template helper', function() {
|
||||
|
||||
test('#escapeHTML', function() {
|
||||
var myVal = '<b>hello</b>';
|
||||
var generated = Sanitizer.escapeHTML `${myVal} world`;
|
||||
assert.equal(generated, '<b>hello</b> world');
|
||||
});
|
||||
|
||||
test('#createSafeHTML', function() {
|
||||
var t = 'Click me';
|
||||
var url = '" ><script>alert(1)</script>';
|
||||
var s;
|
||||
s = Sanitizer.createSafeHTML`<a href="${url}" title="${t}">${t}</a>`;
|
||||
|
||||
var expectedDocs = 'https://developer.mozilla.org/en-US/Firefox_OS/'+
|
||||
'Security/Security_Automation';
|
||||
assert.equal(s.__html,
|
||||
'<a href="" ><script>alert(1)</script'+
|
||||
'>" title="Click me">Click me</a>');
|
||||
assert.ok(s.info.indexOf(expectedDocs) !== -1);
|
||||
});
|
||||
|
||||
test('#unwrapSafeHTML', function() {
|
||||
var escapeMe = '<s>hax</s>';
|
||||
var obj = Sanitizer.createSafeHTML`<b>${escapeMe}</b>`;
|
||||
var unwrapped = Sanitizer.unwrapSafeHTML(obj);
|
||||
assert.equal(unwrapped, '<b><s>hax</s></b>');
|
||||
});
|
||||
|
||||
});
|
||||
145
gaia/apps/sharedtest/test/unit/search_provider_test.js
Executable file
145
gaia/apps/sharedtest/test/unit/search_provider_test.js
Executable file
|
|
@ -0,0 +1,145 @@
|
|||
'use strict';
|
||||
|
||||
/* globals SearchProvider */
|
||||
|
||||
require('/shared/js/search_provider.js');
|
||||
|
||||
var config = {
|
||||
|
||||
'search_providers': {
|
||||
|
||||
'yahoo': {
|
||||
'title': 'Yahoo',
|
||||
'search': {
|
||||
'url': 'https://yahoo.com/search',
|
||||
'params': {'p': '{searchTerms}'}
|
||||
},
|
||||
'suggest': {
|
||||
'url': 'https://yahoo.com/sugg/ff',
|
||||
'params': {'command': '{searchTerms}'}
|
||||
}
|
||||
},
|
||||
|
||||
'ddg': {
|
||||
'title': 'DuckDuckGo',
|
||||
'search': {
|
||||
'url': 'https://ddg.com/',
|
||||
'params': {'t': 'ffos', 'q': '{searchTerms}'}
|
||||
},
|
||||
'suggest': {
|
||||
'url': 'https://ddg.com/ac/',
|
||||
'params': {'q': '{searchTerms}', 'type': 'list'}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'simConfigs': {
|
||||
'123-456': {
|
||||
'es-ES': {
|
||||
'defaultEngine': 'yahoo',
|
||||
'providers': {
|
||||
'yahoo': {
|
||||
'search': {'params': {'custom': 'code'}}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'partnerConfig': {
|
||||
'anOEM': {
|
||||
'en-US': {
|
||||
'defaultEngine': 'ddg',
|
||||
'providers': {
|
||||
'ddg': {
|
||||
'suggest': {'params': {'custom': 'partner'}}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'defaultEngines': {
|
||||
'defaultEngine': 'ddg',
|
||||
'providers': {
|
||||
'yahoo': {},
|
||||
'ddg': {}
|
||||
}
|
||||
},
|
||||
|
||||
'locales': {
|
||||
'en-FR': {
|
||||
'ddg': {
|
||||
'search': {'url': 'https://duckduckgo.fr/'}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
suite('system/SearchProvider', function() {
|
||||
|
||||
test('Basic Default', function() {
|
||||
assert.deepEqual(SearchProvider.pickEngines(config, [], null, null), {
|
||||
'defaultEngine': 'ddg',
|
||||
'providers': {
|
||||
'yahoo': {
|
||||
'title': 'Yahoo',
|
||||
'searchUrl': 'https://yahoo.com/search?p={searchTerms}',
|
||||
'suggestUrl': 'https://yahoo.com/sugg/ff?command={searchTerms}',
|
||||
},
|
||||
'ddg': {
|
||||
'title': 'DuckDuckGo',
|
||||
'searchUrl': 'https://ddg.com/?t=ffos&q={searchTerms}',
|
||||
'suggestUrl': 'https://ddg.com/ac/?q={searchTerms}&type=list'
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('Basic Locale', function() {
|
||||
assert.deepEqual(SearchProvider.pickEngines(config, [], null, 'en-FR'), {
|
||||
'defaultEngine': 'ddg',
|
||||
'providers': {
|
||||
'yahoo': {
|
||||
'title': 'Yahoo',
|
||||
'searchUrl': 'https://yahoo.com/search?p={searchTerms}',
|
||||
'suggestUrl': 'https://yahoo.com/sugg/ff?command={searchTerms}',
|
||||
},
|
||||
'ddg': {
|
||||
'title': 'DuckDuckGo',
|
||||
'searchUrl': 'https://duckduckgo.fr/?t=ffos&q={searchTerms}',
|
||||
'suggestUrl': 'https://ddg.com/ac/?q={searchTerms}&type=list'
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('Match Sim', function() {
|
||||
var sims = ['123-456'];
|
||||
assert.deepEqual(SearchProvider.pickEngines(config, sims, null, 'es-ES'), {
|
||||
'defaultEngine': 'yahoo',
|
||||
'providers': {
|
||||
'yahoo': {
|
||||
'title': 'Yahoo',
|
||||
'searchUrl': 'https://yahoo.com/search?p={searchTerms}&custom=code',
|
||||
'suggestUrl': 'https://yahoo.com/sugg/ff?command={searchTerms}',
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('Match Partner', function() {
|
||||
assert.deepEqual(SearchProvider.pickEngines(config, [], 'anOEM', 'en-US'), {
|
||||
'defaultEngine': 'ddg',
|
||||
'providers': {
|
||||
'ddg': {
|
||||
'title': 'DuckDuckGo',
|
||||
'searchUrl': 'https://ddg.com/?t=ffos&q={searchTerms}',
|
||||
'suggestUrl': 'https://ddg.com/ac/?q={searchTerms}&' +
|
||||
'type=list&custom=partner'
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
145
gaia/apps/sharedtest/test/unit/setImmediate_test.js
Executable file
145
gaia/apps/sharedtest/test/unit/setImmediate_test.js
Executable file
|
|
@ -0,0 +1,145 @@
|
|||
// Copyright (c) 2012 Barnesandnoble.com, llc, Donavon West, and Domenic Denicola
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// NOTE: This is an import of the NobleJS setImmediate() polyfill located here:
|
||||
//
|
||||
// https://github.com/NobleJS/setImmediate
|
||||
//
|
||||
// It has been customized in the following ways:
|
||||
//
|
||||
// 1) Non-gecko browser compatibility code has been removed. The
|
||||
// postMessage() implementation is always used, except for
|
||||
// web workers. There we use setTimeout(0) since MessageChannel
|
||||
// is not implemented in gecko yet. (Bug 911972)
|
||||
// 2) The support for executing strings with eval() has been
|
||||
// disabled and will now throw an exception.
|
||||
// 3) Convert test code to use suite() and test().
|
||||
//
|
||||
// The style of this code is different from the rest of gaia, but
|
||||
// we chose to minimize non-functional changes in order to make
|
||||
// importing fixes from upstream easier in the future.
|
||||
//
|
||||
// XXX: Remove this file if/when bug 686201 land.
|
||||
|
||||
"use strict";
|
||||
/*global setImmediate: false, clearImmediate: false, specify: false, window: false */
|
||||
|
||||
require("/shared/js/setImmediate.js");
|
||||
|
||||
suite("setImmediate polyfill", function() {
|
||||
test("Handlers do execute", function (done) {
|
||||
setImmediate(function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test("Handlers do not execute in the same event loop turn as the call to `setImmediate`", function (done) {
|
||||
var handlerCalled = false;
|
||||
function handler() {
|
||||
handlerCalled = true;
|
||||
done();
|
||||
}
|
||||
|
||||
setImmediate(handler);
|
||||
assert.ok(!handlerCalled);
|
||||
});
|
||||
|
||||
test("`setImmediate` passes through an argument to the handler", function (done) {
|
||||
var expectedArg = { expected: true };
|
||||
|
||||
function handler(actualArg) {
|
||||
assert.strictEqual(actualArg, expectedArg);
|
||||
done();
|
||||
}
|
||||
|
||||
setImmediate(handler, expectedArg);
|
||||
});
|
||||
|
||||
test("`setImmediate` passes through two arguments to the handler", function (done) {
|
||||
var expectedArg1 = { arg1: true };
|
||||
var expectedArg2 = { arg2: true };
|
||||
|
||||
function handler(actualArg1, actualArg2) {
|
||||
assert.strictEqual(actualArg1, expectedArg1);
|
||||
assert.strictEqual(actualArg2, expectedArg2);
|
||||
done();
|
||||
}
|
||||
|
||||
setImmediate(handler, expectedArg1, expectedArg2);
|
||||
});
|
||||
|
||||
test("`clearImmediate` within the same event loop turn prevents the handler from executing", function (done) {
|
||||
var handlerCalled = false;
|
||||
function handler() {
|
||||
handlerCalled = true;
|
||||
}
|
||||
|
||||
var handle = setImmediate(handler);
|
||||
clearImmediate(handle);
|
||||
|
||||
setTimeout(function () {
|
||||
assert.ok(!handlerCalled);
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
test("`clearImmediate` does not interfere with handlers other than the one with ID passed to it", function (done) {
|
||||
var expectedArgs = ["A", "D"];
|
||||
var recordedArgs = [];
|
||||
function handler(arg) {
|
||||
recordedArgs.push(arg);
|
||||
}
|
||||
|
||||
setImmediate(handler, "A");
|
||||
clearImmediate(setImmediate(handler, "B"));
|
||||
var handle = setImmediate(handler, "C");
|
||||
setImmediate(handler, "D");
|
||||
clearImmediate(handle);
|
||||
|
||||
setTimeout(function () {
|
||||
assert.deepEqual(recordedArgs, expectedArgs);
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
// NOTE: Test fxos customization to prevent eval usage
|
||||
test("`setImmediate` does not permit eval", function(done) {
|
||||
function complete() {
|
||||
assert.ok(false, "eval executed");
|
||||
done();
|
||||
}
|
||||
|
||||
try {
|
||||
setImmediate("complete()");
|
||||
} catch (e) {
|
||||
assert.ok(!!e, "received exception when attempting eval");
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
test("When inside a web worker context, setImmediate calls the passed handler", function (done) {
|
||||
var worker = new window.Worker("setImmediate_worker.js");
|
||||
worker.addEventListener("message", function (event) {
|
||||
assert.strictEqual(event.data, "TEST");
|
||||
done();
|
||||
}, false);
|
||||
});
|
||||
});
|
||||
8
gaia/apps/sharedtest/test/unit/setImmediate_worker.js
Executable file
8
gaia/apps/sharedtest/test/unit/setImmediate_worker.js
Executable file
|
|
@ -0,0 +1,8 @@
|
|||
/* global setImmediate */
|
||||
|
||||
importScripts('/shared/js/setImmediate.js');
|
||||
|
||||
setImmediate(function() {
|
||||
'use strict';
|
||||
self.postMessage('TEST');
|
||||
});
|
||||
40
gaia/apps/sharedtest/test/unit/settings_url_test.js
Executable file
40
gaia/apps/sharedtest/test/unit/settings_url_test.js
Executable file
|
|
@ -0,0 +1,40 @@
|
|||
/* global SettingsURL */
|
||||
|
||||
require('/shared/js/settings_url.js');
|
||||
|
||||
suite('SettingsURL', function() {
|
||||
'use strict';
|
||||
var subject;
|
||||
|
||||
suiteSetup(function() {
|
||||
subject = new SettingsURL();
|
||||
});
|
||||
|
||||
test('constructor', function() {
|
||||
assert.ok(!subject._url, 'url is empty on init');
|
||||
assert.ok(!subject._isBlob, 'isBlob is empty on init');
|
||||
});
|
||||
|
||||
test('not a blob', function() {
|
||||
var notBlob = 'http://gaiamobile.org';
|
||||
subject.set(notBlob);
|
||||
assert.ok(!subject._isBlob, 'object is not a blob');
|
||||
assert.equal(subject.get(), notBlob);
|
||||
});
|
||||
|
||||
test('is a blob', function(done) {
|
||||
|
||||
var domBlob = new Blob(['<span>blob</span>'], { type: 'text/xml' });
|
||||
|
||||
var realCreateObjectURL = window.URL.createObjectURL;
|
||||
window.URL.createObjectURL = function() {
|
||||
window.URL.createObjectURL = realCreateObjectURL;
|
||||
|
||||
assert.ok(subject._isBlob, 'object is a blob');
|
||||
assert.notEqual(subject.get(), domBlob);
|
||||
done();
|
||||
};
|
||||
|
||||
subject.set(domBlob);
|
||||
});
|
||||
});
|
||||
25
gaia/apps/sharedtest/test/unit/setup.js
Executable file
25
gaia/apps/sharedtest/test/unit/setup.js
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
(function(window) {
|
||||
'use strict';
|
||||
|
||||
/* require most of the coupled / util objects */
|
||||
|
||||
function l10nLink(href) {
|
||||
var resource = document.createElement('link');
|
||||
resource.setAttribute('href', href);
|
||||
resource.setAttribute('rel', 'localization');
|
||||
document.head.appendChild(resource);
|
||||
}
|
||||
|
||||
l10nLink('/shared/locales/date/date.{locale}.properties');
|
||||
|
||||
// setup localization....
|
||||
require('/shared/js/l10n.js', function() {
|
||||
suiteSetup(function(done) {
|
||||
navigator.mozL10n.ready(done);
|
||||
});
|
||||
});
|
||||
|
||||
require('/shared/test/unit/mocks/mocks_helper.js');
|
||||
require('/shared/test/unit/mocks/elements/custom_elements_helper.js');
|
||||
|
||||
}(this));
|
||||
243
gaia/apps/sharedtest/test/unit/simple_phone_matcher_test.js
Executable file
243
gaia/apps/sharedtest/test/unit/simple_phone_matcher_test.js
Executable file
|
|
@ -0,0 +1,243 @@
|
|||
/* global SimplePhoneMatcher */
|
||||
|
||||
requireApp('sharedtest/shared/js/simple_phone_matcher.js');
|
||||
|
||||
suite('lib/simple_phone_matcher', function() {
|
||||
'use strict';
|
||||
suite('sanitization', function() {
|
||||
test('should remove dots', function() {
|
||||
var number = '06.98.05.70.56';
|
||||
assert.equal('0698057056', SimplePhoneMatcher.sanitizedNumber(number));
|
||||
});
|
||||
|
||||
test('should remove spaces', function() {
|
||||
var number = '+34 764 456 098';
|
||||
assert.equal('+34764456098', SimplePhoneMatcher.sanitizedNumber(number));
|
||||
});
|
||||
|
||||
test('should remove returns', function() {
|
||||
var number = '+34 764 456 098\n';
|
||||
assert.equal('+34764456098', SimplePhoneMatcher.sanitizedNumber(number));
|
||||
});
|
||||
|
||||
test('should remove dashes', function() {
|
||||
var number = '97-111-8876';
|
||||
assert.equal('971118876', SimplePhoneMatcher.sanitizedNumber(number));
|
||||
});
|
||||
|
||||
test('should remove parantheses', function() {
|
||||
var number = '(650) 123 4581';
|
||||
assert.equal('6501234581', SimplePhoneMatcher.sanitizedNumber(number));
|
||||
});
|
||||
|
||||
test('should keep plus', function() {
|
||||
var number = '+55971118876';
|
||||
assert.equal(number, SimplePhoneMatcher.sanitizedNumber(number));
|
||||
});
|
||||
});
|
||||
|
||||
suite('variants generation', function() {
|
||||
function testVariants(number, variant) {
|
||||
var variants = SimplePhoneMatcher.generateVariants(number);
|
||||
assert.include(variants, variant);
|
||||
}
|
||||
|
||||
function testVariantsBothWays(first, second) {
|
||||
var variants = SimplePhoneMatcher.generateVariants(first);
|
||||
assert.include(variants, second);
|
||||
|
||||
variants = SimplePhoneMatcher.generateVariants(second);
|
||||
assert.include(variants, first);
|
||||
}
|
||||
|
||||
test('should sort variants by length', function() {
|
||||
var number = '+55 97 111 8876';
|
||||
var variants = ['1118876', '971118876', '0971118876',
|
||||
'+55971118876', '0055971118876'];
|
||||
|
||||
var generatedVariants = SimplePhoneMatcher.generateVariants(number);
|
||||
assert.equal(variants.join(), generatedVariants.join());
|
||||
});
|
||||
|
||||
suite('international prefix variants', function() {
|
||||
suite('from countries with 00 as international prefix', function() {
|
||||
test('Brazil', function() {
|
||||
testVariantsBothWays('+55971118876', '0055971118876');
|
||||
});
|
||||
|
||||
test('Spain', function() {
|
||||
testVariantsBothWays('+34764456098', '0034764456098');
|
||||
});
|
||||
|
||||
test('France', function() {
|
||||
testVariantsBothWays('+33698057036', '0033698057036');
|
||||
});
|
||||
|
||||
test('UK', function() {
|
||||
testVariantsBothWays('+4402088900346', '004402088900346');
|
||||
});
|
||||
|
||||
test('USA', function() {
|
||||
testVariantsBothWays('+16501234581', '0016501234581');
|
||||
});
|
||||
});
|
||||
|
||||
suite('from countries with 011 as international prefix', function() {
|
||||
var defaultMcc;
|
||||
|
||||
suiteSetup(function() {
|
||||
defaultMcc = SimplePhoneMatcher.mcc;
|
||||
SimplePhoneMatcher.mcc = '310';
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
SimplePhoneMatcher.mcc = defaultMcc;
|
||||
});
|
||||
|
||||
test('France', function() {
|
||||
testVariantsBothWays('+33698057036', '01133698057036');
|
||||
});
|
||||
|
||||
test('UK', function() {
|
||||
testVariantsBothWays('+4402088900346', '0114402088900346');
|
||||
});
|
||||
|
||||
test('USA', function() {
|
||||
testVariantsBothWays('+16501234581', '01116501234581');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('carrier prefix variants in Brazil', function() {
|
||||
var defaultMcc;
|
||||
|
||||
suiteSetup(function() {
|
||||
defaultMcc = SimplePhoneMatcher.mcc;
|
||||
SimplePhoneMatcher.mcc = '724';
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
SimplePhoneMatcher.mcc = defaultMcc;
|
||||
});
|
||||
|
||||
test('number dialed with carrier prefix', function() {
|
||||
testVariants('0311112345678', '1112345678');
|
||||
});
|
||||
|
||||
test('long number dialed with carrier prefix', function() {
|
||||
testVariants('03111123456789', '11123456789');
|
||||
});
|
||||
|
||||
test('number dialed with no carrier prefix', function() {
|
||||
testVariants('01112345678', '1112345678');
|
||||
});
|
||||
|
||||
test('long number dialed with no carrier prefix', function() {
|
||||
testVariants('011123456789', '11123456789');
|
||||
});
|
||||
});
|
||||
|
||||
suite('trunk prefix variants', function() {
|
||||
suite('countries with 0 as trunk prefix', function() {
|
||||
test('Brazil', function() {
|
||||
testVariants('+55971118876', '0971118876');
|
||||
testVariants('+55971118876', '971118876');
|
||||
});
|
||||
|
||||
test('France', function() {
|
||||
testVariants('0033698057036', '0698057036');
|
||||
testVariants('0033698057036', '698057036');
|
||||
});
|
||||
});
|
||||
|
||||
suite('countries with no trunk prefix', function() {
|
||||
test('Spain', function() {
|
||||
testVariants('0034764456098', '764456098');
|
||||
});
|
||||
|
||||
test('UK', function() {
|
||||
testVariants('+4402088900346', '02088900346');
|
||||
});
|
||||
|
||||
test('USA', function() {
|
||||
testVariants('+16501234581', '6501234581');
|
||||
});
|
||||
});
|
||||
|
||||
suite('numbers with a trunk prefix already added', function() {
|
||||
test('Brazil', function() {
|
||||
testVariants('0971118876', '971118876');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('area code variants', function() {
|
||||
suite('countries with 2 digits area codes', function() {
|
||||
test('Brazil', function() {
|
||||
testVariants('+55971118876', '1118876');
|
||||
});
|
||||
});
|
||||
|
||||
suite('countries with 3 digits area codes', function() {
|
||||
test('UK', function() {
|
||||
testVariants('+4402088900346', '88900346');
|
||||
});
|
||||
|
||||
test('USA', function() {
|
||||
testVariants('+16501234581', '1234581');
|
||||
});
|
||||
});
|
||||
|
||||
test('countries where we do not swipe area code', function() {
|
||||
var variants = SimplePhoneMatcher.generateVariants('+33698057055');
|
||||
assert.equal(-1, variants.indexOf('8057055'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('best match search', function() {
|
||||
var variants;
|
||||
|
||||
function testBestMatch(totalMatchNum, allMatches,
|
||||
bestMatchIndex, localIndex,
|
||||
variants, matches) {
|
||||
var result = {
|
||||
totalMatchNum: totalMatchNum,
|
||||
allMatches: allMatches,
|
||||
bestMatchIndex: bestMatchIndex,
|
||||
localIndex: localIndex
|
||||
};
|
||||
assert.deepEqual(result, SimplePhoneMatcher.bestMatch(variants, matches));
|
||||
}
|
||||
|
||||
setup(function() {
|
||||
variants = ['1118876', '0971118876', '+55971118876', '0055971118876'];
|
||||
});
|
||||
|
||||
test('should return the index with the longest match', function() {
|
||||
var matches = [['1118876', '12333'], ['111', '8876'], ['0055971118876']];
|
||||
testBestMatch(4, [[0], [0, 1], [0]], 2, 0, variants, matches);
|
||||
});
|
||||
|
||||
test('should sanitize matches', function() {
|
||||
var matches = [['1118876', '12333'], ['+55 (97) 111 8876']];
|
||||
testBestMatch(2 , [[0], [0]], 1, 0, variants, matches);
|
||||
});
|
||||
|
||||
test('should be compatible with contains matches', function() {
|
||||
var matches = [['112233', '118876'], ['000']];
|
||||
testBestMatch(2 , [[1], [0]], 0, 1, variants, matches);
|
||||
|
||||
variants = ['1118876', '0971118876'];
|
||||
matches = [['0055971118876', '12333'], ['000'], ['12345']];
|
||||
testBestMatch(3, [[0], [0], [0]], 0, 0, variants, matches);
|
||||
});
|
||||
|
||||
test('should return the first element as best match in the worst case',
|
||||
function() {
|
||||
var variants = [];
|
||||
var matches = [['112233', '118876'], ['000']];
|
||||
testBestMatch(2, [[0], [0]], 0, 0, variants, matches);
|
||||
});
|
||||
});
|
||||
});
|
||||
147
gaia/apps/sharedtest/test/unit/simslot_manager_test.js
Executable file
147
gaia/apps/sharedtest/test/unit/simslot_manager_test.js
Executable file
|
|
@ -0,0 +1,147 @@
|
|||
/* globals MockIccManager, MockNavigatorMozMobileConnections, MocksHelper,
|
||||
MockSIMSlot, SIMSlotManager */
|
||||
'use strict';
|
||||
|
||||
require('/shared/test/unit/mocks/mock_simslot.js');
|
||||
require('/shared/test/unit/mocks/mock_icc_manager.js');
|
||||
require('/shared/test/unit/mocks/mock_navigator_moz_mobile_connections.js');
|
||||
|
||||
var mocksForSIMSlotManager = new MocksHelper([
|
||||
'SIMSlot', 'NavigatorMozMobileConnections'
|
||||
]).init();
|
||||
|
||||
suite('SIMSlotManager', function() {
|
||||
var realIccManager = navigator.mozIccManager;
|
||||
var realMobileConnections = navigator.mozMobileConnections;
|
||||
mocksForSIMSlotManager.attachTestHelpers();
|
||||
|
||||
setup(function(callback) {
|
||||
navigator.mozIccManager = MockIccManager;
|
||||
navigator.mozMobileConnections = MockNavigatorMozMobileConnections;
|
||||
require('/shared/js/simslot_manager.js', callback);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
MockNavigatorMozMobileConnections.mTeardown();
|
||||
navigator.mozIccManager = realIccManager;
|
||||
navigator.mozMobileConnections = realMobileConnections;
|
||||
});
|
||||
|
||||
test('Multi-SIM with 1st slot ready and 2nd slot absent.', function() {
|
||||
// Add 2nd MobileConnection for Multi-SIM scenario.
|
||||
MockNavigatorMozMobileConnections.mAddMobileConnection();
|
||||
var stubAddEventListener = this.sinon.stub(
|
||||
MockIccManager, 'addEventListener');
|
||||
|
||||
// Have 1st slot ready to test.
|
||||
MockNavigatorMozMobileConnections[0].iccId = 0;
|
||||
MockIccManager.iccIds = [0];
|
||||
var getIccById = MockIccManager.getIccById;
|
||||
MockIccManager.getIccById = function(iccId) {
|
||||
return (iccId !== 0) ? null : {
|
||||
cardState: 'ready',
|
||||
iccInfo: { iccid: 0 }
|
||||
};
|
||||
};
|
||||
|
||||
// Reset SIMSlotManager before testing.
|
||||
SIMSlotManager.length = 0;
|
||||
SIMSlotManager._instances = [];
|
||||
SIMSlotManager.ready = false;
|
||||
|
||||
// Start the test.
|
||||
SIMSlotManager.init();
|
||||
|
||||
// Verdict.
|
||||
assert.isTrue(SIMSlotManager.isMultiSIM());
|
||||
assert.equal(SIMSlotManager.length,
|
||||
MockNavigatorMozMobileConnections.length);
|
||||
assert.isFalse(SIMSlotManager.ready);
|
||||
assert.isTrue(
|
||||
stubAddEventListener.calledWith('iccdetected', SIMSlotManager));
|
||||
|
||||
// Reset MockIccManager.getIccById.
|
||||
MockIccManager.getIccById = getIccById;
|
||||
});
|
||||
|
||||
test('Multi-SIM with 2 slots ready.', function() {
|
||||
// Add 2nd MobileConnection for Multi-SIM scenario.
|
||||
MockNavigatorMozMobileConnections.mAddMobileConnection();
|
||||
var stubAddEventListener = this.sinon.stub(
|
||||
MockIccManager, 'addEventListener');
|
||||
|
||||
// Reset SIMSlotManager before testing.
|
||||
SIMSlotManager.length = 0;
|
||||
SIMSlotManager._instances = [];
|
||||
SIMSlotManager.ready = false;
|
||||
|
||||
// Start the test.
|
||||
SIMSlotManager.init();
|
||||
|
||||
// Verdict.
|
||||
assert.isTrue(SIMSlotManager.isMultiSIM());
|
||||
assert.equal(SIMSlotManager.length,
|
||||
MockNavigatorMozMobileConnections.length);
|
||||
assert.isTrue(SIMSlotManager.ready);
|
||||
assert.isTrue(
|
||||
stubAddEventListener.calledWith('iccdetected', SIMSlotManager));
|
||||
});
|
||||
|
||||
test('get current SIM count', function() {
|
||||
MockIccManager.iccIds = [0, 1, 2];
|
||||
assert.isFalse(SIMSlotManager.noSIMCardOnDevice());
|
||||
MockIccManager.iccIds = [];
|
||||
assert.isTrue(SIMSlotManager.noSIMCardOnDevice());
|
||||
});
|
||||
|
||||
test('noSIMCardConnectedToNetwork', function() {
|
||||
MockIccManager.iccIds = [0, 1];
|
||||
MockNavigatorMozMobileConnections.mAddMobileConnection();
|
||||
|
||||
SIMSlotManager._instances = [];
|
||||
SIMSlotManager.init();
|
||||
MockNavigatorMozMobileConnections[0].voice = { emergencyCallsOnly: true };
|
||||
MockNavigatorMozMobileConnections[1].voice = { emergencyCallsOnly: true };
|
||||
assert.isTrue(SIMSlotManager.noSIMCardConnectedToNetwork());
|
||||
MockNavigatorMozMobileConnections[0].voice.emergencyCallsOnly = false;
|
||||
assert.isFalse(SIMSlotManager.noSIMCardConnectedToNetwork());
|
||||
});
|
||||
|
||||
test('getSlotByIccId', function() {
|
||||
var card1 = document.createElement('div');
|
||||
card1.iccId = 1;
|
||||
var card2 = document.createElement('div');
|
||||
card2.iccId = 2;
|
||||
var slot1 = new MockSIMSlot({ iccId: 1 }, 0, card1);
|
||||
var slot2 = new MockSIMSlot({ iccId: 2 }, 1, card2);
|
||||
var slot3 = new MockSIMSlot({ iccId: null}, 2);
|
||||
|
||||
SIMSlotManager._instances = [slot1, slot2, slot3];
|
||||
assert.deepEqual(SIMSlotManager.getSlotByIccId(1), slot1);
|
||||
|
||||
assert.deepEqual(SIMSlotManager.getSlotByIccId(2), slot2);
|
||||
|
||||
assert.deepEqual(SIMSlotManager.getSlotByIccId(9999), null);
|
||||
});
|
||||
|
||||
test('iccdetected', function() {
|
||||
var stubGetIccById = this.sinon.stub(SIMSlotManager, 'getSlotByIccId');
|
||||
var card1 = document.createElement('div');
|
||||
card1.iccId = 1;
|
||||
var slot = new MockSIMSlot(null, 1, card1);
|
||||
var stubUpdate = this.sinon.stub(slot, 'update');
|
||||
var card2 = document.createElement('div');
|
||||
card2.iccId = 1;
|
||||
var stubGetIccByIdOfIccManager =
|
||||
this.sinon.stub(MockIccManager, 'getIccById');
|
||||
stubGetIccByIdOfIccManager.returns(card2);
|
||||
stubGetIccById.returns(slot);
|
||||
SIMSlotManager.handleEvent({
|
||||
type: 'iccdetected',
|
||||
iccId: 1
|
||||
});
|
||||
assert.isTrue(stubGetIccById.calledWith(1));
|
||||
assert.isTrue(stubUpdate.calledWith(card2));
|
||||
assert.isTrue(SIMSlotManager.ready);
|
||||
});
|
||||
});
|
||||
114
gaia/apps/sharedtest/test/unit/simslot_test.js
Executable file
114
gaia/apps/sharedtest/test/unit/simslot_test.js
Executable file
|
|
@ -0,0 +1,114 @@
|
|||
/* globals SIMSlot */
|
||||
'use strict';
|
||||
|
||||
suite('SIMSlot', function() {
|
||||
suiteSetup(function() {
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
});
|
||||
|
||||
setup(function(callback) {
|
||||
require('/shared/js/simslot.js', callback);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
});
|
||||
|
||||
test('new', function() {
|
||||
var created = false;
|
||||
window.addEventListener('simslot-created', function() {
|
||||
created = true;
|
||||
});
|
||||
/*jshint unused: false */
|
||||
var slot = new SIMSlot(null, 0);
|
||||
assert.isTrue(created);
|
||||
});
|
||||
|
||||
test('update', function() {
|
||||
var slot = new SIMSlot(null, 0);
|
||||
var card = document.createElement('div');
|
||||
slot.update(card);
|
||||
assert.deepEqual(slot.simCard, card);
|
||||
});
|
||||
|
||||
test('isAbsent', function() {
|
||||
var card = document.createElement('div');
|
||||
card.iccInfo = {
|
||||
iccid: 1
|
||||
};
|
||||
var slot1 = new SIMSlot(null, 0, card);
|
||||
assert.isFalse(slot1.isAbsent());
|
||||
|
||||
var slot2 = new SIMSlot(null, 0);
|
||||
assert.isTrue(slot2.isAbsent());
|
||||
});
|
||||
|
||||
['permanentBlocked'].forEach(function(absentType) {
|
||||
test('isAbsent: ' + absentType, function() {
|
||||
var card = document.createElement('div');
|
||||
card.cardState = absentType;
|
||||
var slot = new SIMSlot(null, 0, card);
|
||||
assert.isTrue(slot.isAbsent());
|
||||
});
|
||||
});
|
||||
|
||||
['unknown', 'illegal', 'absent', 'ready', null].forEach(function(lockType) {
|
||||
test('isLocked: ' + lockType, function() {
|
||||
var card = document.createElement('div');
|
||||
card.cardState = lockType;
|
||||
var slot = new SIMSlot(null, 0, card);
|
||||
assert.isFalse(slot.isLocked());
|
||||
});
|
||||
});
|
||||
|
||||
['pinRequired', 'pukRequired', 'networkLocked',
|
||||
'corporateLocked', 'serviceProviderLocked', 'network1Locked',
|
||||
'network2Locked', 'hrpdNetworkLocked', 'ruimCorporateLocked',
|
||||
'ruimServiceProviderLocked'].forEach(function(lockType) {
|
||||
test('isLocked: ' + lockType, function() {
|
||||
var card = document.createElement('div');
|
||||
card.cardState = lockType;
|
||||
var slot = new SIMSlot(null, 0, card);
|
||||
assert.isTrue(slot.isLocked());
|
||||
});
|
||||
});
|
||||
|
||||
suite('handleEvent', function() {
|
||||
test('cardstatechange', function() {
|
||||
var slot = new SIMSlot(null, 0);
|
||||
var stubPublish = this.sinon.stub(slot, 'publish');
|
||||
slot.handleEvent({
|
||||
type: 'cardstatechange'
|
||||
});
|
||||
assert.isTrue(stubPublish.calledWith('cardstatechange'));
|
||||
});
|
||||
|
||||
test('iccinfochange', function() {
|
||||
var slot = new SIMSlot(null, 0);
|
||||
var stubPublish = this.sinon.stub(slot, 'publish');
|
||||
slot.handleEvent({
|
||||
type: 'iccinfochange'
|
||||
});
|
||||
assert.isTrue(stubPublish.calledWith('iccinfochange'));
|
||||
});
|
||||
|
||||
test('stkcommand', function() {
|
||||
var slot = new SIMSlot(null, 0);
|
||||
var stubPublish = this.sinon.stub(slot, 'publish');
|
||||
slot.handleEvent({
|
||||
type: 'stkcommand'
|
||||
});
|
||||
assert.isTrue(stubPublish.calledWith('stkcommand'));
|
||||
});
|
||||
|
||||
test('stksessionend', function() {
|
||||
var slot = new SIMSlot(null, 0);
|
||||
var stubPublish = this.sinon.stub(slot, 'publish');
|
||||
slot.handleEvent({
|
||||
type: 'stksessionend'
|
||||
});
|
||||
assert.isTrue(stubPublish.calledWith('stksessionend'));
|
||||
});
|
||||
});
|
||||
});
|
||||
32
gaia/apps/sharedtest/test/unit/sticky_header_test.html
Executable file
32
gaia/apps/sharedtest/test/unit/sticky_header_test.html
Executable file
|
|
@ -0,0 +1,32 @@
|
|||
<div id="scrollable">
|
||||
</div>
|
||||
|
||||
<style>
|
||||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
header {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
div {
|
||||
height: 60px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
#sticky {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#scrollable {
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
overflow-x: scroll;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue