diff options
| -rw-r--r-- | test/data/html/dom-text-scanner.html | 2 | ||||
| -rw-r--r-- | test/document-util.test.js | 291 | ||||
| -rw-r--r-- | test/dom-text-scanner.test.js | 12 | ||||
| -rw-r--r-- | test/fixtures/dom-test.js | 14 | ||||
| -rw-r--r-- | test/options-util.test.js | 27 | ||||
| -rw-r--r-- | test/profile-conditions-util.test.js | 14 | ||||
| -rw-r--r-- | test/text-source-map.test.js | 26 | 
7 files changed, 206 insertions, 180 deletions
| diff --git a/test/data/html/dom-text-scanner.html b/test/data/html/dom-text-scanner.html index ff4d0493..1f537d4e 100644 --- a/test/data/html/dom-text-scanner.html +++ b/test/data/html/dom-text-scanner.html @@ -392,4 +392,4 @@      </test-case>  </body> -</html>
\ No newline at end of file +</html> diff --git a/test/document-util.test.js b/test/document-util.test.js index cc8db706..f332d384 100644 --- a/test/document-util.test.js +++ b/test/document-util.test.js @@ -18,12 +18,12 @@  import {fileURLToPath} from 'node:url';  import path from 'path'; -import {describe, expect} from 'vitest'; +import {afterAll, describe, expect, test} from 'vitest';  import {DocumentUtil} from '../ext/js/dom/document-util.js';  import {DOMTextScanner} from '../ext/js/dom/dom-text-scanner.js';  import {TextSourceElement} from '../ext/js/dom/text-source-element.js';  import {TextSourceRange} from '../ext/js/dom/text-source-range.js'; -import {createDomTest} from './fixtures/dom-test.js'; +import {setupDomTest} from './fixtures/dom-test.js';  import {parseJson} from '../dev/json.js';  const dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -110,165 +110,176 @@ function findImposterElement(document) {      return document.querySelector('div[style*="2147483646"]>*');  } -const test = createDomTest(path.join(dirname, 'data/html/document-util.html')); +const documentUtilTestEnv = await setupDomTest(path.join(dirname, 'data/html/document-util.html')); -describe('DocumentUtil', () => { -    test('Text scanning functions', ({window}) => { -        const {document} = window; -        for (const testElement of /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll('test-case[data-test-type=scan]'))) { -            // Get test parameters -            /** @type {import('test/document-util').DocumentUtilTestData} */ -            const { -                elementFromPointSelector, -                caretRangeFromPointSelector, -                startNodeSelector, -                startOffset, -                endNodeSelector, -                endOffset, -                resultType, -                sentenceScanExtent, -                sentence, -                hasImposter, -                terminateAtNewlines -            } = parseJson(/** @type {string} */ (testElement.dataset.testData)); +describe('Document utility tests', () => { +    const {window, teardown} = documentUtilTestEnv; +    afterAll(() => teardown(global)); -            const elementFromPointValue = querySelectorChildOrSelf(testElement, elementFromPointSelector); -            const caretRangeFromPointValue = querySelectorChildOrSelf(testElement, caretRangeFromPointSelector); -            const startNode = getChildTextNodeOrSelf(window, querySelectorChildOrSelf(testElement, startNodeSelector)); -            const endNode = getChildTextNodeOrSelf(window, querySelectorChildOrSelf(testElement, endNodeSelector)); +    describe('DocumentUtil', () => { +        describe('Text scanning functions', () => { +            let testIndex = 0; +            const {document} = window; +            for (const testElement of /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll('test-case[data-test-type=scan]'))) { +                test(`test-case-${testIndex++}`, () => { +                    // Get test parameters +                    /** @type {import('test/document-util').DocumentUtilTestData} */ +                    const { +                        elementFromPointSelector, +                        caretRangeFromPointSelector, +                        startNodeSelector, +                        startOffset, +                        endNodeSelector, +                        endOffset, +                        resultType, +                        sentenceScanExtent, +                        sentence, +                        hasImposter, +                        terminateAtNewlines +                    } = parseJson(/** @type {string} */ (testElement.dataset.testData)); -            // Defaults to true -            const terminateAtNewlines2 = typeof terminateAtNewlines === 'boolean' ? terminateAtNewlines : true; +                    const elementFromPointValue = querySelectorChildOrSelf(testElement, elementFromPointSelector); +                    const caretRangeFromPointValue = querySelectorChildOrSelf(testElement, caretRangeFromPointSelector); +                    const startNode = getChildTextNodeOrSelf(window, querySelectorChildOrSelf(testElement, startNodeSelector)); +                    const endNode = getChildTextNodeOrSelf(window, querySelectorChildOrSelf(testElement, endNodeSelector)); -            expect(elementFromPointValue).not.toStrictEqual(null); -            expect(caretRangeFromPointValue).not.toStrictEqual(null); -            expect(startNode).not.toStrictEqual(null); -            expect(endNode).not.toStrictEqual(null); +                    // Defaults to true +                    const terminateAtNewlines2 = typeof terminateAtNewlines === 'boolean' ? terminateAtNewlines : true; -            // Setup functions -            document.elementFromPoint = () => elementFromPointValue; +                    expect(elementFromPointValue).not.toStrictEqual(null); +                    expect(caretRangeFromPointValue).not.toStrictEqual(null); +                    expect(startNode).not.toStrictEqual(null); +                    expect(endNode).not.toStrictEqual(null); -            document.caretRangeFromPoint = (x, y) => { -                const imposter = getChildTextNodeOrSelf(window, findImposterElement(document)); -                expect(!!imposter).toStrictEqual(!!hasImposter); +                    // Setup functions +                    document.elementFromPoint = () => elementFromPointValue; -                const range = document.createRange(); -                range.setStart(/** @type {Node} */ (imposter ? imposter : startNode), startOffset); -                range.setEnd(/** @type {Node} */ (imposter ? imposter : startNode), endOffset); +                    document.caretRangeFromPoint = (x, y) => { +                        const imposter = getChildTextNodeOrSelf(window, findImposterElement(document)); +                        expect(!!imposter).toStrictEqual(!!hasImposter); -                // Override getClientRects to return a rect guaranteed to contain (x, y) -                range.getClientRects = () => { -                    /** @type {import('test/document-types').PseudoDOMRectList} */ -                    const domRectList = Object.assign( -                        [new DOMRect(x - 1, y - 1, 2, 2)], -                        { -                            /** -                             * @this {DOMRect[]} -                             * @param {number} index -                             * @returns {DOMRect} -                             */ -                            item: function item(index) { return this[index]; } -                        } -                    ); -                    return domRectList; -                }; -                return range; -            }; +                        const range = document.createRange(); +                        range.setStart(/** @type {Node} */ (imposter ? imposter : startNode), startOffset); +                        range.setEnd(/** @type {Node} */ (imposter ? imposter : startNode), endOffset); -            // Test docRangeFromPoint -            const source = DocumentUtil.getRangeFromPoint(0, 0, { -                deepContentScan: false, -                normalizeCssZoom: true -            }); -            switch (resultType) { -                case 'TextSourceRange': -                    expect(getPrototypeOfOrNull(source)).toStrictEqual(TextSourceRange.prototype); -                    break; -                case 'TextSourceElement': -                    expect(getPrototypeOfOrNull(source)).toStrictEqual(TextSourceElement.prototype); -                    break; -                case 'null': -                    expect(source).toStrictEqual(null); -                    break; -                default: -                    expect.unreachable(); -                    break; -            } -            if (source === null) { continue; } +                        // Override getClientRects to return a rect guaranteed to contain (x, y) +                        range.getClientRects = () => { +                            /** @type {import('test/document-types').PseudoDOMRectList} */ +                            const domRectList = Object.assign( +                                [new DOMRect(x - 1, y - 1, 2, 2)], +                                { +                                    /** +                                     * @this {DOMRect[]} +                                     * @param {number} index +                                     * @returns {DOMRect} +                                     */ +                                    item: function item(index) { return this[index]; } +                                } +                            ); +                            return domRectList; +                        }; +                        return range; +                    }; -            // Sentence info -            const terminatorString = '…。..??!!'; -            const terminatorMap = new Map(); -            for (const char of terminatorString) { -                terminatorMap.set(char, [false, true]); -            } -            const quoteArray = [['「', '」'], ['『', '』'], ['\'', '\''], ['"', '"']]; -            const forwardQuoteMap = new Map(); -            const backwardQuoteMap = new Map(); -            for (const [char1, char2] of quoteArray) { -                forwardQuoteMap.set(char1, [char2, false]); -                backwardQuoteMap.set(char2, [char1, false]); -            } +                    // Test docRangeFromPoint +                    const source = DocumentUtil.getRangeFromPoint(0, 0, { +                        deepContentScan: false, +                        normalizeCssZoom: true +                    }); +                    switch (resultType) { +                        case 'TextSourceRange': +                            expect(getPrototypeOfOrNull(source)).toStrictEqual(TextSourceRange.prototype); +                            break; +                        case 'TextSourceElement': +                            expect(getPrototypeOfOrNull(source)).toStrictEqual(TextSourceElement.prototype); +                            break; +                        case 'null': +                            expect(source).toStrictEqual(null); +                            break; +                        default: +                            expect.unreachable(); +                            break; +                    } +                    if (source === null) { return; } + +                    // Sentence info +                    const terminatorString = '…。..??!!'; +                    const terminatorMap = new Map(); +                    for (const char of terminatorString) { +                        terminatorMap.set(char, [false, true]); +                    } +                    const quoteArray = [['「', '」'], ['『', '』'], ['\'', '\''], ['"', '"']]; +                    const forwardQuoteMap = new Map(); +                    const backwardQuoteMap = new Map(); +                    for (const [char1, char2] of quoteArray) { +                        forwardQuoteMap.set(char1, [char2, false]); +                        backwardQuoteMap.set(char2, [char1, false]); +                    } -            // Test docSentenceExtract -            const sentenceActual = DocumentUtil.extractSentence( -                source, -                false, -                sentenceScanExtent, -                terminateAtNewlines2, -                terminatorMap, -                forwardQuoteMap, -                backwardQuoteMap -            ).text; -            expect(sentenceActual).toStrictEqual(sentence); +                    // Test docSentenceExtract +                    const sentenceActual = DocumentUtil.extractSentence( +                        source, +                        false, +                        sentenceScanExtent, +                        terminateAtNewlines2, +                        terminatorMap, +                        forwardQuoteMap, +                        backwardQuoteMap +                    ).text; +                    expect(sentenceActual).toStrictEqual(sentence); -            // Clean -            source.cleanup(); -        } +                    // Clean +                    source.cleanup(); +                }); +            } +        });      }); -}); -describe('DOMTextScanner', () => { -    test('Seek functions', async ({window}) => { -        const {document} = window; -        for (const testElement of /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll('test-case[data-test-type=text-source-range-seek]'))) { -            // Get test parameters -            /** @type {import('test/document-util').DOMTextScannerTestData} */ -            const { -                seekNodeSelector, -                seekNodeIsText, -                seekOffset, -                seekLength, -                seekDirection, -                expectedResultNodeSelector, -                expectedResultNodeIsText, -                expectedResultOffset, -                expectedResultContent -            } = parseJson(/** @type {string} */ (testElement.dataset.testData)); +    describe('DOMTextScanner', () => { +        describe('Seek functions', () => { +            let testIndex = 0; +            const {document} = window; +            for (const testElement of /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll('test-case[data-test-type=text-source-range-seek]'))) { +                test(`test-case-${testIndex++}`, () => { +                    // Get test parameters +                    /** @type {import('test/document-util').DOMTextScannerTestData} */ +                    const { +                        seekNodeSelector, +                        seekNodeIsText, +                        seekOffset, +                        seekLength, +                        seekDirection, +                        expectedResultNodeSelector, +                        expectedResultNodeIsText, +                        expectedResultOffset, +                        expectedResultContent +                    } = parseJson(/** @type {string} */ (testElement.dataset.testData)); -            /** @type {?Node} */ -            let seekNode = testElement.querySelector(/** @type {string} */ (seekNodeSelector)); -            if (seekNodeIsText && seekNode !== null) { -                seekNode = seekNode.firstChild; -            } +                    /** @type {?Node} */ +                    let seekNode = testElement.querySelector(/** @type {string} */ (seekNodeSelector)); +                    if (seekNodeIsText && seekNode !== null) { +                        seekNode = seekNode.firstChild; +                    } -            const expectedResultContent2 = expectedResultContent.join('\n'); +                    const expectedResultContent2 = expectedResultContent.join('\n'); -            /** @type {?Node} */ -            let expectedResultNode = testElement.querySelector(/** @type {string} */ (expectedResultNodeSelector)); -            if (expectedResultNodeIsText && expectedResultNode !== null) { -                expectedResultNode = expectedResultNode.firstChild; -            } +                    /** @type {?Node} */ +                    let expectedResultNode = testElement.querySelector(/** @type {string} */ (expectedResultNodeSelector)); +                    if (expectedResultNodeIsText && expectedResultNode !== null) { +                        expectedResultNode = expectedResultNode.firstChild; +                    } -            const {node, offset, content} = ( +                    const {node, offset, content} = (                  seekDirection === 'forward' ?                  new DOMTextScanner(/** @type {Node} */ (seekNode), seekOffset, true, false).seek(seekLength) :                  new DOMTextScanner(/** @type {Node} */ (seekNode), seekOffset, true, false).seek(-seekLength) -            ); +                    ); -            expect(node).toStrictEqual(expectedResultNode); -            expect(offset).toStrictEqual(expectedResultOffset); -            expect(content).toStrictEqual(expectedResultContent2); -        } +                    expect(node).toStrictEqual(expectedResultNode); +                    expect(offset).toStrictEqual(expectedResultOffset); +                    expect(content).toStrictEqual(expectedResultContent2); +                }); +            } +        });      });  }); diff --git a/test/dom-text-scanner.test.js b/test/dom-text-scanner.test.js index da38d24c..12e3193a 100644 --- a/test/dom-text-scanner.test.js +++ b/test/dom-text-scanner.test.js @@ -18,10 +18,10 @@  import {fileURLToPath} from 'node:url';  import path from 'path'; -import {describe, expect} from 'vitest'; +import {afterAll, describe, expect, test} from 'vitest';  import {parseJson} from '../dev/json.js';  import {DOMTextScanner} from '../ext/js/dom/dom-text-scanner.js'; -import {createDomTest} from './fixtures/dom-test.js'; +import {setupDomTest} from './fixtures/dom-test.js';  const dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -101,11 +101,13 @@ function createAbsoluteGetComputedStyle(window) {      };  } - -const test = createDomTest(path.join(dirname, 'data/html/dom-text-scanner.html')); +const domTestEnv = await setupDomTest(path.join(dirname, 'data/html/dom-text-scanner.html'));  describe('DOMTextScanner', () => { -    test('Seek tests', ({window}) => { +    const {window, teardown} = domTestEnv; +    afterAll(() => teardown(global)); + +    test('Seek tests', () => {          const {document} = window;          window.getComputedStyle = createAbsoluteGetComputedStyle(window); diff --git a/test/fixtures/dom-test.js b/test/fixtures/dom-test.js index 8cfe80a9..e0e7d647 100644 --- a/test/fixtures/dom-test.js +++ b/test/fixtures/dom-test.js @@ -36,6 +36,20 @@ function prepareWindow(window) {  }  /** + * + * @param {string} [htmlFilePath] + * @returns {Promise<{window: import('jsdom').DOMWindow; teardown: (global: unknown) => import('vitest').Awaitable<void>}>} + */ +export async function setupDomTest(htmlFilePath) { +    const html = typeof htmlFilePath === 'string' ? fs.readFileSync(htmlFilePath, {encoding: 'utf8'}) : '<!DOCTYPE html>'; +    const env = builtinEnvironments.jsdom; +    const {teardown} = await env.setup(global, {jsdom: {html}}); +    const window = /** @type {import('jsdom').DOMWindow} */ (/** @type {unknown} */ (global.window)); +    prepareWindow(window); +    return {window, teardown}; +} + +/**   * @param {string} [htmlFilePath]   * @returns {import('vitest').TestAPI<{window: import('jsdom').DOMWindow}>}   */ diff --git a/test/options-util.test.js b/test/options-util.test.js index 41743b9f..a34cc93a 100644 --- a/test/options-util.test.js +++ b/test/options-util.test.js @@ -21,7 +21,7 @@  import fs from 'fs';  import {fileURLToPath} from 'node:url';  import path from 'path'; -import {expect, test, vi} from 'vitest'; +import {expect, test, describe, vi} from 'vitest';  import {OptionsUtil} from '../ext/js/data/options-util.js';  import {TemplatePatcher} from '../ext/js/templates/template-patcher.js';  import {chrome, fetch} from './mocks/common.js'; @@ -628,7 +628,7 @@ async function testUpdate() {  /** */  async function testDefault() { -    test('Default', async () => { +    describe('Default', () => {          /** @type {((options: import('options-util').IntermediateOptions) => void)[]} */          const data = [              (options) => options, @@ -640,27 +640,22 @@ async function testDefault() {              }          ]; -        const optionsUtil = new OptionsUtil(); -        await optionsUtil.prepare(); +        test.each(data)('default-test-%#', async (modify) => { +            const optionsUtil = new OptionsUtil(); +            await optionsUtil.prepare(); -        for (const modify of data) {              const options = optionsUtil.getDefault(); -              const optionsModified = structuredClone(options);              modify(optionsModified); -              const optionsUpdated = await optionsUtil.update(structuredClone(optionsModified));              expect(structuredClone(optionsUpdated)).toStrictEqual(structuredClone(options)); -        } +        });      });  }  /** */  async function testFieldTemplatesUpdate() { -    test('FieldTemplatesUpdate', async () => { -        const optionsUtil = new OptionsUtil(); -        await optionsUtil.prepare(); - +    describe('FieldTemplatesUpdate', () => {          const templatePatcher = new TemplatePatcher();          /**           * @param {string} fileName @@ -1577,7 +1572,11 @@ async function testFieldTemplatesUpdate() {          ];          const updatesPattern = /<<<UPDATE-ADDITIONS>>>/g; -        for (const {old, expected, oldVersion, newVersion} of data) { + +        test.each(data)('field-templates-update-test-%#', async ({old, expected, oldVersion, newVersion}) => { +            const optionsUtil = new OptionsUtil(); +            await optionsUtil.prepare(); +              const options = /** @type {import('core').SafeAny} */ (createOptionsTestData1());              options.profiles[0].options.anki.fieldTemplates = old;              options.version = oldVersion; @@ -1587,7 +1586,7 @@ async function testFieldTemplatesUpdate() {              const optionsUpdated = structuredClone(await optionsUtil.update(options, newVersion));              const fieldTemplatesActual = optionsUpdated.profiles[0].options.anki.fieldTemplates;              expect(fieldTemplatesActual).toStrictEqual(expected2); -        } +        });      });  } diff --git a/test/profile-conditions-util.test.js b/test/profile-conditions-util.test.js index d5c8f8d2..a217eb7a 100644 --- a/test/profile-conditions-util.test.js +++ b/test/profile-conditions-util.test.js @@ -18,12 +18,12 @@  /* eslint-disable no-multi-spaces */ -import {expect, test} from 'vitest'; +import {describe, expect, test} from 'vitest';  import {ProfileConditionsUtil} from '../ext/js/background/profile-conditions-util.js';  /** */  function testNormalizeContext() { -    test('NormalizeContext', () => { +    describe('NormalizeContext', () => {          /** @type {{context: import('settings').OptionsContext, expected: import('profile-conditions-util').NormalizedOptionsContext}[]} */          const data = [              // Empty @@ -51,17 +51,17 @@ function testNormalizeContext() {              }          ]; -        for (const {context, expected} of data) { +        test.each(data)('normalize-context-test-%#', ({context, expected}) => {              const profileConditionsUtil = new ProfileConditionsUtil();              const actual = profileConditionsUtil.normalizeContext(context);              expect(actual).toStrictEqual(expected); -        } +        });      });  }  /** */  function testSchemas() { -    test('Schemas', () => { +    describe('Schemas', () => {          /** @type {{conditionGroups: import('settings').ProfileConditionGroup[], expectedSchema?: import('ext/json-schema').Schema, inputs?: {expected: boolean, context: import('settings').OptionsContext}[]}[]} */          const data = [              // Empty @@ -1100,7 +1100,7 @@ function testSchemas() {              }          ]; -        for (const {conditionGroups, expectedSchema, inputs} of data) { +        test.each(data)('schemas-test-%#', ({conditionGroups, expectedSchema, inputs}) => {              const profileConditionsUtil = new ProfileConditionsUtil();              const schema = profileConditionsUtil.createSchema(conditionGroups);              if (typeof expectedSchema !== 'undefined') { @@ -1113,7 +1113,7 @@ function testSchemas() {                      expect(actual).toStrictEqual(expected);                  }              } -        } +        });      });  } diff --git a/test/text-source-map.test.js b/test/text-source-map.test.js index 54b39319..f798112b 100644 --- a/test/text-source-map.test.js +++ b/test/text-source-map.test.js @@ -16,28 +16,28 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ -import {expect, test} from 'vitest'; +import {describe, expect, test} from 'vitest';  import {TextSourceMap} from '../ext/js/general/text-source-map.js';  /** */  function testSource() { -    test('Source', () => { +    describe('Source', () => {          const data = [              ['source1'],              ['source2'],              ['source3']          ]; -        for (const [source] of data) { +        test.each(data)('source-test-%#', (source) => {              const sourceMap = new TextSourceMap(source);              expect(source).toStrictEqual(sourceMap.source); -        } +        });      });  }  /** */  function testEquals() { -    test('Equals', () => { +    describe('Equals', () => {          /** @type {[args1: [source1: string, mapping1: ?(number[])], args2: [source2: string, mapping2: ?(number[])], expectedEquals: boolean][]} */          const data = [              [['source1', null], ['source1', null], true], @@ -69,19 +69,19 @@ function testEquals() {              [['source3', [1, 1, 1, 1, 1, 1, 1]], ['source6', [1, 1, 1, 1, 1, 1, 1]], false]          ]; -        for (const [[source1, mapping1], [source2, mapping2], expectedEquals] of data) { +        test.each(data)('equals-test-%#', ([source1, mapping1], [source2, mapping2], expectedEquals) => {              const sourceMap1 = new TextSourceMap(source1, mapping1);              const sourceMap2 = new TextSourceMap(source2, mapping2);              expect(sourceMap1.equals(sourceMap1)).toBe(true);              expect(sourceMap2.equals(sourceMap2)).toBe(true);              expect(sourceMap1.equals(sourceMap2)).toStrictEqual(expectedEquals); -        } +        });      });  }  /** */  function testGetSourceLength() { -    test('GetSourceLength', () => { +    describe('GetSourceLength', () => {          /** @type {[args: [source: string, mapping: number[]], finalLength: number, expectedValue: number][]} */          const data = [              [['source', [1, 1, 1, 1, 1, 1]], 1, 1], @@ -101,16 +101,16 @@ function testGetSourceLength() {              [['source', [6, 6]], 1, 6]          ]; -        for (const [[source, mapping], finalLength, expectedValue] of data) { +        test.each(data)('get-source-length-test-%#', ([source, mapping], finalLength, expectedValue) => {              const sourceMap = new TextSourceMap(source, mapping);              expect(sourceMap.getSourceLength(finalLength)).toStrictEqual(expectedValue); -        } +        });      });  }  /** */  function testCombineInsert() { -    test('CombineInsert', () => { +    describe('CombineInsert', () => {          /** @type {[args: [source: string, mapping: ?(number[])], expectedArgs: [expectedSource: string, expectedMapping: ?(number[])], operations: [operation: string, arg1: number, arg2: number][]][]} */          const data = [              // No operations @@ -214,7 +214,7 @@ function testCombineInsert() {              ]          ]; -        for (const [[source, mapping], [expectedSource, expectedMapping], operations] of data) { +        test.each(data)('combine-insert-test-%#', ([source, mapping], [expectedSource, expectedMapping], operations) => {              const sourceMap = new TextSourceMap(source, mapping);              const expectedSourceMap = new TextSourceMap(expectedSource, expectedMapping);              for (const [operation, ...args] of operations) { @@ -228,7 +228,7 @@ function testCombineInsert() {                  }              }              expect(sourceMap.equals(expectedSourceMap)).toBe(true); -        } +        });      });  } |