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 = ` + + + 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 = ` + + + 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
';