diff --git a/README.md b/README.md
index 492ea8d..c651d8c 100644
--- a/README.md
+++ b/README.md
@@ -113,7 +113,7 @@ Returns **[Array][62]<([object][60] \| [undefined][63])>** array of matched n
|E\[foo\|="en"\]|✓| |
|E:defined|Partially supported|Matching with MathML is not yet supported.|
|E:dir(ltr)|✓| |
-|E:lang(en)|Partially supported|Comma-separated list of language codes, e.g. `:lang(en, fr)`, is not yet supported.|
+|E:lang(en)|✓| |
|E:any‑link|✓| |
|E:link|✓| |
|E:visited|✓|Returns `false` or `null` to prevent fingerprinting.|
diff --git a/package.json b/package.json
index e60b7e4..d13f802 100644
--- a/package.json
+++ b/package.json
@@ -25,7 +25,7 @@
"dependencies": {
"@asamuzakjp/nwsapi": "^2.2.16",
"bidi-js": "^1.0.3",
- "css-tree": "^2.3.1",
+ "css-tree": "^3.0.0",
"is-potential-custom-element-name": "^1.0.1"
},
"devDependencies": {
diff --git a/src/js/constant.js b/src/js/constant.js
index 9d0b956..399ae20 100644
--- a/src/js/constant.js
+++ b/src/js/constant.js
@@ -6,14 +6,15 @@
export const ATTR_SELECTOR = 'AttributeSelector';
export const CLASS_SELECTOR = 'ClassSelector';
export const COMBINATOR = 'Combinator';
-export const EMPTY = '__EMPTY__';
export const IDENT = 'Identifier';
export const ID_SELECTOR = 'IdSelector';
export const NOT_SUPPORTED_ERR = 'NotSupportedError';
export const NTH = 'Nth';
+export const OPERATOR = 'Operator';
export const PS_CLASS_SELECTOR = 'PseudoClassSelector';
export const PS_ELEMENT_SELECTOR = 'PseudoElementSelector';
export const SELECTOR = 'Selector';
+export const STRING = 'String';
export const SYNTAX_ERR = 'SyntaxError';
export const TARGET_ALL = 'all';
export const TARGET_FIRST = 'first';
diff --git a/src/js/finder.js b/src/js/finder.js
index dd3c835..7ddedc7 100644
--- a/src/js/finder.js
+++ b/src/js/finder.js
@@ -18,11 +18,11 @@ import {
/* constants */
import {
ATTR_SELECTOR, BIT_01, CLASS_SELECTOR, COMBINATOR, DOCUMENT_FRAGMENT_NODE,
- DOCUMENT_NODE, ELEMENT_NODE, EMPTY, ID_SELECTOR, KEY_FORM_FOCUS,
- KEY_INPUT_DATE, KEY_INPUT_EDIT, KEY_INPUT_TEXT, KEY_LOGICAL,
- NOT_SUPPORTED_ERR, PS_CLASS_SELECTOR, PS_ELEMENT_SELECTOR, SHOW_ALL,
- SYNTAX_ERR, TARGET_ALL, TARGET_FIRST, TARGET_LINEAL, TARGET_SELF, TEXT_NODE,
- TYPE_SELECTOR, WALKER_FILTER
+ DOCUMENT_NODE, ELEMENT_NODE, ID_SELECTOR, KEY_FORM_FOCUS, KEY_INPUT_DATE,
+ KEY_INPUT_EDIT, KEY_INPUT_TEXT, KEY_LOGICAL, NOT_SUPPORTED_ERR,
+ PS_CLASS_SELECTOR, PS_ELEMENT_SELECTOR, SHOW_ALL, SYNTAX_ERR, TARGET_ALL,
+ TARGET_FIRST, TARGET_LINEAL, TARGET_SELF, TEXT_NODE, TYPE_SELECTOR,
+ WALKER_FILTER
} from './constant.js';
const DIR_NEXT = 'next';
const DIR_PREV = 'prev';
@@ -836,7 +836,11 @@ export class Finder {
} = opt;
const matched = new Set();
// :has(), :is(), :not(), :where()
- if (KEY_LOGICAL.includes(astName)) {
+ if (Array.isArray(astChildren) && KEY_LOGICAL.includes(astName)) {
+ if (!astChildren.length && astName !== 'is' && astName !== 'where') {
+ const css = generateCSS(ast);
+ throw new DOMException(`Invalid selector ${css}`, SYNTAX_ERR);
+ }
let astData;
if (this.#astCache.has(ast)) {
astData = this.#astCache.get(ast);
@@ -911,6 +915,10 @@ export class Finder {
} else if (Array.isArray(astChildren)) {
// :nth-child(), :nth-last-child(), nth-of-type(), :nth-last-of-type()
if (/^nth-(?:last-)?(?:child|of-type)$/.test(astName)) {
+ if (astChildren.length !== 1) {
+ const css = generateCSS(ast);
+ throw new DOMException(`Invalid selector ${css}`, SYNTAX_ERR);
+ }
const [branch] = astChildren;
const nodes = this._matchAnPlusB(branch, node, astName, opt);
return nodes;
@@ -918,19 +926,32 @@ export class Finder {
switch (astName) {
// :dir()
case 'dir': {
+ if (astChildren.length !== 1) {
+ const css = generateCSS(ast);
+ throw new DOMException(`Invalid selector ${css}`, SYNTAX_ERR);
+ }
const [astChild] = astChildren;
const res = matchDirectionPseudoClass(astChild, node);
if (res) {
- matched.add(res);
+ matched.add(node);
}
break;
}
// :lang()
case 'lang': {
- const [astChild] = astChildren;
- const res = matchLanguagePseudoClass(astChild, node, opt);
- if (res) {
- matched.add(res);
+ if (!astChildren.length) {
+ const css = generateCSS(ast);
+ throw new DOMException(`Invalid selector ${css}`, SYNTAX_ERR);
+ }
+ let bool;
+ for (const astChild of astChildren) {
+ bool = matchLanguagePseudoClass(astChild, node);
+ if (bool) {
+ break;
+ }
+ }
+ if (bool) {
+ matched.add(node);
}
break;
}
@@ -1671,6 +1692,10 @@ export class Finder {
const { children: astChildren, name: astName } = ast;
let res;
if (Array.isArray(astChildren)) {
+ if (astChildren.length !== 1) {
+ const css = generateCSS(ast);
+ throw new DOMException(`Invalid selector ${css}`, SYNTAX_ERR);
+ }
const { branches } = walkAST(astChildren[0]);
const [branch] = branches;
const [...leaves] = branch;
@@ -1735,13 +1760,7 @@ export class Finder {
_matchSelector(ast, node, opt = {}) {
const { type: astType } = ast;
const matched = new Set();
- if (ast.name === EMPTY) {
- return matched;
- }
const astName = unescapeSelector(ast.name);
- if (typeof astName === 'string' && astName !== ast.name) {
- ast.name = astName;
- }
if (node.nodeType === ELEMENT_NODE) {
switch (astType) {
case ATTR_SELECTOR: {
diff --git a/src/js/matcher.js b/src/js/matcher.js
index e6efe7b..8c6ab00 100644
--- a/src/js/matcher.js
+++ b/src/js/matcher.js
@@ -8,8 +8,8 @@ import { getDirectionality, getType, isNamespaceDeclared } from './utility.js';
/* constants */
import {
- ALPHA_NUM, ELEMENT_NODE, EMPTY, LANG_PART, NOT_SUPPORTED_ERR,
- PS_ELEMENT_SELECTOR, SYNTAX_ERR
+ ALPHA_NUM, ELEMENT_NODE, IDENT, LANG_PART, NOT_SUPPORTED_ERR,
+ PS_ELEMENT_SELECTOR, STRING, SYNTAX_ERR
} from './constant.js';
/**
@@ -73,15 +73,20 @@ export const matchPseudoElementSelector = (astName, astType, opt = {}) => {
* match directionality pseudo-class - :dir()
* @param {object} ast - AST
* @param {object} node - Element node
- * @returns {?object} - matched node
+ * @returns {boolean} - result
*/
export const matchDirectionPseudoClass = (ast, node) => {
- const dir = getDirectionality(node);
- let res;
- if (ast.name === dir) {
- res = node;
+ if (!ast.name) {
+ let type;
+ if (ast.name === '') {
+ type = '(empty String)';
+ } else {
+ type = getType(ast.name);
+ }
+ throw new TypeError(`Unexpected ast type ${type}`);
}
- return res ?? null;
+ const dir = getDirectionality(node);
+ return ast.name === dir;
};
/**
@@ -89,21 +94,24 @@ export const matchDirectionPseudoClass = (ast, node) => {
* @see https://datatracker.ietf.org/doc/html/rfc4647#section-3.3.1
* @param {object} ast - AST
* @param {object} node - Element node
- * @returns {?object} - matched node
+ * @returns {boolean} - result
*/
export const matchLanguagePseudoClass = (ast, node) => {
- if (ast.name === EMPTY) {
- return null;
+ const { name, type, value } = ast;
+ let astName;
+ if (type === STRING && value) {
+ astName = value;
+ } else if (type === IDENT && name) {
+ astName = unescapeSelector(name);
}
- const astName = unescapeSelector(ast.name);
- if (typeof astName === 'string' && astName !== ast.name) {
- ast.name = astName;
+ if (!astName) {
+ return false;
}
let res;
if (astName === '*') {
if (node.hasAttribute('lang')) {
if (node.getAttribute('lang')) {
- res = node;
+ res = true;
}
} else {
let parent = node.parentNode;
@@ -111,7 +119,7 @@ export const matchLanguagePseudoClass = (ast, node) => {
if (parent.nodeType === ELEMENT_NODE) {
if (parent.hasAttribute('lang')) {
if (parent.getAttribute('lang')) {
- res = node;
+ res = true;
}
break;
}
@@ -147,18 +155,13 @@ export const matchLanguagePseudoClass = (ast, node) => {
regExtendedLang = new RegExp(`^${astName}${LANG_PART}$`, 'i');
}
if (node.hasAttribute('lang')) {
- if (regExtendedLang.test(node.getAttribute('lang'))) {
- res = node;
- }
+ res = regExtendedLang.test(node.getAttribute('lang'));
} else {
let parent = node.parentNode;
while (parent) {
if (parent.nodeType === ELEMENT_NODE) {
if (parent.hasAttribute('lang')) {
- const value = parent.getAttribute('lang');
- if (regExtendedLang.test(value)) {
- res = node;
- }
+ res = regExtendedLang.test(parent.getAttribute('lang'));
break;
}
parent = parent.parentNode;
@@ -169,7 +172,7 @@ export const matchLanguagePseudoClass = (ast, node) => {
}
}
}
- return res ?? null;
+ return !!res;
};
/**
diff --git a/src/js/parser.js b/src/js/parser.js
index 96a72a4..e37a3d6 100644
--- a/src/js/parser.js
+++ b/src/js/parser.js
@@ -9,12 +9,11 @@ import { getType } from './utility.js';
/* constants */
import {
ATTR_SELECTOR, BIT_01, BIT_02, BIT_04, BIT_08, BIT_16, BIT_32, BIT_FFFF,
- CLASS_SELECTOR, DUO, EMPTY, HEX, HYPHEN, ID_SELECTOR, KEY_LOGICAL, NTH,
+ CLASS_SELECTOR, DUO, HEX, HYPHEN, ID_SELECTOR, KEY_LOGICAL, NTH,
PS_CLASS_SELECTOR, PS_ELEMENT_SELECTOR, SELECTOR, SYNTAX_ERR, TYPE_SELECTOR
} from './constant.js';
-const REG_LANG_QUOTED = /(:lang\(\s*("[A-Za-z\d\-*]*")\s*\))/;
-const REG_LOGICAL_EMPTY = /(:(is|where)\(\s*\))/;
-const REG_SHADOW_PSEUDO = /^part|slotted$/;
+const REG_EMPTY_PSEUDO_FUNC = /(?<=:(?:dir|has|host(?:-context)?|is|lang|not|nth-(?:last-)?(?:child|of-type)|where)\()\s+\)/g;
+const REG_SHADOW_PS_ELEMENT = /^part|slotted$/;
const U_FFFD = '\uFFFD';
/**
@@ -140,23 +139,8 @@ export const parseSelector = selector => {
res = toPlainObject(ast);
} catch (e) {
const { message } = e;
- // workaround for https://github.com/csstree/csstree/issues/265
- if (message === 'Identifier is expected' &&
- REG_LANG_QUOTED.test(selector)) {
- const [, lang, range] = REG_LANG_QUOTED.exec(selector);
- const escapedRange =
- range.replaceAll('*', '\\*').replace(/^"/, '').replace(/"$/, '');
- let escapedLang = lang.replace(range, escapedRange);
- if (escapedLang === ':lang()') {
- escapedLang = `:lang(${EMPTY})`;
- }
- res = parseSelector(selector.replace(lang, escapedLang));
- } else if (/^(?:Identifier|Selector) is expected$/.test(message) &&
- REG_LOGICAL_EMPTY.test(selector)) {
- const [, sel, name] = REG_LOGICAL_EMPTY.exec(selector);
- res = parseSelector(selector.replace(sel, `:${name}(${EMPTY})`));
- } else if (/^(?:"\]"|Attribute selector [()\s,=~^$*|]+) is expected$/.test(message) &&
- !selector.endsWith(']')) {
+ if (/^(?:"\]"|Attribute selector [()\s,=~^$*|]+) is expected$/.test(message) &&
+ !selector.endsWith(']')) {
const index = selector.lastIndexOf('[');
const sel = selector.substring(index);
if (sel.includes('"')) {
@@ -169,8 +153,16 @@ export const parseSelector = selector => {
} else {
res = parseSelector(`${selector}]`);
}
- } else if (message === '")" is expected' && !selector.endsWith(')')) {
- res = parseSelector(`${selector})`);
+ } else if (message === '")" is expected') {
+ // workaround for https://github.com/csstree/csstree/issues/283
+ if (REG_EMPTY_PSEUDO_FUNC.test(selector)) {
+ res =
+ parseSelector(`${selector.replaceAll(REG_EMPTY_PSEUDO_FUNC, ')')}`);
+ } else if (!selector.endsWith(')')) {
+ res = parseSelector(`${selector})`);
+ } else {
+ throw new DOMException(message, SYNTAX_ERR);
+ }
} else {
throw new DOMException(message, SYNTAX_ERR);
}
@@ -204,7 +196,7 @@ export const walkAST = (ast = {}) => {
break;
}
case PS_ELEMENT_SELECTOR: {
- if (REG_SHADOW_PSEUDO.test(node.name)) {
+ if (REG_SHADOW_PS_ELEMENT.test(node.name)) {
info.set('hasNestedSelector', true);
}
break;
@@ -242,11 +234,11 @@ export const walkAST = (ast = {}) => {
}
}
} else if (node.type === PS_ELEMENT_SELECTOR &&
- REG_SHADOW_PSEUDO.test(node.name)) {
+ REG_SHADOW_PS_ELEMENT.test(node.name)) {
const itemList = list.filter(i => {
const { name, type } = i;
const res =
- type === PS_ELEMENT_SELECTOR && REG_SHADOW_PSEUDO.test(name);
+ type === PS_ELEMENT_SELECTOR && REG_SHADOW_PS_ELEMENT.test(name);
return res;
});
for (const { children } of itemList) {
diff --git a/test/finder.test.js b/test/finder.test.js
index 8013354..d328b0a 100644
--- a/test/finder.test.js
+++ b/test/finder.test.js
@@ -13,8 +13,8 @@ import { Finder } from '../src/js/finder.js';
/* constants */
import {
- ATTR_SELECTOR, CLASS_SELECTOR, COMBINATOR, EMPTY, IDENT,
- ID_SELECTOR, NOT_SUPPORTED_ERR, NTH, PS_CLASS_SELECTOR, PS_ELEMENT_SELECTOR,
+ ATTR_SELECTOR, CLASS_SELECTOR, COMBINATOR, IDENT, ID_SELECTOR,
+ NOT_SUPPORTED_ERR, NTH, OPERATOR, PS_CLASS_SELECTOR, PS_ELEMENT_SELECTOR,
SELECTOR, SYNTAX_ERR, TYPE_SELECTOR
} from '../src/js/constant.js';
const AN_PLUS_B = 'AnPlusB';
@@ -3405,6 +3405,34 @@ describe('Finder', () => {
});
describe('match pseudo class selector', () => {
+ it('should throw', () => {
+ const leaf = {
+ children: [],
+ loc: null,
+ name: 'has',
+ type: PS_CLASS_SELECTOR
+ };
+ const node = document.getElementById('ul1');
+ const finder = new Finder(window);
+ finder.setup(':has()', node);
+ assert.throws(() => finder._matchPseudoClassSelector(leaf, node, {}),
+ DOMException, 'Invalid selector :has()');
+ });
+
+ it('should throw', () => {
+ const leaf = {
+ children: [],
+ loc: null,
+ name: 'not',
+ type: PS_CLASS_SELECTOR
+ };
+ const node = document.getElementById('ul1');
+ const finder = new Finder(window);
+ finder.setup(':not()', node);
+ assert.throws(() => finder._matchPseudoClassSelector(leaf, node, {}),
+ DOMException, 'Invalid selector :not()');
+ });
+
it('should get matched node', () => {
const leaf = {
children: [
@@ -3668,6 +3696,58 @@ describe('Finder', () => {
'Invalid selector :has(:not(:has(li,dd)))');
});
+ it('should throw', () => {
+ const leaf = {
+ children: [],
+ name: 'nth-child',
+ type: PS_CLASS_SELECTOR
+ };
+ const node = document.getElementById('dt1');
+ const finder = new Finder(window);
+ finder.setup(':nth-child()', node);
+ assert.throws(() => finder._matchPseudoClassSelector(leaf, node, {}),
+ DOMException, 'Invalid selector :nth-child()');
+ });
+
+ it('should throw', () => {
+ const leaf = {
+ children: [],
+ name: 'nth-last-child',
+ type: PS_CLASS_SELECTOR
+ };
+ const node = document.getElementById('dt1');
+ const finder = new Finder(window);
+ finder.setup(':nth-last-child()', node);
+ assert.throws(() => finder._matchPseudoClassSelector(leaf, node, {}),
+ DOMException, 'Invalid selector :nth-last-child()');
+ });
+
+ it('should throw', () => {
+ const leaf = {
+ children: [],
+ name: 'nth-of-type',
+ type: PS_CLASS_SELECTOR
+ };
+ const node = document.getElementById('dt1');
+ const finder = new Finder(window);
+ finder.setup(':nth-of-type()', node);
+ assert.throws(() => finder._matchPseudoClassSelector(leaf, node, {}),
+ DOMException, 'Invalid selector :nth-of-type()');
+ });
+
+ it('should throw', () => {
+ const leaf = {
+ children: [],
+ name: 'nth-last-of-type',
+ type: PS_CLASS_SELECTOR
+ };
+ const node = document.getElementById('dt1');
+ const finder = new Finder(window);
+ finder.setup(':nth-last-of-type()', node);
+ assert.throws(() => finder._matchPseudoClassSelector(leaf, node, {}),
+ DOMException, 'Invalid selector :nth-last-of-type()');
+ });
+
it('should get matched node(s)', () => {
const leaf = {
children: [
@@ -3694,6 +3774,21 @@ describe('Finder', () => {
], 'result');
});
+ it('should throw', () => {
+ const leaf = {
+ children: [],
+ name: 'dir',
+ type: PS_CLASS_SELECTOR
+ };
+ const node = document.createElement('div');
+ const parent = document.getElementById('div0');
+ parent.appendChild(node);
+ const finder = new Finder(window);
+ finder.setup(':dir()', node);
+ assert.throws(() => finder._matchPseudoClassSelector(leaf, node, {}),
+ DOMException, 'Invalid selector :dir()');
+ });
+
it('should get matched node', () => {
const leaf = {
children: [
@@ -3717,6 +3812,21 @@ describe('Finder', () => {
], 'result');
});
+ it('should throw', () => {
+ const leaf = {
+ children: [],
+ name: 'lang',
+ type: PS_CLASS_SELECTOR
+ };
+ const node = document.createElement('div');
+ const parent = document.getElementById('div0');
+ parent.appendChild(node);
+ const finder = new Finder(window);
+ finder.setup(':lang()', node);
+ assert.throws(() => finder._matchPseudoClassSelector(leaf, node, {}),
+ DOMException, 'Invalid selector :lang()');
+ });
+
it('should get matched node', () => {
const leaf = {
children: [
@@ -3740,6 +3850,37 @@ describe('Finder', () => {
], 'result');
});
+ it('should get matched node', () => {
+ const leaf = {
+ children: [
+ {
+ name: 'en',
+ type: IDENT
+ },
+ {
+ type: OPERATOR,
+ value: ','
+ },
+ {
+ name: 'fr',
+ type: IDENT
+ }
+ ],
+ name: 'lang',
+ type: PS_CLASS_SELECTOR
+ };
+ const node = document.createElement('div');
+ node.setAttribute('lang', 'fr');
+ const parent = document.getElementById('div0');
+ parent.appendChild(node);
+ const finder = new Finder(window);
+ finder.setup(':lang(en, fr)', node);
+ const res = finder._matchPseudoClassSelector(leaf, node, {});
+ assert.deepEqual([...res], [
+ node
+ ], 'result');
+ });
+
it('should not match', () => {
const leaf = {
children: [
@@ -9165,6 +9306,76 @@ describe('Finder', () => {
DOMException, 'Invalid selector :host-context');
});
+ it('should throw', () => {
+ const ast = {
+ children: [],
+ name: 'host',
+ type: PS_CLASS_SELECTOR
+ };
+ const html = `
+
+
+ Foo
+
+
+
+ Qux
+
+ `;
+ const container = document.getElementById('div0');
+ container.innerHTML = html;
+ class MyElement extends window.HTMLElement {
+ constructor() {
+ super();
+ const shadowRoot = this.attachShadow({ mode: 'open' });
+ const template = document.getElementById('template');
+ shadowRoot.appendChild(template.content.cloneNode(true));
+ }
+ };
+ window.customElements.define('my-element', MyElement);
+ const host = document.getElementById('baz');
+ const node = host.shadowRoot;
+ const finder = new Finder(window);
+ finder.setup(':host() div', node);
+ assert.throws(() => finder._matchShadowHostPseudoClass(ast, node),
+ DOMException, 'Invalid selector :host()');
+ });
+
+ it('should throw', () => {
+ const ast = {
+ children: [],
+ name: 'host-context',
+ type: PS_CLASS_SELECTOR
+ };
+ const html = `
+
+
+ Foo
+
+
+
+ Qux
+
+ `;
+ const container = document.getElementById('div0');
+ container.innerHTML = html;
+ class MyElement extends window.HTMLElement {
+ constructor() {
+ super();
+ const shadowRoot = this.attachShadow({ mode: 'open' });
+ const template = document.getElementById('template');
+ shadowRoot.appendChild(template.content.cloneNode(true));
+ }
+ };
+ window.customElements.define('my-element', MyElement);
+ const host = document.getElementById('baz');
+ const node = host.shadowRoot;
+ const finder = new Finder(window);
+ finder.setup(':host-context() div', node);
+ assert.throws(() => finder._matchShadowHostPseudoClass(ast, node),
+ DOMException, 'Invalid selector :host-context()');
+ });
+
it('should get matched node', () => {
const ast = {
children: null,
@@ -9845,18 +10056,6 @@ describe('Finder', () => {
});
describe('match selector', () => {
- it('should not match', () => {
- const ast = {
- name: EMPTY,
- type: TYPE_SELECTOR
- };
- const finder = new Finder(window);
- finder.setup(':is()', document);
- const res = finder._matchSelector(ast, document.body);
- assert.strictEqual(res.size, 0, 'size');
- assert.deepEqual([...res], [], 'result');
- });
-
it('should get matched node(s)', () => {
const ast = {
name: 'foo',
diff --git a/test/matcher.test.js b/test/matcher.test.js
index 46e4519..2022223 100644
--- a/test/matcher.test.js
+++ b/test/matcher.test.js
@@ -10,7 +10,7 @@ import { afterEach, beforeEach, describe, it } from 'mocha';
/* test */
import * as matcher from '../src/js/matcher.js';
import {
- ATTR_SELECTOR, EMPTY, IDENT, PS_ELEMENT_SELECTOR, TYPE_SELECTOR
+ ATTR_SELECTOR, IDENT, PS_ELEMENT_SELECTOR, TYPE_SELECTOR
} from '../src/js/constant.js';
const STRING = 'String';
@@ -201,7 +201,24 @@ describe('matcher', () => {
describe('match directionality pseudo-class', () => {
const func = matcher.matchDirectionPseudoClass;
- it('should get matched node', () => {
+ it('should throw', () => {
+ const ast = {};
+ const node = document.createElement('bdo');
+ assert.throws(() => func(ast, node), TypeError,
+ 'Unexpected ast type Undefined');
+ });
+
+ it('should throw', () => {
+ const ast = {
+ name: '',
+ type: IDENT
+ };
+ const node = document.createElement('bdo');
+ assert.throws(() => func(ast, node), TypeError,
+ 'Unexpected ast type (empty String)');
+ });
+
+ it('should match', () => {
const ast = {
name: 'ltr',
type: IDENT
@@ -209,10 +226,10 @@ describe('matcher', () => {
const node = document.createElement('bdo');
node.setAttribute('dir', 'ltr');
const res = func(ast, node);
- assert.deepEqual(res, node, 'result');
+ assert.isTrue(res, 'result');
});
- it('should get matched node', () => {
+ it('should match', () => {
const ast = {
name: 'ltr',
type: IDENT
@@ -222,10 +239,10 @@ describe('matcher', () => {
const parent = document.getElementById('div0');
parent.appendChild(node);
const res = func(ast, node);
- assert.deepEqual(res, node, 'result');
+ assert.isTrue(res, 'result');
});
- it('should get matched node', () => {
+ it('should match', () => {
const ast = {
name: 'rtl',
type: IDENT
@@ -235,20 +252,20 @@ describe('matcher', () => {
const parent = document.getElementById('div0');
parent.appendChild(node);
const res = func(ast, node);
- assert.deepEqual(res, node, 'result');
+ assert.isTrue(res, 'result');
});
- it('should get matched node', () => {
+ it('should match', () => {
const ast = {
name: 'ltr',
type: IDENT
};
const root = document.documentElement;
const res = func(ast, root);
- assert.deepEqual(res, root, 'result');
+ assert.isTrue(res, 'result');
});
- it('should get matched node', () => {
+ it('should match', () => {
const ast = {
name: 'ltr',
type: IDENT
@@ -258,10 +275,10 @@ describe('matcher', () => {
const parent = document.getElementById('div0');
parent.appendChild(node);
const res = func(ast, node);
- assert.deepEqual(res, node, 'result');
+ assert.isTrue(res, 'result');
});
- it('should get matched node', () => {
+ it('should match', () => {
const ast = {
name: 'ltr',
type: IDENT
@@ -272,10 +289,10 @@ describe('matcher', () => {
const parent = document.getElementById('div0');
parent.appendChild(node);
const res = func(ast, node);
- assert.deepEqual(res, node, 'result');
+ assert.isTrue(res, 'result');
});
- it('should not match', () => {
+ it('should match', () => {
const ast = {
name: 'ltr',
type: IDENT
@@ -286,10 +303,10 @@ describe('matcher', () => {
const parent = document.getElementById('div0');
parent.appendChild(node);
const res = func(ast, node);
- assert.deepEqual(res, node, 'result');
+ assert.isTrue(res, 'result');
});
- it('should get matched node', () => {
+ it('should match', () => {
const ast = {
name: 'ltr',
type: IDENT
@@ -299,10 +316,10 @@ describe('matcher', () => {
const parent = document.getElementById('div0');
parent.appendChild(node);
const res = func(ast, node);
- assert.deepEqual(res, node, 'result');
+ assert.isTrue(res, 'result');
});
- it('should get matched node', () => {
+ it('should match', () => {
const ast = {
name: 'ltr',
type: IDENT
@@ -313,10 +330,10 @@ describe('matcher', () => {
const parent = document.getElementById('div0');
parent.appendChild(node);
const res = func(ast, node);
- assert.deepEqual(res, node, 'result');
+ assert.isTrue(res, 'result');
});
- it('should get matched node', () => {
+ it('should match', () => {
const ast = {
name: 'ltr',
type: IDENT
@@ -326,10 +343,10 @@ describe('matcher', () => {
const parent = document.getElementById('div0');
parent.appendChild(node);
const res = func(ast, node);
- assert.deepEqual(res, node, 'result');
+ assert.isTrue(res, 'result');
});
- it('should get matched node', () => {
+ it('should match', () => {
const ast = {
name: 'ltr',
type: IDENT
@@ -339,10 +356,10 @@ describe('matcher', () => {
const parent = document.getElementById('div0');
parent.appendChild(node);
const res = func(ast, node);
- assert.deepEqual(res, node, 'result');
+ assert.isTrue(res, 'result');
});
- it('should get matched node', () => {
+ it('should match', () => {
const ast = {
name: 'rtl',
type: IDENT
@@ -352,7 +369,7 @@ describe('matcher', () => {
const parent = document.getElementById('div0');
parent.appendChild(node);
const res = func(ast, node);
- assert.deepEqual(res, node, 'result');
+ assert.isTrue(res, 'result');
});
it('should not match', () => {
@@ -364,10 +381,10 @@ describe('matcher', () => {
const parent = document.getElementById('div0');
parent.appendChild(node);
const res = func(ast, node);
- assert.isNull(res, 'result');
+ assert.isFalse(res, 'result');
});
- it('should get matched node', () => {
+ it('should match', () => {
const ast = {
name: 'ltr',
type: IDENT
@@ -396,10 +413,10 @@ describe('matcher', () => {
const shadow = document.getElementById('baz');
const node = shadow.shadowRoot.getElementById('foo');
const res = func(ast, node);
- assert.deepEqual(res, node, 'result');
+ assert.isTrue(res, 'result');
});
- it('should get matched node', () => {
+ it('should match', () => {
const ast = {
name: 'ltr',
type: IDENT
@@ -408,10 +425,10 @@ describe('matcher', () => {
const parent = document.getElementById('div0');
parent.appendChild(node);
const res = func(ast, node);
- assert.deepEqual(res, node, 'result');
+ assert.isTrue(res, 'result');
});
- it('should get matched node', () => {
+ it('should match', () => {
const ast = {
name: 'rtl',
type: IDENT
@@ -422,10 +439,10 @@ describe('matcher', () => {
const parent = document.getElementById('div0');
parent.appendChild(node);
const res = func(ast, node);
- assert.deepEqual(res, node, 'result');
+ assert.isTrue(res, 'result');
});
- it('should get matched node', () => {
+ it('should match', () => {
const ast = {
name: 'ltr',
type: IDENT
@@ -435,7 +452,7 @@ describe('matcher', () => {
parent.appendChild(node);
parent.setAttribute('dir', 'ltr');
const res = func(ast, node);
- assert.deepEqual(res, node, 'result');
+ assert.isTrue(res, 'result');
});
});
@@ -444,7 +461,7 @@ describe('matcher', () => {
it('should not match', () => {
const ast = {
- name: EMPTY,
+ name: '',
type: IDENT
};
const node = document.createElement('div');
@@ -452,23 +469,23 @@ describe('matcher', () => {
const parent = document.getElementById('div0');
parent.appendChild(node);
const res = func(ast, node);
- assert.isNull(res, 'result');
+ assert.isFalse(res, 'result');
});
it('should not match', () => {
const ast = {
- name: '',
- type: IDENT
+ type: STRING,
+ value: ''
};
const node = document.createElement('div');
node.setAttribute('lang', '');
const parent = document.getElementById('div0');
parent.appendChild(node);
const res = func(ast, node);
- assert.isNull(res, 'result');
+ assert.isFalse(res, 'result');
});
- it('should get matched node', () => {
+ it('should match', () => {
const ast = {
name: '*',
type: IDENT
@@ -478,10 +495,23 @@ describe('matcher', () => {
const parent = document.getElementById('div0');
parent.appendChild(node);
const res = func(ast, node);
- assert.deepEqual(res, node, 'result');
+ assert.isTrue(res, 'result');
});
- it('should get matched node', () => {
+ it('should match', () => {
+ const ast = {
+ type: STRING,
+ value: '*'
+ };
+ const node = document.createElement('div');
+ node.lang = 'en';
+ const parent = document.getElementById('div0');
+ parent.appendChild(node);
+ const res = func(ast, node);
+ assert.isTrue(res, 'result');
+ });
+
+ it('should match', () => {
const ast = {
name: '*',
type: IDENT
@@ -491,10 +521,10 @@ describe('matcher', () => {
const parent = document.getElementById('div0');
parent.appendChild(node);
const res = func(ast, node);
- assert.deepEqual(res, node, 'result');
+ assert.isTrue(res, 'result');
});
- it('should get matched node', () => {
+ it('should match', () => {
const ast = {
name: '*',
type: IDENT
@@ -503,10 +533,10 @@ describe('matcher', () => {
const parent = document.getElementById('div0');
parent.appendChild(node);
const res = func(ast, node);
- assert.deepEqual(res, node, 'result');
+ assert.isTrue(res, 'result');
});
- it('should get matched node', () => {
+ it('should match', () => {
const ast = {
name: '\\*',
type: IDENT
@@ -516,10 +546,10 @@ describe('matcher', () => {
const parent = document.getElementById('div0');
parent.appendChild(node);
const res = func(ast, node);
- assert.deepEqual(res, node, 'result');
+ assert.isTrue(res, 'result');
});
- it('should get matched node', () => {
+ it('should match', () => {
const ast = {
name: '\\*-FR',
type: IDENT
@@ -529,7 +559,7 @@ describe('matcher', () => {
const parent = document.getElementById('div0');
parent.appendChild(node);
const res = func(ast, node);
- assert.deepEqual(res, node, 'result');
+ assert.isTrue(res, 'result');
});
it('should not match', () => {
@@ -542,7 +572,7 @@ describe('matcher', () => {
const parent = document.getElementById('div0');
parent.appendChild(node);
const res = func(ast, node);
- assert.isNull(res, 'result');
+ assert.isFalse(res, 'result');
});
it('should not match', () => {
@@ -554,10 +584,10 @@ describe('matcher', () => {
const node = document.createElement('div');
frag.appendChild(node);
const res = func(ast, node);
- assert.isNull(res, 'result');
+ assert.isFalse(res, 'result');
});
- it('should get matched node', () => {
+ it('should match', () => {
const ast = {
name: 'en',
type: IDENT
@@ -567,10 +597,10 @@ describe('matcher', () => {
const parent = document.getElementById('div0');
parent.appendChild(node);
const res = func(ast, node);
- assert.deepEqual(res, node, 'result');
+ assert.isTrue(res, 'result');
});
- it('should get matched node', () => {
+ it('should match', () => {
const ast = {
name: 'en',
type: IDENT
@@ -580,10 +610,10 @@ describe('matcher', () => {
const parent = document.getElementById('div0');
parent.appendChild(node);
const res = func(ast, node);
- assert.deepEqual(res, node, 'result');
+ assert.isTrue(res, 'result');
});
- it('should get matched node', () => {
+ it('should match', () => {
const ast = {
name: 'en',
type: IDENT
@@ -592,7 +622,7 @@ describe('matcher', () => {
const parent = document.getElementById('div0');
parent.appendChild(node);
const res = func(ast, node);
- assert.deepEqual(res, node, 'result');
+ assert.isTrue(res, 'result');
});
it('should not match', () => {
@@ -605,7 +635,7 @@ describe('matcher', () => {
const parent = document.getElementById('div0');
parent.appendChild(node);
const res = func(ast, node);
- assert.isNull(res, 'result');
+ assert.isFalse(res, 'result');
});
it('should not match', () => {
@@ -617,10 +647,10 @@ describe('matcher', () => {
const node = document.createElement('div');
frag.appendChild(node);
const res = func(ast, node);
- assert.isNull(res, 'result');
+ assert.isFalse(res, 'result');
});
- it('should get matched node', () => {
+ it('should match', () => {
const ast = {
name: 'de-DE',
type: IDENT
@@ -630,10 +660,10 @@ describe('matcher', () => {
const parent = document.getElementById('div0');
parent.appendChild(node);
const res = func(ast, node);
- assert.deepEqual(res, node, 'result');
+ assert.isTrue(res, 'result');
});
- it('should get matched node', () => {
+ it('should match', () => {
const ast = {
name: 'de-Latn-DE',
type: IDENT
@@ -643,10 +673,10 @@ describe('matcher', () => {
const parent = document.getElementById('div0');
parent.appendChild(node);
const res = func(ast, node);
- assert.deepEqual(res, node, 'result');
+ assert.isTrue(res, 'result');
});
- it('should get matched node', () => {
+ it('should match', () => {
const ast = {
name: 'de-de',
type: IDENT
@@ -656,7 +686,7 @@ describe('matcher', () => {
const parent = document.getElementById('div0');
parent.appendChild(node);
const res = func(ast, node);
- assert.deepEqual(res, node, 'result');
+ assert.isTrue(res, 'result');
});
it('should not match', () => {
@@ -669,7 +699,7 @@ describe('matcher', () => {
const parent = document.getElementById('div0');
parent.appendChild(node);
const res = func(ast, node);
- assert.isNull(res, node, 'result');
+ assert.isFalse(res, node, 'result');
});
});
diff --git a/test/parser.test.js b/test/parser.test.js
index fa72343..90796d0 100644
--- a/test/parser.test.js
+++ b/test/parser.test.js
@@ -4,20 +4,19 @@
/* api */
import { assert } from 'chai';
-import { describe, it, xit } from 'mocha';
+import { describe, it } from 'mocha';
/* test */
import * as parser from '../src/js/parser.js';
/* constants */
import {
- ATTR_SELECTOR, CLASS_SELECTOR, COMBINATOR, IDENT, ID_SELECTOR,
- NTH, PS_CLASS_SELECTOR, PS_ELEMENT_SELECTOR, SELECTOR, TYPE_SELECTOR
+ ATTR_SELECTOR, CLASS_SELECTOR, COMBINATOR, IDENT, ID_SELECTOR, NTH, OPERATOR,
+ PS_CLASS_SELECTOR, PS_ELEMENT_SELECTOR, SELECTOR, STRING, TYPE_SELECTOR
} from '../src/js/constant.js';
const AN_PLUS_B = 'AnPlusB';
const RAW = 'Raw';
const SELECTOR_LIST = 'SelectorList';
-const STRING = 'String';
describe('unescape selector', () => {
const func = parser.unescapeSelector;
@@ -2987,8 +2986,48 @@ describe('create AST from CSS selector', () => {
});
describe('negation pseudo-class', () => {
- it('should throw', () => {
- assert.throws(() => func(':not()'), DOMException);
+ it('should get selector list', () => {
+ const res = func(':not()');
+ assert.deepEqual(res, {
+ children: [
+ {
+ children: [
+ {
+ children: [],
+ loc: null,
+ name: 'not',
+ type: PS_CLASS_SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR_LIST
+ }, 'result');
+ });
+
+ it('should get selector list', () => {
+ const res = func(':not( )');
+ assert.deepEqual(res, {
+ children: [
+ {
+ children: [
+ {
+ children: [],
+ loc: null,
+ name: 'not',
+ type: PS_CLASS_SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR_LIST
+ }, 'result');
});
it('should get selector list', () => {
@@ -3220,25 +3259,7 @@ describe('create AST from CSS selector', () => {
{
children: [
{
- children: [
- {
- children: [
- {
- children: [
- {
- loc: null,
- name: '__EMPTY__',
- type: TYPE_SELECTOR
- }
- ],
- loc: null,
- type: SELECTOR
- }
- ],
- loc: null,
- type: SELECTOR_LIST
- }
- ],
+ children: [],
loc: null,
name: 'is',
type: PS_CLASS_SELECTOR
@@ -3260,25 +3281,7 @@ describe('create AST from CSS selector', () => {
{
children: [
{
- children: [
- {
- children: [
- {
- children: [
- {
- loc: null,
- name: '__EMPTY__',
- type: TYPE_SELECTOR
- }
- ],
- loc: null,
- type: SELECTOR
- }
- ],
- loc: null,
- type: SELECTOR_LIST
- }
- ],
+ children: [],
loc: null,
name: 'is',
type: PS_CLASS_SELECTOR
@@ -3306,25 +3309,7 @@ describe('create AST from CSS selector', () => {
{
children: [
{
- children: [
- {
- children: [
- {
- children: [
- {
- loc: null,
- name: '__EMPTY__',
- type: TYPE_SELECTOR
- }
- ],
- loc: null,
- type: SELECTOR
- }
- ],
- loc: null,
- type: SELECTOR_LIST
- }
- ],
+ children: [],
loc: null,
name: 'is',
type: PS_CLASS_SELECTOR
@@ -3511,25 +3496,7 @@ describe('create AST from CSS selector', () => {
{
children: [
{
- children: [
- {
- children: [
- {
- children: [
- {
- loc: null,
- name: '__EMPTY__',
- type: TYPE_SELECTOR
- }
- ],
- loc: null,
- type: SELECTOR
- }
- ],
- loc: null,
- type: SELECTOR_LIST
- }
- ],
+ children: [],
loc: null,
name: 'where',
type: PS_CLASS_SELECTOR
@@ -3551,25 +3518,7 @@ describe('create AST from CSS selector', () => {
{
children: [
{
- children: [
- {
- children: [
- {
- children: [
- {
- loc: null,
- name: '__EMPTY__',
- type: TYPE_SELECTOR
- }
- ],
- loc: null,
- type: SELECTOR
- }
- ],
- loc: null,
- type: SELECTOR_LIST
- }
- ],
+ children: [],
loc: null,
name: 'where',
type: PS_CLASS_SELECTOR
@@ -3597,25 +3546,7 @@ describe('create AST from CSS selector', () => {
{
children: [
{
- children: [
- {
- children: [
- {
- children: [
- {
- loc: null,
- name: '__EMPTY__',
- type: TYPE_SELECTOR
- }
- ],
- loc: null,
- type: SELECTOR
- }
- ],
- loc: null,
- type: SELECTOR_LIST
- }
- ],
+ children: [],
loc: null,
name: 'where',
type: PS_CLASS_SELECTOR
@@ -3795,8 +3726,48 @@ describe('create AST from CSS selector', () => {
});
describe('relational pseudo-class', () => {
- it('should throw', () => {
- assert.throws(() => func(':has()'), DOMException);
+ it('should get selector list', () => {
+ const res = func(':has()');
+ assert.deepEqual(res, {
+ children: [
+ {
+ children: [
+ {
+ children: [],
+ loc: null,
+ name: 'has',
+ type: PS_CLASS_SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR_LIST
+ }, 'result');
+ });
+
+ it('should get selector list', () => {
+ const res = func(':has( )');
+ assert.deepEqual(res, {
+ children: [
+ {
+ children: [
+ {
+ children: [],
+ loc: null,
+ name: 'has',
+ type: PS_CLASS_SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR_LIST
+ }, 'result');
});
it('should get selector list', () => {
@@ -4579,11 +4550,51 @@ describe('create AST from CSS selector', () => {
describe('An+B notation pseudo-class', () => {
it('should throw', () => {
- assert.throws(() => func(':nth-child()'), DOMException);
+ assert.throws(() => func(':nth-child(foo)'), DOMException);
});
- it('should throw', () => {
- assert.throws(() => func(':nth-child(foo)'), DOMException);
+ it('should get selector list', () => {
+ const res = func(':nth-child()');
+ assert.deepEqual(res, {
+ children: [
+ {
+ children: [
+ {
+ children: [],
+ loc: null,
+ name: 'nth-child',
+ type: PS_CLASS_SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR_LIST
+ }, 'result');
+ });
+
+ it('should get selector list', () => {
+ const res = func(':nth-child( )');
+ assert.deepEqual(res, {
+ children: [
+ {
+ children: [
+ {
+ children: [],
+ loc: null,
+ name: 'nth-child',
+ type: PS_CLASS_SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR_LIST
+ }, 'result');
});
it('should get selector list', () => {
@@ -4917,33 +4928,18 @@ describe('create AST from CSS selector', () => {
}, 'result');
});
- it('should throw', () => {
- assert.throws(() => func(':nth-last-child()'), DOMException);
- });
-
it('should throw', () => {
assert.throws(() => func(':nth-last-child(foo)'), DOMException);
});
it('should get selector list', () => {
- const res = func(':nth-last-child(even)');
+ const res = func(':nth-last-child()');
assert.deepEqual(res, {
children: [
{
children: [
{
- children: [
- {
- loc: null,
- nth: {
- loc: null,
- name: 'even',
- type: IDENT
- },
- selector: null,
- type: NTH
- }
- ],
+ children: [],
loc: null,
name: 'nth-last-child',
type: PS_CLASS_SELECTOR
@@ -4959,24 +4955,79 @@ describe('create AST from CSS selector', () => {
});
it('should get selector list', () => {
- const res = func(':nth-last-child(odd)');
+ const res = func(':nth-last-child( )');
assert.deepEqual(res, {
children: [
{
children: [
{
- children: [
- {
- loc: null,
- nth: {
- loc: null,
- name: 'odd',
- type: IDENT
- },
- selector: null,
- type: NTH
- }
- ],
+ children: [],
+ loc: null,
+ name: 'nth-last-child',
+ type: PS_CLASS_SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR_LIST
+ }, 'result');
+ });
+
+ it('should get selector list', () => {
+ const res = func(':nth-last-child(even)');
+ assert.deepEqual(res, {
+ children: [
+ {
+ children: [
+ {
+ children: [
+ {
+ loc: null,
+ nth: {
+ loc: null,
+ name: 'even',
+ type: IDENT
+ },
+ selector: null,
+ type: NTH
+ }
+ ],
+ loc: null,
+ name: 'nth-last-child',
+ type: PS_CLASS_SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR_LIST
+ }, 'result');
+ });
+
+ it('should get selector list', () => {
+ const res = func(':nth-last-child(odd)');
+ assert.deepEqual(res, {
+ children: [
+ {
+ children: [
+ {
+ children: [
+ {
+ loc: null,
+ nth: {
+ loc: null,
+ name: 'odd',
+ type: IDENT
+ },
+ selector: null,
+ type: NTH
+ }
+ ],
loc: null,
name: 'nth-last-child',
type: PS_CLASS_SELECTOR
@@ -5114,11 +5165,51 @@ describe('create AST from CSS selector', () => {
});
it('should throw', () => {
- assert.throws(() => func(':nth-of-type()'), DOMException);
+ assert.throws(() => func(':nth-of-type(foo)'), DOMException);
});
- it('should throw', () => {
- assert.throws(() => func(':nth-of-type(foo)'), DOMException);
+ it('should get selector list', () => {
+ const res = func(':nth-of-type()');
+ assert.deepEqual(res, {
+ children: [
+ {
+ children: [
+ {
+ children: [],
+ loc: null,
+ name: 'nth-of-type',
+ type: PS_CLASS_SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR_LIST
+ }, 'result');
+ });
+
+ it('should get selector list', () => {
+ const res = func(':nth-of-type( )');
+ assert.deepEqual(res, {
+ children: [
+ {
+ children: [
+ {
+ children: [],
+ loc: null,
+ name: 'nth-of-type',
+ type: PS_CLASS_SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR_LIST
+ }, 'result');
});
it('should get selector list', () => {
@@ -5260,11 +5351,51 @@ describe('create AST from CSS selector', () => {
});
it('should throw', () => {
- assert.throws(() => func(':nth-last-of-type()'), DOMException);
+ assert.throws(() => func(':nth-last-of-type(foo)'), DOMException);
});
- it('should throw', () => {
- assert.throws(() => func(':nth-last-of-type(foo)'), DOMException);
+ it('should get selector list', () => {
+ const res = func(':nth-last-of-type()');
+ assert.deepEqual(res, {
+ children: [
+ {
+ children: [
+ {
+ children: [],
+ loc: null,
+ name: 'nth-last-of-type',
+ type: PS_CLASS_SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR_LIST
+ }, 'result');
+ });
+
+ it('should get selector list', () => {
+ const res = func(':nth-last-of-type( )');
+ assert.deepEqual(res, {
+ children: [
+ {
+ children: [
+ {
+ children: [],
+ loc: null,
+ name: 'nth-last-of-type',
+ type: PS_CLASS_SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR_LIST
+ }, 'result');
});
it('should get selector list', () => {
@@ -5464,7 +5595,51 @@ describe('create AST from CSS selector', () => {
});
});
- describe('linguistic pseudo-class', () => {
+ describe('directionality pseudo-class', () => {
+ it('should get selector list', () => {
+ const res = func(':dir()');
+ assert.deepEqual(res, {
+ children: [
+ {
+ children: [
+ {
+ children: [],
+ loc: null,
+ name: 'dir',
+ type: PS_CLASS_SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR_LIST
+ }, 'result');
+ });
+
+ it('should get selector list', () => {
+ const res = func(':dir( )');
+ assert.deepEqual(res, {
+ children: [
+ {
+ children: [
+ {
+ children: [],
+ loc: null,
+ name: 'dir',
+ type: PS_CLASS_SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR_LIST
+ }, 'result');
+ });
+
it('should get selector list', () => {
const res = func(':dir(foo)');
assert.deepEqual(res, {
@@ -5522,7 +5697,7 @@ describe('create AST from CSS selector', () => {
});
it('should get selector list', () => {
- const res = func(':dir(rtr)');
+ const res = func(':dir(rtl)');
assert.deepEqual(res, {
children: [
{
@@ -5531,7 +5706,7 @@ describe('create AST from CSS selector', () => {
children: [
{
loc: null,
- name: 'rtr',
+ name: 'rtl',
type: IDENT
}
],
@@ -5577,6 +5752,57 @@ describe('create AST from CSS selector', () => {
}, 'result');
});
+ it('should throw', () => {
+ assert.throws(() => func(':dir(ltr,rtl)'), DOMException,
+ '")" is expected');
+ });
+ });
+
+ describe('linguistic pseudo-class', () => {
+ it('should get selector list', () => {
+ const res = func(':lang()');
+ assert.deepEqual(res, {
+ children: [
+ {
+ children: [
+ {
+ children: [],
+ loc: null,
+ name: 'lang',
+ type: PS_CLASS_SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR_LIST
+ }, 'result');
+ });
+
+ it('should get selector list', () => {
+ const res = func(':lang( )');
+ assert.deepEqual(res, {
+ children: [
+ {
+ children: [
+ {
+ children: [],
+ loc: null,
+ name: 'lang',
+ type: PS_CLASS_SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR_LIST
+ }, 'result');
+ });
+
it('should get selector list', () => {
const res = func(':lang(de)');
assert.deepEqual(res, {
@@ -5633,8 +5859,7 @@ describe('create AST from CSS selector', () => {
}, 'result');
});
- // FIXME: expect to parse
- xit('should get selector list', () => {
+ it('should get selector list', () => {
const res = func(':lang(de, fr)');
assert.deepEqual(res, {
children: [
@@ -5647,6 +5872,11 @@ describe('create AST from CSS selector', () => {
name: 'de',
type: IDENT
},
+ {
+ loc: null,
+ type: OPERATOR,
+ value: ','
+ },
{
loc: null,
name: 'fr',
@@ -5705,8 +5935,8 @@ describe('create AST from CSS selector', () => {
children: [
{
loc: null,
- name: '\\*',
- type: IDENT
+ type: STRING,
+ value: '*'
}
],
loc: null,
@@ -5733,8 +5963,46 @@ describe('create AST from CSS selector', () => {
children: [
{
loc: null,
- name: 'en-US',
- type: IDENT
+ type: STRING,
+ value: 'en-US'
+ }
+ ],
+ loc: null,
+ name: 'lang',
+ type: PS_CLASS_SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR_LIST
+ }, 'result');
+ });
+
+ it('should get selector list', () => {
+ const res = func(':lang("de", "fr")');
+ assert.deepEqual(res, {
+ children: [
+ {
+ children: [
+ {
+ children: [
+ {
+ loc: null,
+ type: STRING,
+ value: 'de'
+ },
+ {
+ loc: null,
+ type: OPERATOR,
+ value: ','
+ },
+ {
+ loc: null,
+ type: STRING,
+ value: 'fr'
}
],
loc: null,
@@ -5761,8 +6029,8 @@ describe('create AST from CSS selector', () => {
children: [
{
loc: null,
- name: '\\*-Latn',
- type: IDENT
+ type: STRING,
+ value: '*-Latn'
}
],
loc: null,
@@ -5789,8 +6057,40 @@ describe('create AST from CSS selector', () => {
children: [
{
loc: null,
- name: '__EMPTY__',
- type: IDENT
+ type: STRING,
+ value: ''
+ }
+ ],
+ loc: null,
+ name: 'lang',
+ type: PS_CLASS_SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR_LIST
+ }, 'result');
+ });
+
+ it('should throw', () => {
+ assert.throws(() => func(':lang(0)'), DOMException);
+ });
+
+ it('should get selector list', () => {
+ const res = func(':lang("0")');
+ assert.deepEqual(res, {
+ children: [
+ {
+ children: [
+ {
+ children: [
+ {
+ loc: null,
+ type: STRING,
+ value: '0'
}
],
loc: null,
@@ -5902,8 +6202,92 @@ describe('create AST from CSS selector', () => {
}, 'result');
});
- it('should throw', () => {
- assert.throws(() => func(':host()'), DOMException);
+ it('should get selector list', () => {
+ const res = func(':host()');
+ assert.deepEqual(res, {
+ children: [
+ {
+ children: [
+ {
+ children: [],
+ loc: null,
+ name: 'host',
+ type: PS_CLASS_SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR_LIST
+ }, 'result');
+ });
+
+ it('should get selector list', () => {
+ const res = func(':host( )');
+ assert.deepEqual(res, {
+ children: [
+ {
+ children: [
+ {
+ children: [],
+ loc: null,
+ name: 'host',
+ type: PS_CLASS_SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR_LIST
+ }, 'result');
+ });
+
+ it('should get selector list', () => {
+ const res = func(':host-context()');
+ assert.deepEqual(res, {
+ children: [
+ {
+ children: [
+ {
+ children: [],
+ loc: null,
+ name: 'host-context',
+ type: PS_CLASS_SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR_LIST
+ }, 'result');
+ });
+
+ it('should get selector list', () => {
+ const res = func(':host-context( )');
+ assert.deepEqual(res, {
+ children: [
+ {
+ children: [
+ {
+ children: [],
+ loc: null,
+ name: 'host-context',
+ type: PS_CLASS_SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR
+ }
+ ],
+ loc: null,
+ type: SELECTOR_LIST
+ }, 'result');
});
it('should throw', () => {
diff --git a/test/wpt.test.js b/test/wpt.test.js
index 0b57bfa..e4f22fe 100644
--- a/test/wpt.test.js
+++ b/test/wpt.test.js
@@ -1007,8 +1007,7 @@ describe('local wpt test cases', () => {
assert.isTrue(res, 'result');
});
- // FIXME: not yet supported
- xit('lang-011.html, should match', () => {
+ it('lang-011.html, should match', () => {
const html =
'
This should be green
';
document.body.innerHTML = html;
@@ -1017,17 +1016,16 @@ describe('local wpt test cases', () => {
assert.isTrue(res, 'result');
});
- // FIXME: not yet supported
- xit('lang-012.html, should match', () => {
+ it('lang-012.html, should match', () => {
const html =
'This should be green
';
document.body.innerHTML = html;
const node = document.getElementById('target');
- const res = node.matches(':lang(fr, nl, de)');
+ const res = node.matches(':lang(de, nl, fr)');
assert.isTrue(res, 'result');
});
- // FIXME: not yet supported
+ // FIXME: throws which is expected, need to fix test
xit('lang-013.html, should not match', () => {
const html =
'This should be green
';
@@ -1037,7 +1035,7 @@ describe('local wpt test cases', () => {
assert.isFalse(res, 'result');
});
- // FIXME: CSSTree throws
+ // FIXME: throws which is expected, need to fix test
xit('lang-014.html, should not match', () => {
const html =
'This should be green
';