diff options
Diffstat (limited to 'test')
24 files changed, 1640 insertions, 0 deletions
| diff --git a/test/data/dictionaries/invalid-dictionary1/index.json b/test/data/dictionaries/invalid-dictionary1/index.json new file mode 100644 index 00000000..1be3b360 --- /dev/null +++ b/test/data/dictionaries/invalid-dictionary1/index.json @@ -0,0 +1,7 @@ +{ +    "title": "Invalid Dictionary 1", +    "format": 0, +    "revision": "test", +    "sequenced": true, +    "description": "Invalid format number" +}
\ No newline at end of file diff --git a/test/data/dictionaries/invalid-dictionary2/index.json b/test/data/dictionaries/invalid-dictionary2/index.json new file mode 100644 index 00000000..ba2cc669 --- /dev/null +++ b/test/data/dictionaries/invalid-dictionary2/index.json @@ -0,0 +1,7 @@ +{ +    "title": "Invalid Dictionary 2", +    "format": 3, +    "revision": "test", +    "sequenced": true, +    "description": "Empty entry in kanji bank" +}
\ No newline at end of file diff --git a/test/data/dictionaries/invalid-dictionary2/kanji_bank_1.json b/test/data/dictionaries/invalid-dictionary2/kanji_bank_1.json new file mode 100644 index 00000000..5825bcac --- /dev/null +++ b/test/data/dictionaries/invalid-dictionary2/kanji_bank_1.json @@ -0,0 +1,3 @@ +[ +    [] +]
\ No newline at end of file diff --git a/test/data/dictionaries/invalid-dictionary3/index.json b/test/data/dictionaries/invalid-dictionary3/index.json new file mode 100644 index 00000000..f23fa3f0 --- /dev/null +++ b/test/data/dictionaries/invalid-dictionary3/index.json @@ -0,0 +1,7 @@ +{ +    "title": "Invalid Dictionary 3", +    "format": 3, +    "revision": "test", +    "sequenced": true, +    "description": "Invalid type entry in kanji meta bank" +}
\ No newline at end of file diff --git a/test/data/dictionaries/invalid-dictionary3/kanji_meta_bank_1.json b/test/data/dictionaries/invalid-dictionary3/kanji_meta_bank_1.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/test/data/dictionaries/invalid-dictionary3/kanji_meta_bank_1.json @@ -0,0 +1 @@ +{}
\ No newline at end of file diff --git a/test/data/dictionaries/invalid-dictionary4/index.json b/test/data/dictionaries/invalid-dictionary4/index.json new file mode 100644 index 00000000..542791d7 --- /dev/null +++ b/test/data/dictionaries/invalid-dictionary4/index.json @@ -0,0 +1,7 @@ +{ +    "title": "Invalid Dictionary 4", +    "format": 3, +    "revision": "test", +    "sequenced": true, +    "description": "Invalid value as part of a tag bank entry" +}
\ No newline at end of file diff --git a/test/data/dictionaries/invalid-dictionary4/tag_bank_1.json b/test/data/dictionaries/invalid-dictionary4/tag_bank_1.json new file mode 100644 index 00000000..4f19b476 --- /dev/null +++ b/test/data/dictionaries/invalid-dictionary4/tag_bank_1.json @@ -0,0 +1,3 @@ +[ +    [{"invalid": true}, "category1", 0, "tag1 notes", 0] +]
\ No newline at end of file diff --git a/test/data/dictionaries/invalid-dictionary5/index.json b/test/data/dictionaries/invalid-dictionary5/index.json new file mode 100644 index 00000000..e0d0f00e --- /dev/null +++ b/test/data/dictionaries/invalid-dictionary5/index.json @@ -0,0 +1,7 @@ +{ +    "title": "Invalid Dictionary 5", +    "format": 3, +    "revision": "test", +    "sequenced": true, +    "description": "Invalid type as part of a term bank entry" +}
\ No newline at end of file diff --git a/test/data/dictionaries/invalid-dictionary5/term_bank_1.json b/test/data/dictionaries/invalid-dictionary5/term_bank_1.json new file mode 100644 index 00000000..7288a996 --- /dev/null +++ b/test/data/dictionaries/invalid-dictionary5/term_bank_1.json @@ -0,0 +1,3 @@ +[ +    ["打", "だ", "tag1 tag2", "", 2, false, 1, "tag3 tag4 tag5"] +]
\ No newline at end of file diff --git a/test/data/dictionaries/invalid-dictionary6/index.json b/test/data/dictionaries/invalid-dictionary6/index.json new file mode 100644 index 00000000..b91acca3 --- /dev/null +++ b/test/data/dictionaries/invalid-dictionary6/index.json @@ -0,0 +1,7 @@ +{ +    "title": "Invalid Dictionary 6", +    "format": 3, +    "revision": "test", +    "sequenced": true, +    "description": "Invalid root type for term meta bank" +}
\ No newline at end of file diff --git a/test/data/dictionaries/invalid-dictionary6/term_meta_bank_1.json b/test/data/dictionaries/invalid-dictionary6/term_meta_bank_1.json new file mode 100644 index 00000000..02e4a84d --- /dev/null +++ b/test/data/dictionaries/invalid-dictionary6/term_meta_bank_1.json @@ -0,0 +1 @@ +false
\ No newline at end of file diff --git a/test/data/dictionaries/valid-dictionary1/index.json b/test/data/dictionaries/valid-dictionary1/index.json new file mode 100644 index 00000000..3034bf38 --- /dev/null +++ b/test/data/dictionaries/valid-dictionary1/index.json @@ -0,0 +1,6 @@ +{ +    "title": "Test Dictionary", +    "format": 3, +    "revision": "test", +    "sequenced": true +}
\ No newline at end of file diff --git a/test/data/dictionaries/valid-dictionary1/kanji_bank_1.json b/test/data/dictionaries/valid-dictionary1/kanji_bank_1.json new file mode 100644 index 00000000..264f94c1 --- /dev/null +++ b/test/data/dictionaries/valid-dictionary1/kanji_bank_1.json @@ -0,0 +1,42 @@ +[ +    [ +        "打", +        "ダ ダアス", +        "う.つ う.ち- ぶ.つ", +        "ktag1 ktag2", +        [ +            "meaning1", +            "meaning2", +            "meaning3", +            "meaning4", +            "meaning5" +        ], +        { +            "kstat1": "1", +            "kstat2": "2", +            "kstat3": "3", +            "kstat4": "4", +            "kstat5": "5" +        } +    ], +    [ +        "込", +        "", +        "-こ.む こ.む こ.み -こ.み こ.める", +        "ktag1 ktag2", +        [ +            "meaning1", +            "meaning2", +            "meaning3", +            "meaning4", +            "meaning5" +        ], +        { +            "kstat1": "1", +            "kstat2": "2", +            "kstat3": "3", +            "kstat4": "4", +            "kstat5": "5" +        } +    ] +]
\ No newline at end of file diff --git a/test/data/dictionaries/valid-dictionary1/kanji_meta_bank_1.json b/test/data/dictionaries/valid-dictionary1/kanji_meta_bank_1.json new file mode 100644 index 00000000..73e75b8a --- /dev/null +++ b/test/data/dictionaries/valid-dictionary1/kanji_meta_bank_1.json @@ -0,0 +1,4 @@ +[ +    ["打", "freq", 1], +    ["込", "freq", 2] +]
\ No newline at end of file diff --git a/test/data/dictionaries/valid-dictionary1/tag_bank_1.json b/test/data/dictionaries/valid-dictionary1/tag_bank_1.json new file mode 100644 index 00000000..109ad395 --- /dev/null +++ b/test/data/dictionaries/valid-dictionary1/tag_bank_1.json @@ -0,0 +1,7 @@ +[ +    ["tag1", "category1", 0, "tag1 notes", 0], +    ["tag2", "category2", 0, "tag2 notes", 0], +    ["tag3", "category3", 0, "tag3 notes", 0], +    ["tag4", "category4", 0, "tag4 notes", 0], +    ["tag5", "category5", 0, "tag5 notes", 0] +]
\ No newline at end of file diff --git a/test/data/dictionaries/valid-dictionary1/tag_bank_2.json b/test/data/dictionaries/valid-dictionary1/tag_bank_2.json new file mode 100644 index 00000000..5e7936b3 --- /dev/null +++ b/test/data/dictionaries/valid-dictionary1/tag_bank_2.json @@ -0,0 +1,9 @@ +[ +    ["ktag1", "kcategory1", 0, "ktag1 notes", 0], +    ["ktag2", "kcategory2", 0, "ktag2 notes", 0], +    ["kstat1", "kcategory3", 0, "kstat1 notes", 0], +    ["kstat2", "kcategory4", 0, "kstat2 notes", 0], +    ["kstat3", "kcategory5", 0, "kstat3 notes", 0], +    ["kstat4", "kcategory6", 0, "kstat4 notes", 0], +    ["kstat5", "kcategory7", 0, "kstat5 notes", 0] +]
\ No newline at end of file diff --git a/test/data/dictionaries/valid-dictionary1/term_bank_1.json b/test/data/dictionaries/valid-dictionary1/term_bank_1.json new file mode 100644 index 00000000..755d9f6a --- /dev/null +++ b/test/data/dictionaries/valid-dictionary1/term_bank_1.json @@ -0,0 +1,34 @@ +[ +    ["打", "だ", "tag1 tag2", "", 2, ["definition1a (打, だ)", "definition1b (打, だ)"], 1, "tag3 tag4 tag5"], +    ["打", "ダース", "tag1 tag2", "", 1, ["definition1a (打, ダース)", "definition1b (打, ダース)"], 2, "tag3 tag4 tag5"], +    ["打つ", "うつ", "tag1 tag2", "v5", 3, ["definition1a (打つ, うつ)", "definition1b (打つ, うつ)"], 3, "tag3 tag4 tag5"], +    ["打つ", "うつ", "tag1 tag2", "v5", 4, ["definition2a (打つ, うつ)", "definition2b (打つ, うつ)"], 3, "tag3 tag4 tag5"], +    ["打つ", "うつ", "tag1 tag2", "v5", 5, ["definition3a (打つ, うつ)", "definition3b (打つ, うつ)"], 3, "tag3 tag4 tag5"], +    ["打つ", "うつ", "tag1 tag2", "v5", 6, ["definition4a (打つ, うつ)", "definition4b (打つ, うつ)"], 3, "tag3 tag4 tag5"], +    ["打つ", "うつ", "tag1 tag2", "v5", 7, ["definition5a (打つ, うつ)", "definition5b (打つ, うつ)"], 3, "tag3 tag4 tag5"], +    ["打つ", "うつ", "tag1 tag2", "v5", 8, ["definition6a (打つ, うつ)", "definition6b (打つ, うつ)"], 3, "tag3 tag4 tag5"], +    ["打つ", "うつ", "tag1 tag2", "v5", 9, ["definition7a (打つ, うつ)", "definition7b (打つ, うつ)"], 3, "tag3 tag4 tag5"], +    ["打つ", "うつ", "tag1 tag2", "v5", 10, ["definition8a (打つ, うつ)", "definition8b (打つ, うつ)"], 3, "tag3 tag4 tag5"], +    ["打つ", "うつ", "tag1 tag2", "v5", 11, ["definition9a (打つ, うつ)", "definition9b (打つ, うつ)"], 3, "tag3 tag4 tag5"], +    ["打つ", "うつ", "tag1 tag2", "v5", 12, ["definition10a (打つ, うつ)", "definition10b (打つ, うつ)"], 3, "tag3 tag4 tag5"], +    ["打つ", "うつ", "tag1 tag2", "v5", 13, ["definition11a (打つ, うつ)", "definition11b (打つ, うつ)"], 3, "tag3 tag4 tag5"], +    ["打つ", "うつ", "tag1 tag2", "v5", 14, ["definition12a (打つ, うつ)", "definition12b (打つ, うつ)"], 3, "tag3 tag4 tag5"], +    ["打つ", "うつ", "tag1 tag2", "v5", 15, ["definition13a (打つ, うつ)", "definition13b (打つ, うつ)"], 3, "tag3 tag4 tag5"], +    ["打つ", "うつ", "tag1 tag2", "v5", 16, ["definition14a (打つ, うつ)", "definition14b (打つ, うつ)"], 3, "tag3 tag4 tag5"], +    ["打つ", "うつ", "tag1 tag2", "v5", 17, ["definition15a (打つ, うつ)", "definition15b (打つ, うつ)"], 3, "tag3 tag4 tag5"], +    ["打つ", "ぶつ", "tag1 tag2", "v5", 18, ["definition1a (打つ, ぶつ)", "definition1b (打つ, ぶつ)"], 4, "tag3 tag4 tag5"], +    ["打つ", "ぶつ", "tag1 tag2", "v5", 19, ["definition2a (打つ, ぶつ)", "definition2b (打つ, ぶつ)"], 4, "tag3 tag4 tag5"], +    ["打ち込む", "うちこむ", "tag1 tag2", "v5", 20, ["definition1a (打ち込む, うちこむ)", "definition1b (打ち込む, うちこむ)"], 5, "tag3 tag4 tag5"], +    ["打ち込む", "うちこむ", "tag1 tag2", "v5", 21, ["definition2a (打ち込む, うちこむ)", "definition2b (打ち込む, うちこむ)"], 5, "tag5 tag6 tag7"], +    ["打ち込む", "うちこむ", "tag1 tag2", "v5", 22, ["definition3a (打ち込む, うちこむ)", "definition3b (打ち込む, うちこむ)"], 5, "tag5 tag6 tag7"], +    ["打ち込む", "うちこむ", "tag1 tag2", "v5", 23, ["definition4a (打ち込む, うちこむ)", "definition4b (打ち込む, うちこむ)"], 5, "tag5 tag6 tag7"], +    ["打ち込む", "うちこむ", "tag1 tag2", "v5", 24, ["definition5a (打ち込む, うちこむ)", "definition5b (打ち込む, うちこむ)"], 5, "tag5 tag6 tag7"], +    ["打ち込む", "うちこむ", "tag1 tag2", "v5", 25, ["definition6a (打ち込む, うちこむ)", "definition6b (打ち込む, うちこむ)"], 5, "tag5 tag6 tag7"], +    ["打ち込む", "うちこむ", "tag1 tag2", "v5", 26, ["definition7a (打ち込む, うちこむ)", "definition7b (打ち込む, うちこむ)"], 5, "tag5 tag6 tag7"], +    ["打ち込む", "うちこむ", "tag1 tag2", "v5", 27, ["definition8a (打ち込む, うちこむ)", "definition8b (打ち込む, うちこむ)"], 5, "tag5 tag6 tag7"], +    ["打ち込む", "うちこむ", "tag1 tag2", "v5", 28, ["definition9a (打ち込む, うちこむ)", "definition9b (打ち込む, うちこむ)"], 5, "tag5 tag6 tag7"], +    ["打ち込む", "ぶちこむ", "tag1 tag2", "v5", 29, ["definition1a (打ち込む, ぶちこむ)", "definition1b (打ち込む, ぶちこむ)"], 6, "tag3 tag4 tag5"], +    ["打ち込む", "ぶちこむ", "tag1 tag2", "v5", 30, ["definition2a (打ち込む, ぶちこむ)", "definition2b (打ち込む, ぶちこむ)"], 6, "tag3 tag4 tag5"], +    ["打ち込む", "ぶちこむ", "tag1 tag2", "v5", 31, ["definition3a (打ち込む, ぶちこむ)", "definition3b (打ち込む, ぶちこむ)"], 6, "tag3 tag4 tag5"], +    ["打ち込む", "ぶちこむ", "tag1 tag2", "v5", 32, ["definition4a (打ち込む, ぶちこむ)", "definition4b (打ち込む, ぶちこむ)"], 6, "tag3 tag4 tag5"] +]
\ No newline at end of file diff --git a/test/data/dictionaries/valid-dictionary1/term_meta_bank_1.json b/test/data/dictionaries/valid-dictionary1/term_meta_bank_1.json new file mode 100644 index 00000000..78096502 --- /dev/null +++ b/test/data/dictionaries/valid-dictionary1/term_meta_bank_1.json @@ -0,0 +1,5 @@ +[ +    ["打", "freq", 1], +    ["打つ", "freq", 2], +    ["打ち込む", "freq", 3] +]
\ No newline at end of file diff --git a/test/dictionary-validate.js b/test/dictionary-validate.js new file mode 100644 index 00000000..14eee2ed --- /dev/null +++ b/test/dictionary-validate.js @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2020  Alex Yatskov <alex@foosoft.net> + * Author: Alex Yatskov <alex@foosoft.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <https://www.gnu.org/licenses/>. + */ + +const fs = require('fs'); +const path = require('path'); +const yomichanTest = require('./yomichan-test'); + +const JSZip = yomichanTest.JSZip; +const {JsonSchema} = yomichanTest.requireScript('ext/bg/js/json-schema.js', ['JsonSchema']); + + +function readSchema(relativeFileName) { +    const fileName = path.join(__dirname, relativeFileName); +    const source = fs.readFileSync(fileName, {encoding: 'utf8'}); +    return JSON.parse(source); +} + + +async function validateDictionaryBanks(zip, fileNameFormat, schema) { +    let index = 1; +    while (true) { +        const fileName = fileNameFormat.replace(/\?/, index); + +        const file = zip.files[fileName]; +        if (!file) { break; } + +        const data = JSON.parse(await file.async('string')); +        JsonSchema.validate(data, schema); + +        ++index; +    } +} + +async function validateDictionary(archive, schemas) { +    const indexFile = archive.files['index.json']; +    if (!indexFile) { +        throw new Error('No dictionary index found in archive'); +    } + +    const index = JSON.parse(await indexFile.async('string')); +    const version = index.format || index.version; + +    JsonSchema.validate(index, schemas.index); + +    await validateDictionaryBanks(archive, 'term_bank_?.json', version === 1 ? schemas.termBankV1 : schemas.termBankV3); +    await validateDictionaryBanks(archive, 'term_meta_bank_?.json', schemas.termMetaBankV3); +    await validateDictionaryBanks(archive, 'kanji_bank_?.json', version === 1 ? schemas.kanjiBankV1 : schemas.kanjiBankV3); +    await validateDictionaryBanks(archive, 'kanji_meta_bank_?.json', schemas.kanjiMetaBankV3); +    await validateDictionaryBanks(archive, 'tag_bank_?.json', schemas.tagBankV3); +} + +function getSchemas() { +    return { +        index: readSchema('../ext/bg/data/dictionary-index-schema.json'), +        kanjiBankV1: readSchema('../ext/bg/data/dictionary-kanji-bank-v1-schema.json'), +        kanjiBankV3: readSchema('../ext/bg/data/dictionary-kanji-bank-v3-schema.json'), +        kanjiMetaBankV3: readSchema('../ext/bg/data/dictionary-kanji-meta-bank-v3-schema.json'), +        tagBankV3: readSchema('../ext/bg/data/dictionary-tag-bank-v3-schema.json'), +        termBankV1: readSchema('../ext/bg/data/dictionary-term-bank-v1-schema.json'), +        termBankV3: readSchema('../ext/bg/data/dictionary-term-bank-v3-schema.json'), +        termMetaBankV3: readSchema('../ext/bg/data/dictionary-term-meta-bank-v3-schema.json') +    }; +} + + +async function main() { +    const dictionaryFileNames = process.argv.slice(2); +    if (dictionaryFileNames.length === 0) { +        console.log([ +            'Usage:', +            '  node dictionary-validate <dictionary-file-names>...' +        ].join('\n')); +        return; +    } + +    const schemas = getSchemas(); + +    for (const dictionaryFileName of dictionaryFileNames) { +        try { +            console.log(`Validating ${dictionaryFileName}...`); +            const source = fs.readFileSync(dictionaryFileName); +            const archive = await JSZip.loadAsync(source); +            await validateDictionary(archive, schemas); +            console.log('No issues found'); +        } catch (e) { +            console.warn(e); +        } +    } +} + + +if (require.main === module) { main(); } + + +module.exports = { +    getSchemas, +    validateDictionary +}; diff --git a/test/schema-validate.js b/test/schema-validate.js new file mode 100644 index 00000000..a4f2d94c --- /dev/null +++ b/test/schema-validate.js @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2020  Alex Yatskov <alex@foosoft.net> + * Author: Alex Yatskov <alex@foosoft.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <https://www.gnu.org/licenses/>. + */ + +const fs = require('fs'); +const yomichanTest = require('./yomichan-test'); + +const {JsonSchema} = yomichanTest.requireScript('ext/bg/js/json-schema.js', ['JsonSchema']); + + +function main() { +    const args = process.argv.slice(2); +    if (args.length < 2) { +        console.log([ +            'Usage:', +            '  node schema-validate <schema-file-name> <data-file-names>...' +        ].join('\n')); +        return; +    } + +    const schemaSource = fs.readFileSync(args[0], {encoding: 'utf8'}); +    const schema = JSON.parse(schemaSource); + +    for (const dataFileName of args.slice(1)) { +        try { +            console.log(`Validating ${dataFileName}...`); +            const dataSource = fs.readFileSync(dataFileName, {encoding: 'utf8'}); +            const data = JSON.parse(dataSource); +            JsonSchema.validate(data, schema); +            console.log('No issues found'); +        } catch (e) { +            console.warn(e); +        } +    } +} + + +if (require.main === module) { main(); } diff --git a/test/test-database.js b/test/test-database.js new file mode 100644 index 00000000..c2317881 --- /dev/null +++ b/test/test-database.js @@ -0,0 +1,935 @@ +/* + * Copyright (C) 2020  Alex Yatskov <alex@foosoft.net> + * Author: Alex Yatskov <alex@foosoft.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <https://www.gnu.org/licenses/>. + */ + +const fs = require('fs'); +const url = require('url'); +const path = require('path'); +const assert = require('assert'); +const yomichanTest = require('./yomichan-test'); +require('fake-indexeddb/auto'); + +const chrome = { +    runtime: { +        onMessage: { +            addListener() { /* NOP */ } +        }, +        getURL(path2) { +            return url.pathToFileURL(path.join(__dirname, '..', 'ext', path2.replace(/^\//, ''))); +        } +    } +}; + +class XMLHttpRequest { +    constructor() { +        this._eventCallbacks = new Map(); +        this._url = ''; +        this._responseText = null; +    } + +    overrideMimeType() { +        // NOP +    } + +    addEventListener(eventName, callback) { +        let callbacks = this._eventCallbacks.get(eventName); +        if (typeof callbacks === 'undefined') { +            callbacks = []; +            this._eventCallbacks.set(eventName, callbacks); +        } +        callbacks.push(callback); +    } + +    open(action, url2) { +        this._url = url2; +    } + +    send() { +        const filePath = url.fileURLToPath(this._url); +        Promise.resolve() +            .then(() => { +                let source; +                try { +                    source = fs.readFileSync(filePath, {encoding: 'utf8'}); +                } catch (e) { +                    this._trigger('error'); +                    return; +                } +                this._responseText = source; +                this._trigger('load'); +            }); +    } + +    get responseText() { +        return this._responseText; +    } + +    _trigger(eventName, ...args) { +        const callbacks = this._eventCallbacks.get(eventName); +        if (typeof callbacks === 'undefined') { return; } + +        for (let i = 0, ii = callbacks.length; i < ii; ++i) { +            callbacks[i](...args); +        } +    } +} + +const {JsonSchema} = yomichanTest.requireScript('ext/bg/js/json-schema.js', ['JsonSchema']); +const {dictFieldSplit, dictTagSanitize} = yomichanTest.requireScript('ext/bg/js/dictionary.js', ['dictFieldSplit', 'dictTagSanitize']); +const {stringReverse, hasOwn} = yomichanTest.requireScript('ext/mixed/js/core.js', ['stringReverse', 'hasOwn'], {chrome}); +const {requestJson} = yomichanTest.requireScript('ext/bg/js/request.js', ['requestJson'], {XMLHttpRequest}); + +const databaseGlobals = { +    chrome, +    JsonSchema, +    requestJson, +    stringReverse, +    hasOwn, +    dictFieldSplit, +    dictTagSanitize, +    indexedDB: global.indexedDB, +    JSZip: yomichanTest.JSZip +}; +databaseGlobals.window = databaseGlobals; +const {Database} = yomichanTest.requireScript('ext/bg/js/database.js', ['Database'], databaseGlobals); + + +function countTermsWithExpression(terms, expression) { +    return terms.reduce((i, v) => (i + (v.expression === expression ? 1 : 0)), 0); +} + +function countTermsWithReading(terms, reading) { +    return terms.reduce((i, v) => (i + (v.reading === reading ? 1 : 0)), 0); +} + +function countMetasWithMode(metas, mode) { +    return metas.reduce((i, v) => (i + (v.mode === mode ? 1 : 0)), 0); +} + +function countKanjiWithCharacter(kanji, character) { +    return kanji.reduce((i, v) => (i + (v.character === character ? 1 : 0)), 0); +} + + +function clearDatabase(timeout) { +    return new Promise((resolve, reject) => { +        let timer = setTimeout(() => { +            timer = null; +            reject(new Error(`clearDatabase failed to resolve after ${timeout}ms`)); +        }, timeout); + +        (async () => { +            const indexedDB = global.indexedDB; +            for (const {name} of await indexedDB.databases()) { +                await new Promise((resolve2, reject2) => { +                    const request = indexedDB.deleteDatabase(name); +                    request.onerror = (e) => reject2(e); +                    request.onsuccess = () => resolve2(); +                }); +            } +            if (timer !== null) { +                clearTimeout(timer); +            } +            resolve(); +        })(); +    }); +} + + +async function testDatabase1() { +    // Load dictionary data +    const testDictionary = yomichanTest.createTestDictionaryArchive('valid-dictionary1'); +    const testDictionarySource = await testDictionary.generateAsync({type: 'string'}); +    const testDictionaryIndex = JSON.parse(await testDictionary.files['index.json'].async('string')); + +    const title = testDictionaryIndex.title; +    const titles = new Map([ +        [title, {priority: 0, allowSecondarySearches: false}] +    ]); + +    // Setup iteration data +    const iterations = [ +        { +            cleanup: async () => { +                // Test purge +                await database.purge(); +                await testDatabaseEmpty1(database); +            } +        }, +        { +            cleanup: async () => { +                // Test deleteDictionary +                let progressEvent = false; +                await database.deleteDictionary( +                    title, +                    () => { +                        progressEvent = true; +                    }, +                    {rate: 1000} +                ); +                assert.ok(progressEvent); + +                await testDatabaseEmpty1(database); +            } +        }, +        { +            cleanup: async () => {} +        } +    ]; + +    // Setup database +    const database = new Database(); +    await database.prepare(); + +    for (const {cleanup} of iterations) { +        const expectedSummary = { +            title, +            revision: 'test', +            sequenced: true, +            version: 3, +            prefixWildcardsSupported: true +        }; + +        // Import data +        let progressEvent = false; +        const {result, errors} = await database.importDictionary( +            testDictionarySource, +            () => { +                progressEvent = true; +            }, +            {prefixWildcardsSupported: true} +        ); +        assert.deepStrictEqual(errors, []); +        assert.deepStrictEqual(result, expectedSummary); +        assert.ok(progressEvent); + +        // Get info summary +        const info = await database.getDictionaryInfo(); +        assert.deepStrictEqual(info, [expectedSummary]); + +        // Get counts +        const counts = await database.getDictionaryCounts( +            info.map((v) => v.title), +            true +        ); +        assert.deepStrictEqual(counts, { +            counts: [{kanji: 2, kanjiMeta: 2, terms: 32, termMeta: 3, tagMeta: 12}], +            total: {kanji: 2, kanjiMeta: 2, terms: 32, termMeta: 3, tagMeta: 12} +        }); + +        // Test find* functions +        await testFindTermsBulkTest1(database, titles); +        await testTindTermsExactBulk1(database, titles); +        await testFindTermsBySequenceBulk1(database, title); +        await testFindTermMetaBulk1(database, titles); +        await testFindKanjiBulk1(database, titles); +        await testFindKanjiMetaBulk1(database, titles); +        await testFindTagForTitle1(database, title); + +        // Cleanup +        await cleanup(); +    } + +    await database.close(); +} + +async function testDatabaseEmpty1(database) { +    const info = await database.getDictionaryInfo(); +    assert.deepStrictEqual(info, []); + +    const counts = await database.getDictionaryCounts([], true); +    assert.deepStrictEqual(counts, { +        counts: [], +        total: {kanji: 0, kanjiMeta: 0, terms: 0, termMeta: 0, tagMeta: 0} +    }); +} + +async function testFindTermsBulkTest1(database, titles) { +    const data = [ +        { +            inputs: [ +                { +                    wildcard: null, +                    termList: ['打', '打つ', '打ち込む'] +                }, +                { +                    wildcard: null, +                    termList: ['だ', 'ダース', 'うつ', 'ぶつ', 'うちこむ', 'ぶちこむ'] +                }, +                { +                    wildcard: 'suffix', +                    termList: ['打'] +                } +            ], +            expectedResults: { +                total: 32, +                expressions: [ +                    ['打', 2], +                    ['打つ', 17], +                    ['打ち込む', 13] +                ], +                readings: [ +                    ['だ', 1], +                    ['ダース', 1], +                    ['うつ', 15], +                    ['ぶつ', 2], +                    ['うちこむ', 9], +                    ['ぶちこむ', 4] +                ] +            } +        }, +        { +            inputs: [ +                { +                    wildcard: null, +                    termList: ['込む'] +                } +            ], +            expectedResults: { +                total: 0, +                expressions: [], +                readings: [] +            } +        }, +        { +            inputs: [ +                { +                    wildcard: 'prefix', +                    termList: ['込む'] +                } +            ], +            expectedResults: { +                total: 13, +                expressions: [ +                    ['打ち込む', 13] +                ], +                readings: [ +                    ['うちこむ', 9], +                    ['ぶちこむ', 4] +                ] +            } +        }, +        { +            inputs: [ +                { +                    wildcard: null, +                    termList: [] +                } +            ], +            expectedResults: { +                total: 0, +                expressions: [], +                readings: [] +            } +        } +    ]; + +    for (const {inputs, expectedResults} of data) { +        for (const {termList, wildcard} of inputs) { +            const results = await database.findTermsBulk(termList, titles, wildcard); +            assert.strictEqual(results.length, expectedResults.total); +            for (const [expression, count] of expectedResults.expressions) { +                assert.strictEqual(countTermsWithExpression(results, expression), count); +            } +            for (const [reading, count] of expectedResults.readings) { +                assert.strictEqual(countTermsWithReading(results, reading), count); +            } +        } +    } +} + +async function testTindTermsExactBulk1(database, titles) { +    const data = [ +        { +            inputs: [ +                { +                    termList: ['打', '打つ', '打ち込む'], +                    readingList: ['だ', 'うつ', 'うちこむ'] +                } +            ], +            expectedResults: { +                total: 25, +                expressions: [ +                    ['打', 1], +                    ['打つ', 15], +                    ['打ち込む', 9] +                ], +                readings: [ +                    ['だ', 1], +                    ['うつ', 15], +                    ['うちこむ', 9] +                ] +            } +        }, +        { +            inputs: [ +                { +                    termList: ['打', '打つ', '打ち込む'], +                    readingList: ['だ?', 'うつ?', 'うちこむ?'] +                } +            ], +            expectedResults: { +                total: 0, +                expressions: [], +                readings: [] +            } +        }, +        { +            inputs: [ +                { +                    termList: ['打つ', '打つ'], +                    readingList: ['うつ', 'ぶつ'] +                } +            ], +            expectedResults: { +                total: 17, +                expressions: [ +                    ['打つ', 17] +                ], +                readings: [ +                    ['うつ', 15], +                    ['ぶつ', 2] +                ] +            } +        }, +        { +            inputs: [ +                { +                    termList: ['打つ'], +                    readingList: ['うちこむ'] +                } +            ], +            expectedResults: { +                total: 0, +                expressions: [], +                readings: [] +            } +        }, +        { +            inputs: [ +                { +                    termList: [], +                    readingList: [] +                } +            ], +            expectedResults: { +                total: 0, +                expressions: [], +                readings: [] +            } +        } +    ]; + +    for (const {inputs, expectedResults} of data) { +        for (const {termList, readingList} of inputs) { +            const results = await database.findTermsExactBulk(termList, readingList, titles); +            assert.strictEqual(results.length, expectedResults.total); +            for (const [expression, count] of expectedResults.expressions) { +                assert.strictEqual(countTermsWithExpression(results, expression), count); +            } +            for (const [reading, count] of expectedResults.readings) { +                assert.strictEqual(countTermsWithReading(results, reading), count); +            } +        } +    } +} + +async function testFindTermsBySequenceBulk1(database, mainDictionary) { +    const data = [ +        { +            inputs: [ +                { +                    sequenceList: [1, 2, 3, 4, 5, 6] +                } +            ], +            expectedResults: { +                total: 32, +                expressions: [ +                    ['打', 2], +                    ['打つ', 17], +                    ['打ち込む', 13] +                ], +                readings: [ +                    ['だ', 1], +                    ['ダース', 1], +                    ['うつ', 15], +                    ['ぶつ', 2], +                    ['うちこむ', 9], +                    ['ぶちこむ', 4] +                ] +            } +        }, +        { +            inputs: [ +                { +                    sequenceList: [1] +                } +            ], +            expectedResults: { +                total: 1, +                expressions: [ +                    ['打', 1] +                ], +                readings: [ +                    ['だ', 1] +                ] +            } +        }, +        { +            inputs: [ +                { +                    sequenceList: [2] +                } +            ], +            expectedResults: { +                total: 1, +                expressions: [ +                    ['打', 1] +                ], +                readings: [ +                    ['ダース', 1] +                ] +            } +        }, +        { +            inputs: [ +                { +                    sequenceList: [3] +                } +            ], +            expectedResults: { +                total: 15, +                expressions: [ +                    ['打つ', 15] +                ], +                readings: [ +                    ['うつ', 15] +                ] +            } +        }, +        { +            inputs: [ +                { +                    sequenceList: [4] +                } +            ], +            expectedResults: { +                total: 2, +                expressions: [ +                    ['打つ', 2] +                ], +                readings: [ +                    ['ぶつ', 2] +                ] +            } +        }, +        { +            inputs: [ +                { +                    sequenceList: [5] +                } +            ], +            expectedResults: { +                total: 9, +                expressions: [ +                    ['打ち込む', 9] +                ], +                readings: [ +                    ['うちこむ', 9] +                ] +            } +        }, +        { +            inputs: [ +                { +                    sequenceList: [6] +                } +            ], +            expectedResults: { +                total: 4, +                expressions: [ +                    ['打ち込む', 4] +                ], +                readings: [ +                    ['ぶちこむ', 4] +                ] +            } +        }, +        { +            inputs: [ +                { +                    sequenceList: [-1] +                } +            ], +            expectedResults: { +                total: 0, +                expressions: [], +                readings: [] +            } +        }, +        { +            inputs: [ +                { +                    sequenceList: [] +                } +            ], +            expectedResults: { +                total: 0, +                expressions: [], +                readings: [] +            } +        } +    ]; + +    for (const {inputs, expectedResults} of data) { +        for (const {sequenceList} of inputs) { +            const results = await database.findTermsBySequenceBulk(sequenceList, mainDictionary); +            assert.strictEqual(results.length, expectedResults.total); +            for (const [expression, count] of expectedResults.expressions) { +                assert.strictEqual(countTermsWithExpression(results, expression), count); +            } +            for (const [reading, count] of expectedResults.readings) { +                assert.strictEqual(countTermsWithReading(results, reading), count); +            } +        } +    } +} + +async function testFindTermMetaBulk1(database, titles) { +    const data = [ +        { +            inputs: [ +                { +                    termList: ['打'] +                } +            ], +            expectedResults: { +                total: 1, +                modes: [ +                    ['freq', 1] +                ] +            } +        }, +        { +            inputs: [ +                { +                    termList: ['打つ'] +                } +            ], +            expectedResults: { +                total: 1, +                modes: [ +                    ['freq', 1] +                ] +            } +        }, +        { +            inputs: [ +                { +                    termList: ['打ち込む'] +                } +            ], +            expectedResults: { +                total: 1, +                modes: [ +                    ['freq', 1] +                ] +            } +        }, +        { +            inputs: [ +                { +                    termList: ['?'] +                } +            ], +            expectedResults: { +                total: 0, +                modes: [] +            } +        } +    ]; + +    for (const {inputs, expectedResults} of data) { +        for (const {termList} of inputs) { +            const results = await database.findTermMetaBulk(termList, titles); +            assert.strictEqual(results.length, expectedResults.total); +            for (const [mode, count] of expectedResults.modes) { +                assert.strictEqual(countMetasWithMode(results, mode), count); +            } +        } +    } +} + +async function testFindKanjiBulk1(database, titles) { +    const data = [ +        { +            inputs: [ +                { +                    kanjiList: ['打'] +                } +            ], +            expectedResults: { +                total: 1, +                kanji: [ +                    ['打', 1] +                ] +            } +        }, +        { +            inputs: [ +                { +                    kanjiList: ['込'] +                } +            ], +            expectedResults: { +                total: 1, +                kanji: [ +                    ['込', 1] +                ] +            } +        }, +        { +            inputs: [ +                { +                    kanjiList: ['?'] +                } +            ], +            expectedResults: { +                total: 0, +                kanji: [] +            } +        } +    ]; + +    for (const {inputs, expectedResults} of data) { +        for (const {kanjiList} of inputs) { +            const results = await database.findKanjiBulk(kanjiList, titles); +            assert.strictEqual(results.length, expectedResults.total); +            for (const [kanji, count] of expectedResults.kanji) { +                assert.strictEqual(countKanjiWithCharacter(results, kanji), count); +            } +        } +    } +} + +async function testFindKanjiMetaBulk1(database, titles) { +    const data = [ +        { +            inputs: [ +                { +                    kanjiList: ['打'] +                } +            ], +            expectedResults: { +                total: 1, +                modes: [ +                    ['freq', 1] +                ] +            } +        }, +        { +            inputs: [ +                { +                    kanjiList: ['込'] +                } +            ], +            expectedResults: { +                total: 1, +                modes: [ +                    ['freq', 1] +                ] +            } +        }, +        { +            inputs: [ +                { +                    kanjiList: ['?'] +                } +            ], +            expectedResults: { +                total: 0, +                modes: [] +            } +        } +    ]; + +    for (const {inputs, expectedResults} of data) { +        for (const {kanjiList} of inputs) { +            const results = await database.findKanjiMetaBulk(kanjiList, titles); +            assert.strictEqual(results.length, expectedResults.total); +            for (const [mode, count] of expectedResults.modes) { +                assert.strictEqual(countMetasWithMode(results, mode), count); +            } +        } +    } +} + +async function testFindTagForTitle1(database, title) { +    const data = [ +        { +            inputs: [ +                { +                    name: 'tag1' +                } +            ], +            expectedResults: { +                value: {category: 'category1', dictionary: title, name: 'tag1', notes: 'tag1 notes', order: 0, score: 0} +            } +        }, +        { +            inputs: [ +                { +                    name: 'ktag1' +                } +            ], +            expectedResults: { +                value: {category: 'kcategory1', dictionary: title, name: 'ktag1', notes: 'ktag1 notes', order: 0, score: 0} +            } +        }, +        { +            inputs: [ +                { +                    name: 'kstat1' +                } +            ], +            expectedResults: { +                value: {category: 'kcategory3', dictionary: title, name: 'kstat1', notes: 'kstat1 notes', order: 0, score: 0} +            } +        }, +        { +            inputs: [ +                { +                    name: 'invalid' +                } +            ], +            expectedResults: { +                value: null +            } +        } +    ]; + +    for (const {inputs, expectedResults} of data) { +        for (const {name} of inputs) { +            const result = await database.findTagForTitle(name, title); +            assert.deepStrictEqual(result, expectedResults.value); +        } +    } +} + + +async function testDatabase2() { +    // Load dictionary data +    const testDictionary = yomichanTest.createTestDictionaryArchive('valid-dictionary1'); +    const testDictionarySource = await testDictionary.generateAsync({type: 'string'}); +    const testDictionaryIndex = JSON.parse(await testDictionary.files['index.json'].async('string')); + +    const title = testDictionaryIndex.title; +    const titles = new Map([ +        [title, {priority: 0, allowSecondarySearches: false}] +    ]); + +    // Setup database +    const database = new Database(); + +    // Error: not prepared +    await assert.rejects(async () => await database.purge()); +    await assert.rejects(async () => await database.deleteDictionary(title, () => {}, {})); +    await assert.rejects(async () => await database.findTermsBulk(['?'], titles, null)); +    await assert.rejects(async () => await database.findTermsExactBulk(['?'], ['?'], titles)); +    await assert.rejects(async () => await database.findTermsBySequenceBulk([1], title)); +    await assert.rejects(async () => await database.findTermMetaBulk(['?'], titles)); +    await assert.rejects(async () => await database.findTermMetaBulk(['?'], titles)); +    await assert.rejects(async () => await database.findKanjiBulk(['?'], titles)); +    await assert.rejects(async () => await database.findKanjiMetaBulk(['?'], titles)); +    await assert.rejects(async () => await database.findTagForTitle('tag', title)); +    await assert.rejects(async () => await database.getDictionaryInfo()); +    await assert.rejects(async () => await database.getDictionaryCounts(titles, true)); +    await assert.rejects(async () => await database.importDictionary(testDictionarySource, () => {}, {})); + +    await database.prepare(); + +    // Error: already prepared +    await assert.rejects(async () => await database.prepare()); + +    await database.importDictionary(testDictionarySource, () => {}, {}); + +    // Error: dictionary already imported +    await assert.rejects(async () => await database.importDictionary(testDictionarySource, () => {}, {})); + +    await database.close(); +} + + +async function testDatabase3() { +    const invalidDictionaries = [ +        'invalid-dictionary1', +        'invalid-dictionary2', +        'invalid-dictionary3', +        'invalid-dictionary4', +        'invalid-dictionary5', +        'invalid-dictionary6' +    ]; + +    // Setup database +    const database = new Database(); +    await database.prepare(); + +    for (const invalidDictionary of invalidDictionaries) { +        const testDictionary = yomichanTest.createTestDictionaryArchive(invalidDictionary); +        const testDictionarySource = await testDictionary.generateAsync({type: 'string'}); + +        let error = null; +        try { +            await database.importDictionary(testDictionarySource, () => {}, {}); +        } catch (e) { +            error = e; +        } + +        if (error === null) { +            assert.ok(false, `Expected an error while importing ${invalidDictionary}`); +        } else { +            const prefix = 'Dictionary has invalid data'; +            const message = error.message; +            assert.ok(typeof message, 'string'); +            assert.ok(message.startsWith(prefix), `Expected error message to start with '${prefix}': ${message}`); +        } +    } + +    await database.close(); +} + + +async function main() { +    const clearTimeout = 5000; +    try { +        await testDatabase1(); +        await clearDatabase(clearTimeout); + +        await testDatabase2(); +        await clearDatabase(clearTimeout); + +        await testDatabase3(); +        await clearDatabase(clearTimeout); +    } catch (e) { +        console.log(e); +        process.exit(-1); +        throw e; +    } +} + + +if (require.main === module) { main(); } diff --git a/test/test-dictionary.js b/test/test-dictionary.js new file mode 100644 index 00000000..74f9e62b --- /dev/null +++ b/test/test-dictionary.js @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2020  Alex Yatskov <alex@foosoft.net> + * Author: Alex Yatskov <alex@foosoft.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <https://www.gnu.org/licenses/>. + */ + +const yomichanTest = require('./yomichan-test'); +const dictionaryValidate = require('./dictionary-validate'); + + +async function main() { +    const dictionaries = [ +        {name: 'valid-dictionary1', valid: true}, +        {name: 'invalid-dictionary1', valid: false}, +        {name: 'invalid-dictionary2', valid: false}, +        {name: 'invalid-dictionary3', valid: false}, +        {name: 'invalid-dictionary4', valid: false}, +        {name: 'invalid-dictionary5', valid: false}, +        {name: 'invalid-dictionary6', valid: false} +    ]; + +    const schemas = dictionaryValidate.getSchemas(); + +    for (const {name, valid} of dictionaries) { +        const archive = yomichanTest.createTestDictionaryArchive(name); + +        let error = null; +        try { +            await dictionaryValidate.validateDictionary(archive, schemas); +        } catch (e) { +            error = e; +        } + +        if (valid) { +            if (error !== null) { +                throw error; +            } +        } else { +            if (error === null) { +                throw new Error(`Expected dictionary ${name} to be invalid`); +            } +        } +    } +} + + +if (require.main === module) { main(); } diff --git a/test/test-schema.js b/test/test-schema.js new file mode 100644 index 00000000..f4612f86 --- /dev/null +++ b/test/test-schema.js @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2020  Alex Yatskov <alex@foosoft.net> + * Author: Alex Yatskov <alex@foosoft.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <https://www.gnu.org/licenses/>. + */ + +const assert = require('assert'); +const yomichanTest = require('./yomichan-test'); + +const {JsonSchema} = yomichanTest.requireScript('ext/bg/js/json-schema.js', ['JsonSchema']); + + +function testValidate1() { +    const schema = { +        allOf: [ +            { +                type: 'number' +            }, +            { +                anyOf: [ +                    {minimum: 10, maximum: 100}, +                    {minimum: -100, maximum: -10} +                ] +            }, +            { +                oneOf: [ +                    {multipleOf: 3}, +                    {multipleOf: 5} +                ] +            }, +            { +                not: [ +                    {multipleOf: 20} +                ] +            } +        ] +    }; + +    const schemaValidate = (value) => { +        try { +            JsonSchema.validate(value, schema); +            return true; +        } catch (e) { +            return false; +        } +    }; + +    const jsValidate = (value) => { +        return ( +            typeof value === 'number' && +            ( +                (value >= 10 && value <= 100) || +                (value >= -100 && value <= -10) +            ) && +            ( +                ( +                    (value % 3) === 0 || +                    (value % 5) === 0 +                ) && +                (value % 15) !== 0 +            ) && +            (value % 20) !== 0 +        ); +    }; + +    for (let i = -111; i <= 111; i++) { +        const actual = schemaValidate(i, schema); +        const expected = jsValidate(i); +        assert.strictEqual(actual, expected); +    } +} + + +function testGetValidValueOrDefault1() { +    // Test value defaulting on objects with additionalProperties=false +    const schema = { +        type: 'object', +        required: ['test'], +        properties: { +            test: { +                type: 'string', +                default: 'default' +            } +        }, +        additionalProperties: false +    }; + +    const testData = [ +        [ +            void 0, +            {test: 'default'} +        ], +        [ +            null, +            {test: 'default'} +        ], +        [ +            0, +            {test: 'default'} +        ], +        [ +            '', +            {test: 'default'} +        ], +        [ +            [], +            {test: 'default'} +        ], +        [ +            {}, +            {test: 'default'} +        ], +        [ +            {test: 'value'}, +            {test: 'value'} +        ], +        [ +            {test2: 'value2'}, +            {test: 'default'} +        ], +        [ +            {test: 'value', test2: 'value2'}, +            {test: 'value'} +        ] +    ]; + +    for (const [value, expected] of testData) { +        const actual = JsonSchema.getValidValueOrDefault(schema, value); +        assert.deepStrictEqual(actual, expected); +    } +} + +function testGetValidValueOrDefault2() { +    // Test value defaulting on objects with additionalProperties=true +    const schema = { +        type: 'object', +        required: ['test'], +        properties: { +            test: { +                type: 'string', +                default: 'default' +            } +        }, +        additionalProperties: true +    }; + +    const testData = [ +        [ +            {}, +            {test: 'default'} +        ], +        [ +            {test: 'value'}, +            {test: 'value'} +        ], +        [ +            {test2: 'value2'}, +            {test: 'default', test2: 'value2'} +        ], +        [ +            {test: 'value', test2: 'value2'}, +            {test: 'value', test2: 'value2'} +        ] +    ]; + +    for (const [value, expected] of testData) { +        const actual = JsonSchema.getValidValueOrDefault(schema, value); +        assert.deepStrictEqual(actual, expected); +    } +} + +function testGetValidValueOrDefault3() { +    // Test value defaulting on objects with additionalProperties={schema} +    const schema = { +        type: 'object', +        required: ['test'], +        properties: { +            test: { +                type: 'string', +                default: 'default' +            } +        }, +        additionalProperties: { +            type: 'number', +            default: 10 +        } +    }; + +    const testData = [ +        [ +            {}, +            {test: 'default'} +        ], +        [ +            {test: 'value'}, +            {test: 'value'} +        ], +        [ +            {test2: 'value2'}, +            {test: 'default', test2: 10} +        ], +        [ +            {test: 'value', test2: 'value2'}, +            {test: 'value', test2: 10} +        ], +        [ +            {test2: 2}, +            {test: 'default', test2: 2} +        ], +        [ +            {test: 'value', test2: 2}, +            {test: 'value', test2: 2} +        ], +        [ +            {test: 'value', test2: 2, test3: null}, +            {test: 'value', test2: 2, test3: 10} +        ], +        [ +            {test: 'value', test2: 2, test3: void 0}, +            {test: 'value', test2: 2, test3: 10} +        ] +    ]; + +    for (const [value, expected] of testData) { +        const actual = JsonSchema.getValidValueOrDefault(schema, value); +        assert.deepStrictEqual(actual, expected); +    } +} + + +function main() { +    testValidate1(); +    testGetValidValueOrDefault1(); +    testGetValidValueOrDefault2(); +    testGetValidValueOrDefault3(); +} + + +if (require.main === module) { main(); } diff --git a/test/yomichan-test.js b/test/yomichan-test.js new file mode 100644 index 00000000..78bfb9c6 --- /dev/null +++ b/test/yomichan-test.js @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2020  Alex Yatskov <alex@foosoft.net> + * Author: Alex Yatskov <alex@foosoft.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <https://www.gnu.org/licenses/>. + */ + +const fs = require('fs'); +const path = require('path'); + + +let JSZip = null; + +function requireScript(fileName, exportNames, variables) { +    const absoluteFileName = path.join(__dirname, '..', fileName); +    const source = fs.readFileSync(absoluteFileName, {encoding: 'utf8'}); +    const exportNamesString = Array.isArray(exportNames) ? exportNames.join(',') : ''; +    const variablesArgumentName = '__variables__'; +    let variableString = ''; +    if (typeof variables === 'object' && variables !== null) { +        variableString = Object.keys(variables).join(','); +        variableString = `const {${variableString}} = ${variablesArgumentName};`; +    } +    return Function(variablesArgumentName, `'use strict';${variableString}${source}\n;return {${exportNamesString}};`)(variables); +} + +function getJSZip() { +    if (JSZip === null) { +        process.noDeprecation = true; // Suppress a warning about JSZip +        JSZip = require(path.join(__dirname, '../ext/mixed/lib/jszip.min.js')); +        process.noDeprecation = false; +    } +    return JSZip; +} + +function createTestDictionaryArchive(dictionary, dictionaryName) { +    const dictionaryDirectory = path.join(__dirname, 'data', 'dictionaries', dictionary); +    const fileNames = fs.readdirSync(dictionaryDirectory); + +    const archive = new (getJSZip())(); + +    for (const fileName of fileNames) { +        const source = fs.readFileSync(path.join(dictionaryDirectory, fileName), {encoding: 'utf8'}); +        const json = JSON.parse(source); +        if (fileName === 'index.json' && typeof dictionaryName === 'string') { +            json.title = dictionaryName; +        } +        archive.file(fileName, JSON.stringify(json, null, 0)); +    } + +    return archive; +} + + +module.exports = { +    requireScript, +    createTestDictionaryArchive, +    get JSZip() { return getJSZip(); } +}; |