summaryrefslogtreecommitdiffstats
path: root/browser/components/urlbar/tests/unit/test_l10nCache.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/urlbar/tests/unit/test_l10nCache.js')
-rw-r--r--browser/components/urlbar/tests/unit/test_l10nCache.js685
1 files changed, 685 insertions, 0 deletions
diff --git a/browser/components/urlbar/tests/unit/test_l10nCache.js b/browser/components/urlbar/tests/unit/test_l10nCache.js
new file mode 100644
index 0000000000..e92c75fa01
--- /dev/null
+++ b/browser/components/urlbar/tests/unit/test_l10nCache.js
@@ -0,0 +1,685 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests L10nCache in UrlbarUtils.jsm.
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ L10nCache: "resource:///modules/UrlbarUtils.sys.mjs",
+});
+
+add_task(async function comprehensive() {
+ // Set up a mock localization.
+ let l10n = initL10n({
+ args0a: "Zero args value",
+ args0b: "Another zero args value",
+ args1a: "One arg value is { $arg1 }",
+ args1b: "Another one arg value is { $arg1 }",
+ args2a: "Two arg values are { $arg1 } and { $arg2 }",
+ args2b: "More two arg values are { $arg1 } and { $arg2 }",
+ args3a: "Three arg values are { $arg1 }, { $arg2 }, and { $arg3 }",
+ args3b: "More three arg values are { $arg1 }, { $arg2 }, and { $arg3 }",
+ attrs1: [".label = attrs1 label has zero args"],
+ attrs2: [
+ ".label = attrs2 label has zero args",
+ ".tooltiptext = attrs2 tooltiptext arg value is { $arg1 }",
+ ],
+ attrs3: [
+ ".label = attrs3 label has zero args",
+ ".tooltiptext = attrs3 tooltiptext arg value is { $arg1 }",
+ ".alt = attrs3 alt arg values are { $arg1 } and { $arg2 }",
+ ],
+ });
+
+ let tests = [
+ // different strings with the same number of args and also the same strings
+ // with different args
+ {
+ obj: {
+ id: "args0a",
+ },
+ expected: {
+ value: "Zero args value",
+ attributes: null,
+ },
+ },
+ {
+ obj: {
+ id: "args0b",
+ },
+ expected: {
+ value: "Another zero args value",
+ attributes: null,
+ },
+ },
+ {
+ obj: {
+ id: "args1a",
+ args: { arg1: "foo1" },
+ },
+ expected: {
+ value: "One arg value is foo1",
+ attributes: null,
+ },
+ },
+ {
+ obj: {
+ id: "args1a",
+ args: { arg1: "foo2" },
+ },
+ expected: {
+ value: "One arg value is foo2",
+ attributes: null,
+ },
+ },
+ {
+ obj: {
+ id: "args1b",
+ args: { arg1: "foo1" },
+ },
+ expected: {
+ value: "Another one arg value is foo1",
+ attributes: null,
+ },
+ },
+ {
+ obj: {
+ id: "args1b",
+ args: { arg1: "foo2" },
+ },
+ expected: {
+ value: "Another one arg value is foo2",
+ attributes: null,
+ },
+ },
+ {
+ obj: {
+ id: "args2a",
+ args: { arg1: "foo1", arg2: "bar1" },
+ },
+ expected: {
+ value: "Two arg values are foo1 and bar1",
+ attributes: null,
+ },
+ },
+ {
+ obj: {
+ id: "args2a",
+ args: { arg1: "foo2", arg2: "bar2" },
+ },
+ expected: {
+ value: "Two arg values are foo2 and bar2",
+ attributes: null,
+ },
+ },
+ {
+ obj: {
+ id: "args2b",
+ args: { arg1: "foo1", arg2: "bar1" },
+ },
+ expected: {
+ value: "More two arg values are foo1 and bar1",
+ attributes: null,
+ },
+ },
+ {
+ obj: {
+ id: "args2b",
+ args: { arg1: "foo2", arg2: "bar2" },
+ },
+ expected: {
+ value: "More two arg values are foo2 and bar2",
+ attributes: null,
+ },
+ },
+ {
+ obj: {
+ id: "args3a",
+ args: { arg1: "foo1", arg2: "bar1", arg3: "baz1" },
+ },
+ expected: {
+ value: "Three arg values are foo1, bar1, and baz1",
+ attributes: null,
+ },
+ },
+ {
+ obj: {
+ id: "args3a",
+ args: { arg1: "foo2", arg2: "bar2", arg3: "baz2" },
+ },
+ expected: {
+ value: "Three arg values are foo2, bar2, and baz2",
+ attributes: null,
+ },
+ },
+ {
+ obj: {
+ id: "args3b",
+ args: { arg1: "foo1", arg2: "bar1", arg3: "baz1" },
+ },
+ expected: {
+ value: "More three arg values are foo1, bar1, and baz1",
+ attributes: null,
+ },
+ },
+ {
+ obj: {
+ id: "args3b",
+ args: { arg1: "foo2", arg2: "bar2", arg3: "baz2" },
+ },
+ expected: {
+ value: "More three arg values are foo2, bar2, and baz2",
+ attributes: null,
+ },
+ },
+
+ // two instances of the same string with their args swapped
+ {
+ obj: {
+ id: "args2a",
+ args: { arg1: "arg A", arg2: "arg B" },
+ },
+ expected: {
+ value: "Two arg values are arg A and arg B",
+ attributes: null,
+ },
+ },
+ {
+ obj: {
+ id: "args2a",
+ args: { arg1: "arg B", arg2: "arg A" },
+ },
+ expected: {
+ value: "Two arg values are arg B and arg A",
+ attributes: null,
+ },
+ },
+
+ // strings with attributes
+ {
+ obj: {
+ id: "attrs1",
+ },
+ expected: {
+ value: null,
+ attributes: {
+ label: "attrs1 label has zero args",
+ },
+ },
+ },
+ {
+ obj: {
+ id: "attrs2",
+ args: {
+ arg1: "arg A",
+ },
+ },
+ expected: {
+ value: null,
+ attributes: {
+ label: "attrs2 label has zero args",
+ tooltiptext: "attrs2 tooltiptext arg value is arg A",
+ },
+ },
+ },
+ {
+ obj: {
+ id: "attrs3",
+ args: {
+ arg1: "arg A",
+ arg2: "arg B",
+ },
+ },
+ expected: {
+ value: null,
+ attributes: {
+ label: "attrs3 label has zero args",
+ tooltiptext: "attrs3 tooltiptext arg value is arg A",
+ alt: "attrs3 alt arg values are arg A and arg B",
+ },
+ },
+ },
+ ];
+
+ let cache = new L10nCache(l10n);
+
+ // Get some non-cached strings.
+ Assert.ok(!cache.get({ id: "uncached1" }), "Uncached string 1");
+ Assert.ok(!cache.get({ id: "uncached2", args: "foo" }), "Uncached string 2");
+
+ // Add each test string and get it back.
+ for (let { obj, expected } of tests) {
+ await cache.add(obj);
+ let message = cache.get(obj);
+ Assert.deepEqual(
+ message,
+ expected,
+ "Expected message for obj: " + JSON.stringify(obj)
+ );
+ }
+
+ // Get each string again to make sure each add didn't somehow mess up the
+ // previously added strings.
+ for (let { obj, expected } of tests) {
+ Assert.deepEqual(
+ cache.get(obj),
+ expected,
+ "Expected message for obj: " + JSON.stringify(obj)
+ );
+ }
+
+ // Delete some of the strings. We'll delete every other one to mix it up.
+ for (let i = 0; i < tests.length; i++) {
+ if (i % 2 == 0) {
+ let { obj } = tests[i];
+ cache.delete(obj);
+ Assert.ok(!cache.get(obj), "Deleted from cache: " + JSON.stringify(obj));
+ }
+ }
+
+ // Get each remaining string.
+ for (let i = 0; i < tests.length; i++) {
+ if (i % 2 != 0) {
+ let { obj, expected } = tests[i];
+ Assert.deepEqual(
+ cache.get(obj),
+ expected,
+ "Expected message for obj: " + JSON.stringify(obj)
+ );
+ }
+ }
+
+ // Clear the cache.
+ cache.clear();
+ for (let { obj } of tests) {
+ Assert.ok(!cache.get(obj), "After cache clear: " + JSON.stringify(obj));
+ }
+
+ // `ensure` each test string and get it back.
+ for (let { obj, expected } of tests) {
+ await cache.ensure(obj);
+ let message = cache.get(obj);
+ Assert.deepEqual(
+ message,
+ expected,
+ "Expected message for obj: " + JSON.stringify(obj)
+ );
+
+ // Call `ensure` again. This time, `add` should not be called.
+ let originalAdd = cache.add;
+ cache.add = () => Assert.ok(false, "add erroneously called");
+ await cache.ensure(obj);
+ cache.add = originalAdd;
+ }
+
+ // Clear the cache again.
+ cache.clear();
+ for (let { obj } of tests) {
+ Assert.ok(!cache.get(obj), "After cache clear: " + JSON.stringify(obj));
+ }
+
+ // `ensureAll` the test strings and get them back.
+ let objects = tests.map(({ obj }) => obj);
+ await cache.ensureAll(objects);
+ for (let { obj, expected } of tests) {
+ let message = cache.get(obj);
+ Assert.deepEqual(
+ message,
+ expected,
+ "Expected message for obj: " + JSON.stringify(obj)
+ );
+ }
+
+ // Ensure the cache is cleared after the app locale changes
+ Assert.greater(cache.size(), 0, "The cache has messages in it.");
+ Services.obs.notifyObservers(null, "intl:app-locales-changed");
+ await l10n.ready;
+ Assert.equal(cache.size(), 0, "The cache is empty on app locale change");
+});
+
+// Tests the `excludeArgsFromCacheKey` option.
+add_task(async function excludeArgsFromCacheKey() {
+ // Set up a mock localization.
+ let l10n = initL10n({
+ args0: "Zero args value",
+ args1: "One arg value is { $arg1 }",
+ attrs0: [".label = attrs0 label has zero args"],
+ attrs1: [
+ ".label = attrs1 label has zero args",
+ ".tooltiptext = attrs1 tooltiptext arg value is { $arg1 }",
+ ],
+ });
+
+ let cache = new L10nCache(l10n);
+
+ // Test cases. For each test case, we cache a string using one or more
+ // methods, `cache.add({ excludeArgsFromCacheKey: true })` and/or
+ // `cache.ensure({ excludeArgsFromCacheKey: true })`. After calling each
+ // method, we call `cache.get()` to get the cached string.
+ //
+ // Test cases are cumulative, so when `cache.add()` is called for a string and
+ // then `cache.ensure()` is called for the same string but with different l10n
+ // argument values, the string should be re-cached with the new values.
+ //
+ // Each item in the tests array is: `{ methods, obj, gets }`
+ //
+ // {array} methods
+ // Array of cache method names, one or more of: "add", "ensure"
+ // Methods are called in the order they are listed.
+ // {object} obj
+ // An l10n object that will be passed to the cache methods:
+ // `{ id, args, excludeArgsFromCacheKey }`
+ // {array} gets
+ // An array of objects that describes a series of calls to `cache.get()` and
+ // the expected return values: `{ obj, expected }`
+ //
+ // {object} obj
+ // An l10n object that will be passed to `cache.get():`
+ // `{ id, args, excludeArgsFromCacheKey }`
+ // {object} expected
+ // The expected return value from `get()`.
+ let tests = [
+ // args0: string with no args and no attributes
+ {
+ methods: ["add", "ensure"],
+ obj: {
+ id: "args0",
+ excludeArgsFromCacheKey: true,
+ },
+ gets: [
+ {
+ obj: { id: "args0" },
+ expected: {
+ value: "Zero args value",
+ attributes: null,
+ },
+ },
+ {
+ obj: { id: "args0", excludeArgsFromCacheKey: true },
+ expected: {
+ value: "Zero args value",
+ attributes: null,
+ },
+ },
+ ],
+ },
+
+ // args1: string with one arg and no attributes
+ {
+ methods: ["add"],
+ obj: {
+ id: "args1",
+ args: { arg1: "ADD" },
+ excludeArgsFromCacheKey: true,
+ },
+ gets: [
+ {
+ obj: { id: "args1" },
+ expected: {
+ value: "One arg value is ADD",
+ attributes: null,
+ },
+ },
+ {
+ obj: { id: "args1", excludeArgsFromCacheKey: true },
+ expected: {
+ value: "One arg value is ADD",
+ attributes: null,
+ },
+ },
+ {
+ obj: {
+ id: "args1",
+ args: { arg1: "some other value" },
+ excludeArgsFromCacheKey: true,
+ },
+ expected: {
+ value: "One arg value is ADD",
+ attributes: null,
+ },
+ },
+ {
+ obj: {
+ id: "args1",
+ args: { arg1: "some other value" },
+ },
+ expected: undefined,
+ },
+ ],
+ },
+ {
+ methods: ["ensure"],
+ obj: {
+ id: "args1",
+ args: { arg1: "ENSURE" },
+ excludeArgsFromCacheKey: true,
+ },
+ gets: [
+ {
+ obj: { id: "args1" },
+ expected: {
+ value: "One arg value is ENSURE",
+ attributes: null,
+ },
+ },
+ {
+ obj: { id: "args1", excludeArgsFromCacheKey: true },
+ expected: {
+ value: "One arg value is ENSURE",
+ attributes: null,
+ },
+ },
+ {
+ obj: {
+ id: "args1",
+ args: { arg1: "some other value" },
+ excludeArgsFromCacheKey: true,
+ },
+ expected: {
+ value: "One arg value is ENSURE",
+ attributes: null,
+ },
+ },
+ {
+ obj: {
+ id: "args1",
+ args: { arg1: "some other value" },
+ },
+ expected: undefined,
+ },
+ ],
+ },
+
+ // attrs0: string with no args and one attribute
+ {
+ methods: ["add", "ensure"],
+ obj: {
+ id: "attrs0",
+ excludeArgsFromCacheKey: true,
+ },
+ gets: [
+ {
+ obj: { id: "attrs0" },
+ expected: {
+ value: null,
+ attributes: {
+ label: "attrs0 label has zero args",
+ },
+ },
+ },
+ {
+ obj: { id: "attrs0", excludeArgsFromCacheKey: true },
+ expected: {
+ value: null,
+ attributes: {
+ label: "attrs0 label has zero args",
+ },
+ },
+ },
+ ],
+ },
+
+ // attrs1: string with one arg and two attributes
+ {
+ methods: ["add"],
+ obj: {
+ id: "attrs1",
+ args: { arg1: "ADD" },
+ excludeArgsFromCacheKey: true,
+ },
+ gets: [
+ {
+ obj: { id: "attrs1" },
+ expected: {
+ value: null,
+ attributes: {
+ label: "attrs1 label has zero args",
+ tooltiptext: "attrs1 tooltiptext arg value is ADD",
+ },
+ },
+ },
+ {
+ obj: { id: "attrs1", excludeArgsFromCacheKey: true },
+ expected: {
+ value: null,
+ attributes: {
+ label: "attrs1 label has zero args",
+ tooltiptext: "attrs1 tooltiptext arg value is ADD",
+ },
+ },
+ },
+ {
+ obj: {
+ id: "attrs1",
+ args: { arg1: "some other value" },
+ excludeArgsFromCacheKey: true,
+ },
+ expected: {
+ value: null,
+ attributes: {
+ label: "attrs1 label has zero args",
+ tooltiptext: "attrs1 tooltiptext arg value is ADD",
+ },
+ },
+ },
+ {
+ obj: {
+ id: "attrs1",
+ args: { arg1: "some other value" },
+ },
+ expected: undefined,
+ },
+ ],
+ },
+ {
+ methods: ["ensure"],
+ obj: {
+ id: "attrs1",
+ args: { arg1: "ENSURE" },
+ excludeArgsFromCacheKey: true,
+ },
+ gets: [
+ {
+ obj: { id: "attrs1" },
+ expected: {
+ value: null,
+ attributes: {
+ label: "attrs1 label has zero args",
+ tooltiptext: "attrs1 tooltiptext arg value is ENSURE",
+ },
+ },
+ },
+ {
+ obj: { id: "attrs1", excludeArgsFromCacheKey: true },
+ expected: {
+ value: null,
+ attributes: {
+ label: "attrs1 label has zero args",
+ tooltiptext: "attrs1 tooltiptext arg value is ENSURE",
+ },
+ },
+ },
+ {
+ obj: {
+ id: "attrs1",
+ args: { arg1: "some other value" },
+ excludeArgsFromCacheKey: true,
+ },
+ expected: {
+ value: null,
+ attributes: {
+ label: "attrs1 label has zero args",
+ tooltiptext: "attrs1 tooltiptext arg value is ENSURE",
+ },
+ },
+ },
+ {
+ obj: {
+ id: "attrs1",
+ args: { arg1: "some other value" },
+ },
+ expected: undefined,
+ },
+ ],
+ },
+ ];
+
+ let sandbox = sinon.createSandbox();
+ let spy = sandbox.spy(cache, "add");
+
+ for (let { methods, obj, gets } of tests) {
+ for (let method of methods) {
+ info(`Calling method '${method}' with l10n obj: ` + JSON.stringify(obj));
+ await cache[method](obj);
+
+ // `add()` should always be called: We either just called it directly, or
+ // `ensure({ excludeArgsFromCacheKey: true })` called it.
+ Assert.ok(
+ spy.calledOnce,
+ "add() should have been called once: " + JSON.stringify(obj)
+ );
+ spy.resetHistory();
+
+ for (let { obj: getObj, expected } of gets) {
+ Assert.deepEqual(
+ cache.get(getObj),
+ expected,
+ "Expected message for get: " + JSON.stringify(getObj)
+ );
+ }
+ }
+ }
+
+ sandbox.restore();
+});
+
+/**
+ * Sets up a mock localization.
+ *
+ * @param {object} pairs
+ * Fluent strings as key-value pairs.
+ * @returns {Localization}
+ * The mock Localization object.
+ */
+function initL10n(pairs) {
+ let source = Object.entries(pairs)
+ .map(([key, value]) => {
+ if (Array.isArray(value)) {
+ value = value.map(s => " \n" + s).join("");
+ }
+ return `${key} = ${value}`;
+ })
+ .join("\n");
+ let registry = new L10nRegistry();
+ registry.registerSources([
+ L10nFileSource.createMock(
+ "test",
+ "app",
+ ["en-US"],
+ "/localization/{locale}",
+ [{ source, path: "/localization/en-US/test.ftl" }]
+ ),
+ ]);
+ return new Localization(["/test.ftl"], true, registry, ["en-US"]);
+}