Commit bef8a166 authored by David Sveningsson's avatar David Sveningsson
Browse files

feat(meta): `transparent` can be limited to specific elements

parent edd24516
......@@ -35,7 +35,7 @@ export interface MetaElement {
deprecated?: boolean | string | DeprecatedElement;
foreign?: boolean;
void?: boolean;
transparent?: boolean;
transparent?: boolean | string[];
scriptSupporting?: boolean;
form?: boolean;
labelable?: boolean;
......@@ -164,6 +164,9 @@ as a child of a `<span>` element (phrasing) it only allows new phrasing content.
For custom elements it can be useful to set this if the content category isn't
flow.
When set to `true` all children are checked.
When set to array only the listed tagnames or content categories are checked.
### `scriptSupporting`
Elements whose primary purpose is to support scripting should set this flag to `true`.
......
......@@ -51,7 +51,7 @@ export interface MetaData {
deprecated?: boolean | string | DeprecatedElement;
foreign?: boolean;
void?: boolean;
transparent?: boolean;
transparent?: boolean | string[];
implicitClosed?: string[];
scriptSupporting?: boolean;
form?: boolean;
......
......@@ -238,7 +238,7 @@ export class Validator {
* @param defaultMatch - The default return value when node categories is not known.
*/
// eslint-disable-next-line complexity
private static validatePermittedCategory(
public static validatePermittedCategory(
node: HtmlElement,
category: string,
defaultMatch: boolean
......
......@@ -95,20 +95,84 @@ describe("rule element-permitted-content", () => {
);
});
it("should not report error when phrasing a-element is child of @phrasing", () => {
expect.assertions(1);
const report = htmlvalidate.validateString("<span><a><span></span></a></span>");
expect(report).toBeValid();
});
describe("transparent", () => {
it("should not report error when phrasing a-element is child of @phrasing", () => {
expect.assertions(1);
const report = htmlvalidate.validateString("<span><a><span></span></a></span>");
expect(report).toBeValid();
});
it("should report error when non-phrasing a-element is child of @phrasing", () => {
expect.assertions(2);
const report = htmlvalidate.validateString("<span><a><div></div></a></span>");
expect(report).toBeInvalid();
expect(report).toHaveError(
"element-permitted-content",
"Element <div> is not permitted as content in <span>"
);
it("should report error when non-phrasing a-element is child of @phrasing", () => {
expect.assertions(2);
const report = htmlvalidate.validateString("<span><a><div></div></a></span>");
expect(report).toBeInvalid();
expect(report).toHaveError(
"element-permitted-content",
"Element <div> is not permitted as content in <span>"
);
});
it("should report error for children listed as transparent", () => {
expect.assertions(2);
const htmlvalidate = new HtmlValidate({
elements: [
"html5",
{
"transparent-element": {
phrasing: true,
transparent: ["div"],
},
},
],
rules: { "element-permitted-content": "error" },
});
const markup = "<span><transparent-element><div></div></transparent-element></span>";
const report = htmlvalidate.validateString(markup);
expect(report).toBeInvalid();
expect(report).toHaveError(
"element-permitted-content",
"Element <div> is not permitted as content in <span>"
);
});
it("should not report error for children not listed as transparent", () => {
expect.assertions(1);
const htmlvalidate = new HtmlValidate({
elements: [
"html5",
{
"transparent-element": {
phrasing: true,
transparent: ["p"],
},
},
],
rules: { "element-permitted-content": "error" },
});
const markup = "<span><transparent-element><div></div></transparent-element></span>";
const report = htmlvalidate.validateString(markup);
expect(report).toBeValid();
});
it("should not report error for transparent unknown element children", () => {
expect.assertions(1);
const htmlvalidate = new HtmlValidate({
elements: [
"html5",
{
"transparent-element": {
phrasing: true,
transparent: ["@flow"],
},
},
],
rules: { "element-permitted-content": "error" },
});
const markup =
"<span><transparent-element><unknown-element></unknown-element></transparent-element></span>";
const report = htmlvalidate.validateString(markup);
expect(report).toBeValid();
});
});
it("should report error when label contains non-phrasing", () => {
......
......@@ -4,6 +4,19 @@ import { Validator } from "../meta";
import { Permitted } from "../meta/element";
import { Rule, RuleDocumentation, ruleDocumentationUrl } from "../rule";
function getTransparentChildren(node: HtmlElement, transparent: boolean | string[]): HtmlElement[] {
if (typeof transparent === "boolean") {
return node.childElements;
} else {
/* only return children which matches one of the given content categories */
return node.childElements.filter((it) => {
return transparent.some((category) => {
return Validator.validatePermittedCategory(it, category, false);
});
});
}
}
export default class ElementPermittedContent extends Rule {
public documentation(): RuleDocumentation {
return {
......@@ -61,11 +74,12 @@ export default class ElementPermittedContent extends Rule {
return true;
}
/* for transparent elements all of the children must be validated against
/* for transparent elements all/listed children must be validated against
* the (this elements) parent, i.e. if this node was removed from the DOM it
* should still be valid. */
if (cur.meta && cur.meta.transparent) {
return cur.childElements
const children = getTransparentChildren(cur, cur.meta.transparent);
return children
.map((child: HtmlElement) => {
return this.validatePermittedContentImpl(child, parent, rules);
})
......
......@@ -80,7 +80,7 @@
"transparent": {
"title": "Mark element as transparent",
"description": "Transparent elements follows the same content model as its parent, i.e. the content must be allowed in the parent.",
"type": "boolean"
"anyOf": [{ "type": "boolean" }, { "type": "array", "items": { "type": "string" } }]
},
"implicitClosed": {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment