Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>MozLitElement Tests</title>
<link
rel="stylesheet"
/>
<script>
let html, render, renderTarget, defaultTemplate, MozLitElement;
var mockL10n;
add_setup(async function setup() {
({ html, render } =
await import("chrome://global/content/vendor/lit.all.mjs"));
({ MozLitElement } =
await import("chrome://global/content/lit-utils.mjs"));
renderTarget = document.getElementById("render");
const l10nReg = new L10nRegistry();
const fs = [
{
path: "/localization/en-US/mock.ftl",
source: `
example-label =
.label = Example label!
.message = And a message!
.tooltiptext = Also a tooltiptext!
.rename = Also renamed!
.accesskey = E
has-custom-attr =
.label = CustomAttribute!
.message = message!
.tooltiptext = tooltiptext!
.rename = rename!
.accesskey = A
.myattr = Custom attribute
`,
},
];
const source = L10nFileSource.createMock(
"test",
"app",
["en-US"],
"/localization/{locale}",
fs
);
l10nReg.registerSources([source]);
mockL10n = new DOMLocalization(["/mock.ftl"], false, l10nReg, [
"en-US",
]);
mockL10n.addResourceIds(["/mock.ftl"]);
defaultTemplate = html`
<example-element data-l10n-id="example-label"></example-element>
`;
});
async function renderTemplate(template = defaultTemplate) {
render(template, renderTarget);
return renderTarget.firstElementChild;
}
function defineExampleElement() {
class ExampleElement extends MozLitElement {
static properties = {
label: { type: String, fluent: true },
message: { type: String, fluent: true },
tooltipText: { type: String, fluent: true },
renamedAttribute: {
type: String,
fluent: true,
attribute: "rename",
},
mappedAttribute: {
type: String,
mapped: true,
attribute: "mapped",
},
ariaLabel: { type: String, mapped: true },
accessKey: { type: String, mapped: true, fluent: true },
};
render() {
return (
this.label +
this.message +
this.tooltipText +
this.renamedAttribute +
this.accessKey
);
}
updated(changes) {
if (changes.has("label") && this.label) {
this.dispatchEvent(new CustomEvent("label-changed"));
}
}
}
customElements.define("example-element", ExampleElement);
}
add_task(async function testL10nAttrs() {
let el = await renderTemplate();
window.el = el;
defineExampleElement();
is(el.shadowRoot.textContent, "", "There's no text");
is(
el.dataset.l10nAttrs,
"label,message,tooltiptext,rename,accesskey",
"data-l10n-attrs is set"
);
await new Promise(r =>
el.addEventListener("label-changed", r, { once: true })
);
is(
el.shadowRoot.textContent,
"Example label!And a message!Also a tooltiptext!Also renamed!E",
"Text rendered automatically on upgrade"
);
is(el.accessKey, "E", "accessKey is mapped with custom attribute");
ok(!el.hasAttribute("accesskey"), "renamed attribute was removed");
});
add_task(async function testCustomL10nAttrs() {
let el = await renderTemplate(
html`<example-element
data-l10n-id="has-custom-attr"
data-l10n-attrs="myattr"
></example-element>`
);
await new Promise(r =>
el.addEventListener("label-changed", r, { once: true })
);
is(
el.shadowRoot.textContent,
"CustomAttribute!message!tooltiptext!rename!A",
"Text rendered automatically on upgrade"
);
is(
el.dataset.l10nAttrs,
"label,message,tooltiptext,rename,accesskey,myattr",
"data-l10n-attrs is set"
);
is(
el.getAttribute("myattr"),
"Custom attribute",
"Custom attribute was set"
);
});
add_task(async function testMappedAttributes() {
let el = await renderTemplate(
html`<example-element
accesskey="f"
mapped="mapped-val"
aria-label="Label!"
></example-element>`
);
is(el.accessKey, "f", "accessKey property is correct");
ok(!el.hasAttribute("accesskey"), "accesskey attribute was removed");
is(
el.mappedAttribute,
"mapped-val",
"mappedAttribute is mapped with custom attribute"
);
ok(!el.hasAttribute("mapped"), "mapped attribute was removed");
is(el.ariaLabel, "Label!", "ariaLabel property is set");
ok(!el.hasAttribute("aria-label"), "aria-label was removed");
});
function defineSiblingElements() {
class SiblingBase extends MozLitElement {
static properties = {
label: { type: String, fluent: true },
description: { type: String, fluent: true },
ariaLabel: { type: String, mapped: true },
};
render() {
return this.label;
}
}
customElements.define("sibling-base", SiblingBase);
class SiblingA extends SiblingBase {
static properties = {
propA: { type: String, fluent: true },
mappedA: { type: String, mapped: true, attribute: "mapped-a" },
};
}
customElements.define("sibling-a", SiblingA);
class SiblingB extends SiblingBase {
static properties = {
propB: { type: String, fluent: true },
mappedB: { type: String, mapped: true, attribute: "mapped-b" },
};
}
customElements.define("sibling-b", SiblingB);
}
// Regression test: fluentProperties and mappedAttributes are stored on the
// class, so a subclass adding its own must not mutate the array it inherits
// from an ancestor, which would leak that subclass's attributes into its
// parent and every sibling (and, since classes finalize lazily on first
// use, do so in an order-dependent, flaky way).
add_task(async function testFluentPropertiesNotSharedAcrossSubclasses() {
defineSiblingElements();
// Rendering upgrades the elements, which sets data-l10n-attrs from each
// class's fluentProperties.
render(
html`<sibling-base></sibling-base>
<sibling-a></sibling-a>
<sibling-b></sibling-b>`,
renderTarget
);
let [baseEl, aEl, bEl] = renderTarget.children;
await Promise.all([
baseEl.updateComplete,
aEl.updateComplete,
bEl.updateComplete,
]);
is(
baseEl.dataset.l10nAttrs,
"label,description",
"Base element only exposes its own fluent attributes"
);
is(
aEl.dataset.l10nAttrs,
"label,description,propa",
"Sibling A inherits the base attributes plus its own, not sibling B's"
);
is(
bEl.dataset.l10nAttrs,
"label,description,propb",
"Sibling B inherits the base attributes plus its own, not sibling A's"
);
isDeeply(
baseEl.constructor.mappedAttributes,
[["ariaLabel", "ariaLabelAttribute"]],
"Base element only has its own mapped attribute"
);
isDeeply(
aEl.constructor.mappedAttributes,
[
["ariaLabel", "ariaLabelAttribute"],
["mappedA", "mappedAAttribute"],
],
"Sibling A has the base mapped attribute plus its own, not sibling B's"
);
isDeeply(
bEl.constructor.mappedAttributes,
[
["ariaLabel", "ariaLabelAttribute"],
["mappedB", "mappedBAttribute"],
],
"Sibling B has the base mapped attribute plus its own, not sibling A's"
);
});
</script>
</head>
<body>
<div id="render"></div>
</body>
</html>