diff options
-rw-r--r-- | .eslintrc.json | 10 | ||||
-rw-r--r-- | ext/bg/js/database.js | 6 | ||||
-rw-r--r-- | package-lock.json | 98 | ||||
-rw-r--r-- | package.json | 5 | ||||
-rw-r--r-- | test/dictionary-validate.js | 65 | ||||
-rw-r--r-- | test/schema-validate.js | 8 | ||||
-rw-r--r-- | test/test-database.js | 783 | ||||
-rw-r--r-- | test/test-dictionary-data/index.json | 6 | ||||
-rw-r--r-- | test/test-dictionary-data/kanji_bank_1.json | 42 | ||||
-rw-r--r-- | test/test-dictionary-data/kanji_meta_bank_1.json | 4 | ||||
-rw-r--r-- | test/test-dictionary-data/tag_bank_1.json | 7 | ||||
-rw-r--r-- | test/test-dictionary-data/tag_bank_2.json | 9 | ||||
-rw-r--r-- | test/test-dictionary-data/term_bank_1.json | 34 | ||||
-rw-r--r-- | test/test-dictionary-data/term_meta_bank_1.json | 5 | ||||
-rw-r--r-- | test/test-dictionary.js | 12 | ||||
-rw-r--r-- | test/test-schema.js | 9 | ||||
-rw-r--r-- | test/yomichan-test.js | 59 |
17 files changed, 1116 insertions, 46 deletions
diff --git a/.eslintrc.json b/.eslintrc.json index a350bce4..9b0754fc 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -72,11 +72,15 @@ }, { "files": ["test/**/*.js"], + "parserOptions": { + "ecmaVersion": 8, + "sourceType": "module" + }, "env": { "browser": false, - "es2017": false, - "webextensions": false, - "node": true + "es2017": true, + "node": true, + "webextensions": false } } ] diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js index b54d832c..02d59c83 100644 --- a/ext/bg/js/database.js +++ b/ext/bg/js/database.js @@ -96,6 +96,12 @@ class Database { } } + async close() { + this.validate(); + this.db.close(); + this.db = null; + } + async purge() { this.validate(); diff --git a/package-lock.json b/package-lock.json index 11cf28bc..505c71db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -93,6 +93,12 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, + "base64-arraybuffer-es6": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/base64-arraybuffer-es6/-/base64-arraybuffer-es6-0.5.0.tgz", + "integrity": "sha512-UCIPaDJrNNj5jG2ZL+nzJ7czvZV/ZYX6LaIRgfVU1k1edJOQg7dkbiSKzwHkNp6aHEHER/PhlFBrMYnlvJJQEw==", + "dev": true + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -162,6 +168,12 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "core-js": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", + "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", + "dev": true + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -207,6 +219,15 @@ "esutils": "^2.0.2" } }, + "domexception": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", + "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "dev": true, + "requires": { + "webidl-conversions": "^4.0.2" + } + }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -353,6 +374,16 @@ "tmp": "^0.0.33" } }, + "fake-indexeddb": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fake-indexeddb/-/fake-indexeddb-3.0.0.tgz", + "integrity": "sha512-VrnV9dJWlVWvd8hp9MMR+JS4RLC4ZmToSkuCg91ZwpYE5mSODb3n5VEaV62Hf3AusnbrPfwQhukU+rGZm5W8PQ==", + "dev": true, + "requires": { + "realistic-structured-clone": "^2.0.1", + "setimmediate": "^1.0.5" + } + }, "fast-deep-equal": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", @@ -601,6 +632,12 @@ "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", "dev": true }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true + }, "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -732,6 +769,18 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, + "realistic-structured-clone": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/realistic-structured-clone/-/realistic-structured-clone-2.0.2.tgz", + "integrity": "sha512-5IEvyfuMJ4tjQOuKKTFNvd+H9GSbE87IcendSBannE28PTrbolgaVg5DdEApRKhtze794iXqVUFKV60GLCNKEg==", + "dev": true, + "requires": { + "core-js": "^2.5.3", + "domexception": "^1.0.1", + "typeson": "^5.8.2", + "typeson-registry": "^1.0.0-alpha.20" + } + }, "regexpp": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", @@ -793,6 +842,12 @@ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -951,6 +1006,15 @@ "os-tmpdir": "~1.0.2" } }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, "tslib": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", @@ -972,6 +1036,23 @@ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true }, + "typeson": { + "version": "5.18.2", + "resolved": "https://registry.npmjs.org/typeson/-/typeson-5.18.2.tgz", + "integrity": "sha512-Vetd+OGX05P4qHyHiSLdHZ5Z5GuQDrHHwSdjkqho9NSCYVSLSfRMjklD/unpHH8tXBR9Z/R05rwJSuMpMFrdsw==", + "dev": true + }, + "typeson-registry": { + "version": "1.0.0-alpha.34", + "resolved": "https://registry.npmjs.org/typeson-registry/-/typeson-registry-1.0.0-alpha.34.tgz", + "integrity": "sha512-2U0R5eFGJPaqha8HBAICJv6rW2x/cAVHizURHbcAo61Mpd47s+MDn67Ktxoyl9jWgsqCAibZsrldG8v/2ZuCaw==", + "dev": true, + "requires": { + "base64-arraybuffer-es6": "0.5.0", + "typeson": "5.18.2", + "whatwg-url": "7.1.0" + } + }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -987,6 +1068,23 @@ "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", "dev": true }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, + "whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", diff --git a/package.json b/package.json index b486c25d..2bbc6a79 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "scripts": { "test": "npm run test-lint && npm run test-code", "test-lint": "eslint .", - "test-code": "node ./test/test-schema.js" + "test-code": "node ./test/test-schema.js && node ./test/test-dictionary.js && node ./test/test-database.js" }, "repository": { "type": "git", @@ -27,6 +27,7 @@ "homepage": "https://foosoft.net/projects/yomichan/", "devDependencies": { "eslint": "^6.8.0", - "eslint-plugin-no-unsanitized": "^3.0.2" + "eslint-plugin-no-unsanitized": "^3.0.2", + "fake-indexeddb": "^3.0.0" } } diff --git a/test/dictionary-validate.js b/test/dictionary-validate.js index 971c4971..25a5de88 100644 --- a/test/dictionary-validate.js +++ b/test/dictionary-validate.js @@ -1,13 +1,9 @@ const fs = require('fs'); const path = require('path'); +const yomichanTest = require('./yomichan-test'); -process.noDeprecation = true; // Suppress a warning about JSZip -const JSZip = require(path.join(__dirname, '../ext/mixed/lib/jszip.min.js')); -process.noDeprecation = false; - -const jsonSchemaFileName = path.join(__dirname, '../ext/bg/js/json-schema.js'); -const jsonSchemaFileSource = fs.readFileSync(jsonSchemaFileName, {encoding: 'utf8'}); -const JsonSchema = Function(`'use strict';${jsonSchemaFileSource};return JsonSchema;`)(); +const JSZip = yomichanTest.JSZip; +const {JsonSchema} = yomichanTest.requireScript('ext/bg/js/json-schema.js', ['JsonSchema']); function readSchema(relativeFileName) { @@ -20,7 +16,7 @@ function readSchema(relativeFileName) { async function validateDictionaryBanks(zip, fileNameFormat, schema) { let index = 1; while (true) { - const fileName = fileNameFormat.replace(/%s/, index); + const fileName = fileNameFormat.replace(/\?/, index); const file = zip.files[fileName]; if (!file) { break; } @@ -32,11 +28,8 @@ async function validateDictionaryBanks(zip, fileNameFormat, schema) { } } -async function validateDictionary(fileName, schemas) { - const source = fs.readFileSync(fileName); - const zip = await JSZip.loadAsync(source); - - const indexFile = zip.files['index.json']; +async function validateDictionary(archive, schemas) { + const indexFile = archive.files['index.json']; if (!indexFile) { throw new Error('No dictionary index found in archive'); } @@ -46,11 +39,24 @@ async function validateDictionary(fileName, schemas) { JsonSchema.validate(index, schemas.index); - await validateDictionaryBanks(zip, 'term_bank_%s.json', version === 1 ? schemas.termBankV1 : schemas.termBankV3); - await validateDictionaryBanks(zip, 'term_meta_bank_%s.json', schemas.termMetaBankV3); - await validateDictionaryBanks(zip, 'kanji_bank_%s.json', version === 1 ? schemas.kanjiBankV1 : schemas.kanjiBankV3); - await validateDictionaryBanks(zip, 'kanji_meta_bank_%s.json', schemas.kanjiMetaBankV3); - await validateDictionaryBanks(zip, 'tag_bank_%s.json', schemas.tagBankV3); + 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') + }; } @@ -64,21 +70,14 @@ async function main() { return; } - const schemas = { - 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') - }; + const schemas = getSchemas(); for (const dictionaryFileName of dictionaryFileNames) { try { console.log(`Validating ${dictionaryFileName}...`); - await validateDictionary(dictionaryFileName, schemas); + 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); @@ -87,4 +86,10 @@ async function main() { } -main(); +if (require.main === module) { main(); } + + +module.exports = { + getSchemas, + validateDictionary +}; diff --git a/test/schema-validate.js b/test/schema-validate.js index ac5a8a85..1271a611 100644 --- a/test/schema-validate.js +++ b/test/schema-validate.js @@ -1,9 +1,7 @@ const fs = require('fs'); -const path = require('path'); +const yomichanTest = require('./yomichan-test'); -const jsonSchemaFileName = path.join(__dirname, '../ext/bg/js/json-schema.js'); -const jsonSchemaFileSource = fs.readFileSync(jsonSchemaFileName, {encoding: 'utf8'}); -const JsonSchema = Function(`'use strict';${jsonSchemaFileSource};return JsonSchema;`)(); +const {JsonSchema} = yomichanTest.requireScript('ext/bg/js/json-schema.js', ['JsonSchema']); function main() { @@ -33,4 +31,4 @@ function main() { } -main(); +if (require.main === module) { main(); } diff --git a/test/test-database.js b/test/test-database.js new file mode 100644 index 00000000..fcf519d7 --- /dev/null +++ b/test/test-database.js @@ -0,0 +1,783 @@ +const assert = require('assert'); +const yomichanTest = require('./yomichan-test'); +require('fake-indexeddb/auto'); + +const chrome = { + runtime: { + onMessage: { + addListener: () => { /* NOP */ } + } + } +}; + +const {Database} = yomichanTest.requireScript('ext/bg/js/database.js', ['Database']); +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}); + +global.window = global; +global.JSZip = yomichanTest.JSZip; +global.dictFieldSplit = dictFieldSplit; +global.dictTagSanitize = dictTagSanitize; +global.stringReverse = stringReverse; +global.hasOwn = hasOwn; + + +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); +} + + +async function clearDatabase() { + const indexedDB = global.indexedDB; + for (const {name} of await indexedDB.databases()) { + await new Promise((resolve, reject) => { + const request = indexedDB.deleteDatabase(name); + request.onerror = (e) => reject(e); + request.onsuccess = () => resolve(); + }); + } +} + + +async function testDatabase1() { + // Load dictionary data + const testDictionary = yomichanTest.createTestDictionaryArchive(); + const testDictionarySource = await testDictionary.generateAsync({type: 'string'}); + const testDictionaryIndex = JSON.parse(await testDictionary.files['index.json'].async('string')); + + const title = testDictionaryIndex.title; + const titles = [title]; + + // 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(); + const testDictionarySource = await testDictionary.generateAsync({type: 'string'}); + const testDictionaryIndex = JSON.parse(await testDictionary.files['index.json'].async('string')); + + const title = testDictionaryIndex.title; + const titles = [title]; + + // 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 main() { + await testDatabase1(); + await clearDatabase(); + + await testDatabase2(); + await clearDatabase(); +} + + +if (require.main === module) { main(); } diff --git a/test/test-dictionary-data/index.json b/test/test-dictionary-data/index.json new file mode 100644 index 00000000..3034bf38 --- /dev/null +++ b/test/test-dictionary-data/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/test-dictionary-data/kanji_bank_1.json b/test/test-dictionary-data/kanji_bank_1.json new file mode 100644 index 00000000..264f94c1 --- /dev/null +++ b/test/test-dictionary-data/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/test-dictionary-data/kanji_meta_bank_1.json b/test/test-dictionary-data/kanji_meta_bank_1.json new file mode 100644 index 00000000..73e75b8a --- /dev/null +++ b/test/test-dictionary-data/kanji_meta_bank_1.json @@ -0,0 +1,4 @@ +[ + ["打", "freq", 1], + ["込", "freq", 2] +]
\ No newline at end of file diff --git a/test/test-dictionary-data/tag_bank_1.json b/test/test-dictionary-data/tag_bank_1.json new file mode 100644 index 00000000..109ad395 --- /dev/null +++ b/test/test-dictionary-data/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/test-dictionary-data/tag_bank_2.json b/test/test-dictionary-data/tag_bank_2.json new file mode 100644 index 00000000..5e7936b3 --- /dev/null +++ b/test/test-dictionary-data/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/test-dictionary-data/term_bank_1.json b/test/test-dictionary-data/term_bank_1.json new file mode 100644 index 00000000..755d9f6a --- /dev/null +++ b/test/test-dictionary-data/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/test-dictionary-data/term_meta_bank_1.json b/test/test-dictionary-data/term_meta_bank_1.json new file mode 100644 index 00000000..78096502 --- /dev/null +++ b/test/test-dictionary-data/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/test-dictionary.js b/test/test-dictionary.js new file mode 100644 index 00000000..84014540 --- /dev/null +++ b/test/test-dictionary.js @@ -0,0 +1,12 @@ +const yomichanTest = require('./yomichan-test'); +const dictionaryValidate = require('./dictionary-validate'); + + +async function main() { + const archive = yomichanTest.createTestDictionaryArchive(); + const schemas = dictionaryValidate.getSchemas(); + await dictionaryValidate.validateDictionary(archive, schemas); +} + + +if (require.main === module) { main(); } diff --git a/test/test-schema.js b/test/test-schema.js index 2f294e43..ca4f56dd 100644 --- a/test/test-schema.js +++ b/test/test-schema.js @@ -1,10 +1,7 @@ -const fs = require('fs'); -const path = require('path'); const assert = require('assert'); +const yomichanTest = require('./yomichan-test'); -const jsonSchemaFileName = path.join(__dirname, '../ext/bg/js/json-schema.js'); -const jsonSchemaFileSource = fs.readFileSync(jsonSchemaFileName, {encoding: 'utf8'}); -const JsonSchema = Function(`'use strict';${jsonSchemaFileSource};return JsonSchema;`)(); +const {JsonSchema} = yomichanTest.requireScript('ext/bg/js/json-schema.js', ['JsonSchema']); function testValidate1() { @@ -233,4 +230,4 @@ function main() { } -main(); +if (require.main === module) { main(); } diff --git a/test/yomichan-test.js b/test/yomichan-test.js new file mode 100644 index 00000000..dd4da919 --- /dev/null +++ b/test/yomichan-test.js @@ -0,0 +1,59 @@ +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(dictionaryName) { + const fileNames = [ + 'index.json', + 'tag_bank_1.json', + 'tag_bank_2.json', + 'term_bank_1.json', + 'kanji_bank_1.json', + 'term_meta_bank_1.json', + 'kanji_meta_bank_1.json' + ]; + + const archive = new (getJSZip())(); + + for (const fileName of fileNames) { + const source = fs.readFileSync(path.join(__dirname, 'test-dictionary-data', 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(); } +}; |