Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,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 }}');
});
});
});

View 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');
});
});
});

View 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);
});
});

View 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');
});
});
});

View 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;
}

View 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');
});
});
});

View 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');
});
});
});

View 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');
});
});
});