Something went wrong. Try again.
Pop-up dictionary browser extension for language learning. Successor to Yomichan. (PERSONAL FORK)
Something went wrong. Try again.
JavaScript
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036/* * Copyright (C) 2023-2025 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * 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/>. */
import {describe, expect, test} from 'vitest';import {JsonSchema} from '../ext/js/data/json-schema.js';
/** * @param {import('ext/json-schema').Schema} schema * @param {unknown} value * @returns {boolean} */function schemaValidate(schema, value) { return new JsonSchema(schema).isValid(value);}
/** * @param {import('ext/json-schema').Schema} schema * @param {unknown} value * @returns {import('ext/json-schema').Value} */function getValidValueOrDefault(schema, value) { return new JsonSchema(schema).getValidValueOrDefault(value);}
/** * @param {import('ext/json-schema').Schema} schema * @param {import('ext/json-schema').Value} value * @returns {import('ext/json-schema').Value} */function createProxy(schema, value) { return new JsonSchema(schema).createProxy(value);}
describe('JsonSchema', () => { describe('Validate1', () => { /** @type {import('ext/json-schema').Schema} */ const schema = { allOf: [ { type: 'number', }, { anyOf: [ {minimum: 10, maximum: 100}, {minimum: -100, maximum: -10}, ], }, { oneOf: [ {multipleOf: 3}, {multipleOf: 5}, ], }, { not: { anyOf: [ {multipleOf: 20}, ], }, }, ], };
/** * @param {number} value * @returns {boolean} */ 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 ); };
test('works as expected', () => { for (let i = -111; i <= 111; i++) { const actual = schemaValidate(schema, i); const expected = jsValidate(i); expect(actual).toStrictEqual(expected); } }); });
describe('Validate2', () => { /* eslint-disable @stylistic/no-multi-spaces */ /** @type {{schema: import('ext/json-schema').Schema, inputs: {expected: boolean, value: unknown}[]}[]} */ const data = [ // String tests { schema: { type: 'string', }, inputs: [ {expected: false, value: null}, {expected: false, value: void 0}, {expected: false, value: 0}, {expected: false, value: {}}, {expected: false, value: []}, {expected: true, value: ''}, ], }, { schema: { type: 'string', minLength: 2, }, inputs: [ {expected: false, value: ''}, {expected: false, value: '1'}, {expected: true, value: '12'}, {expected: true, value: '123'}, ], }, { schema: { type: 'string', maxLength: 2, }, inputs: [ {expected: true, value: ''}, {expected: true, value: '1'}, {expected: true, value: '12'}, {expected: false, value: '123'}, ], }, { schema: { type: 'string', pattern: 'test', }, inputs: [ {expected: false, value: ''}, {expected: true, value: 'test'}, {expected: false, value: 'TEST'}, {expected: true, value: 'ABCtestDEF'}, {expected: false, value: 'ABCTESTDEF'}, ], }, { schema: { type: 'string', pattern: '^test$', }, inputs: [ {expected: false, value: ''}, {expected: true, value: 'test'}, {expected: false, value: 'TEST'}, {expected: false, value: 'ABCtestDEF'}, {expected: false, value: 'ABCTESTDEF'}, ], }, { schema: { type: 'string', pattern: '^test$', patternFlags: 'i', }, inputs: [ {expected: false, value: ''}, {expected: true, value: 'test'}, {expected: true, value: 'TEST'}, {expected: false, value: 'ABCtestDEF'}, {expected: false, value: 'ABCTESTDEF'}, ], }, { schema: { type: 'string', pattern: '*', }, inputs: [ {expected: false, value: ''}, ], }, { schema: { type: 'string', pattern: '.', patternFlags: '?', }, inputs: [ {expected: false, value: ''}, ], },
// Const tests { schema: { const: 32, }, inputs: [ {expected: true, value: 32}, {expected: false, value: 0}, {expected: false, value: '32'}, {expected: false, value: null}, {expected: false, value: {a: 'b'}}, {expected: false, value: [1, 2, 3]}, ], }, { schema: { const: '32', }, inputs: [ {expected: false, value: 32}, {expected: false, value: 0}, {expected: true, value: '32'}, {expected: false, value: null}, {expected: false, value: {a: 'b'}}, {expected: false, value: [1, 2, 3]}, ], }, { schema: { const: null, }, inputs: [ {expected: false, value: 32}, {expected: false, value: 0}, {expected: false, value: '32'}, {expected: true, value: null}, {expected: false, value: {a: 'b'}}, {expected: false, value: [1, 2, 3]}, ], }, { schema: { const: {a: 'b'}, }, inputs: [ {expected: false, value: 32}, {expected: false, value: 0}, {expected: false, value: '32'}, {expected: false, value: null}, {expected: false, value: {a: 'b'}}, {expected: false, value: [1, 2, 3]}, ], }, { schema: { const: [1, 2, 3], }, inputs: [ {expected: false, value: 32}, {expected: false, value: 0}, {expected: false, value: '32'}, {expected: false, value: null}, {expected: false, value: {a: 'b'}}, {expected: false, value: [1, 2, 3]}, ], },
// Array contains tests { schema: { type: 'array', contains: {const: 32}, }, inputs: [ {expected: false, value: []}, {expected: true, value: [32]}, {expected: true, value: [1, 32]}, {expected: true, value: [1, 32, 1]}, {expected: false, value: [33]}, {expected: false, value: [1, 33]}, {expected: false, value: [1, 33, 1]}, ], },
// Number limits tests { schema: { type: 'number', minimum: 0, }, inputs: [ {expected: false, value: -1}, {expected: true, value: 0}, {expected: true, value: 1}, ], }, { schema: { type: 'number', exclusiveMinimum: 0, }, inputs: [ {expected: false, value: -1}, {expected: false, value: 0}, {expected: true, value: 1}, ], }, { schema: { type: 'number', maximum: 0, }, inputs: [ {expected: true, value: -1}, {expected: true, value: 0}, {expected: false, value: 1}, ], }, { schema: { type: 'number', exclusiveMaximum: 0, }, inputs: [ {expected: true, value: -1}, {expected: false, value: 0}, {expected: false, value: 1}, ], },
// Integer limits tests { schema: { type: 'integer', minimum: 0, }, inputs: [ {expected: false, value: -1}, {expected: true, value: 0}, {expected: true, value: 1}, ], }, { schema: { type: 'integer', exclusiveMinimum: 0, }, inputs: [ {expected: false, value: -1}, {expected: false, value: 0}, {expected: true, value: 1}, ], }, { schema: { type: 'integer', maximum: 0, }, inputs: [ {expected: true, value: -1}, {expected: true, value: 0}, {expected: false, value: 1}, ], }, { schema: { type: 'integer', exclusiveMaximum: 0, }, inputs: [ {expected: true, value: -1}, {expected: false, value: 0}, {expected: false, value: 1}, ], }, { schema: { type: 'integer', multipleOf: 2, }, inputs: [ {expected: true, value: -2}, {expected: false, value: -1}, {expected: true, value: 0}, {expected: false, value: 1}, {expected: true, value: 2}, ], },
// Numeric type tests { schema: { type: 'number', }, inputs: [ {expected: true, value: 0}, {expected: true, value: 0.5}, {expected: true, value: 1}, {expected: false, value: '0'}, {expected: false, value: null}, {expected: false, value: []}, {expected: false, value: {}}, ], }, { schema: { type: 'integer', }, inputs: [ {expected: true, value: 0}, {expected: false, value: 0.5}, {expected: true, value: 1}, {expected: false, value: '0'}, {expected: false, value: null}, {expected: false, value: []}, {expected: false, value: {}}, ], },
// Reference tests { schema: { definitions: { example: { type: 'number', }, }, $ref: '#/definitions/example', }, inputs: [ {expected: true, value: 0}, {expected: true, value: 0.5}, {expected: true, value: 1}, {expected: false, value: '0'}, {expected: false, value: null}, {expected: false, value: []}, {expected: false, value: {}}, ], }, { schema: { definitions: { example: { type: 'integer', }, }, $ref: '#/definitions/example', }, inputs: [ {expected: true, value: 0}, {expected: false, value: 0.5}, {expected: true, value: 1}, {expected: false, value: '0'}, {expected: false, value: null}, {expected: false, value: []}, {expected: false, value: {}}, ], }, { schema: { definitions: { example: { type: 'object', additionalProperties: false, properties: { test: { $ref: '#/definitions/example', }, }, }, }, $ref: '#/definitions/example', }, inputs: [ {expected: false, value: 0}, {expected: false, value: 0.5}, {expected: false, value: 1}, {expected: false, value: '0'}, {expected: false, value: null}, {expected: false, value: []}, {expected: true, value: {}}, {expected: false, value: {test: 0}}, {expected: false, value: {test: 0.5}}, {expected: false, value: {test: 1}}, {expected: false, value: {test: '0'}}, {expected: false, value: {test: null}}, {expected: false, value: {test: []}}, {expected: true, value: {test: {}}}, {expected: true, value: {test: {test: {}}}}, {expected: true, value: {test: {test: {test: {}}}}}, ], }, ]; /* eslint-enable @stylistic/no-multi-spaces */
describe.each(data)('Schema %#', ({schema, inputs}) => { test.each(inputs)(`schemaValidate(${JSON.stringify(schema)}, $value) -> $expected`, ({expected, value}) => { const actual = schemaValidate(schema, value); expect(actual).toStrictEqual(expected); }); }); });
describe('GetValidValueOrDefault1', () => { /** @type {{schema: import('ext/json-schema').Schema, inputs: [value: unknown, expected: unknown][]}[]} */ const data = [ // Test value defaulting on objects with additionalProperties=false { schema: { type: 'object', required: ['test'], properties: { test: { type: 'string', default: 'default', }, }, additionalProperties: false, }, inputs: [ [ 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'}, ], ], },
// Test value defaulting on objects with additionalProperties=true { schema: { type: 'object', required: ['test'], properties: { test: { type: 'string', default: 'default', }, }, additionalProperties: true, }, inputs: [ [ {}, {test: 'default'}, ], [ {test: 'value'}, {test: 'value'}, ], [ {test2: 'value2'}, {test: 'default', test2: 'value2'}, ], [ {test: 'value', test2: 'value2'}, {test: 'value', test2: 'value2'}, ], ], },
// Test value defaulting on objects with additionalProperties={schema} { schema: { type: 'object', required: ['test'], properties: { test: { type: 'string', default: 'default', }, }, additionalProperties: { type: 'number', default: 10, }, }, inputs: [ [ {}, {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}, ], ], },
// Test value defaulting where hasOwnProperty is false { schema: { type: 'object', required: ['test'], properties: { test: { type: 'string', default: 'default', }, }, }, inputs: [ [ {}, {test: 'default'}, ], [ {test: 'value'}, {test: 'value'}, ], [ Object.create({test: 'value'}), {test: 'default'}, ], ], }, { schema: { type: 'object', required: ['toString'], properties: { toString: /** @type {import('ext/json-schema').SchemaObject} */ ({ type: 'string', default: 'default', }), }, }, inputs: [ [ {}, {toString: 'default'}, ], [ {toString: 'value'}, {toString: 'value'}, ], [ Object.create({toString: 'value'}), {toString: 'default'}, ], ], },
// Test enum { schema: { type: 'object', required: ['test'], properties: { test: { type: 'string', default: 'value1', enum: ['value1', 'value2', 'value3'], }, }, }, inputs: [ [ {test: 'value1'}, {test: 'value1'}, ], [ {test: 'value2'}, {test: 'value2'}, ], [ {test: 'value3'}, {test: 'value3'}, ], [ {test: 'value4'}, {test: 'value1'}, ], ], },
// Test valid vs invalid default { schema: { type: 'object', required: ['test'], properties: { test: { type: 'integer', default: 2, minimum: 1, }, }, }, inputs: [ [ {test: -1}, {test: 2}, ], ], }, { schema: { type: 'object', required: ['test'], properties: { test: { type: 'integer', default: 1, minimum: 2, }, }, }, inputs: [ [ {test: -1}, {test: -1}, ], ], },
// Test references { schema: { definitions: { example: { type: 'number', default: 0, }, }, $ref: '#/definitions/example', }, inputs: [ [ 1, 1, ], [ null, 0, ], [ 'test', 0, ], [ {test: 'value'}, 0, ], ], }, { schema: { definitions: { example: { type: 'object', additionalProperties: false, properties: { test: { $ref: '#/definitions/example', }, }, }, }, $ref: '#/definitions/example', }, inputs: [ [ 1, {}, ], [ null, {}, ], [ 'test', {}, ], [ {}, {}, ], [ {test: {}}, {test: {}}, ], [ {test: 'value'}, {test: {}}, ], [ {test: {test: {}}}, {test: {test: {}}}, ], ], }, ];
describe.each(data)('Schema %#', ({schema, inputs}) => { test.each(inputs)(`getValidValueOrDefault(${JSON.stringify(schema)}, %o) -> %o`, (value, expected) => { const actual = getValidValueOrDefault(schema, value); expect(actual).toStrictEqual(expected); }); }); });
describe('Proxy1', () => { /* eslint-disable @stylistic/no-multi-spaces */ /** @type {{schema: import('ext/json-schema').Schema, tests: {error: boolean, value?: import('ext/json-schema').Value, action: (value: import('core').SafeAny) => void}[]}[]} */ const data = [ // Object tests { schema: { type: 'object', required: ['test'], additionalProperties: false, properties: { test: { type: 'string', default: 'default', }, }, }, tests: [ {error: false, value: {test: 'default'}, action: (value) => { value.test = 'string'; }}, {error: true, value: {test: 'default'}, action: (value) => { value.test = null; }}, {error: true, value: {test: 'default'}, action: (value) => { delete value.test; }}, {error: true, value: {test: 'default'}, action: (value) => { value.test2 = 'string'; }}, {error: false, value: {test: 'default'}, action: (value) => { delete value.test2; }}, ], }, { schema: { type: 'object', required: ['test'], additionalProperties: true, properties: { test: { type: 'string', default: 'default', }, }, }, tests: [ {error: false, value: {test: 'default'}, action: (value) => { value.test = 'string'; }}, {error: true, value: {test: 'default'}, action: (value) => { value.test = null; }}, {error: true, value: {test: 'default'}, action: (value) => { delete value.test; }}, {error: false, value: {test: 'default'}, action: (value) => { value.test2 = 'string'; }}, {error: false, value: {test: 'default'}, action: (value) => { delete value.test2; }}, ], }, { schema: { type: 'object', required: ['test1'], additionalProperties: false, properties: { test1: { type: 'object', required: ['test2'], additionalProperties: false, properties: { test2: { type: 'object', required: ['test3'], additionalProperties: false, properties: { test3: { type: 'string', default: 'default', }, }, }, }, }, }, }, tests: [ {error: false, action: (value) => { value.test1.test2.test3 = 'string'; }}, {error: true, action: (value) => { value.test1.test2.test3 = null; }}, {error: true, action: (value) => { delete value.test1.test2.test3; }}, {error: true, action: (value) => { value.test1.test2 = null; }}, {error: true, action: (value) => { value.test1 = null; }}, {error: true, action: (value) => { value.test4 = 'string'; }}, {error: false, action: (value) => { delete value.test4; }}, ], },
// Array tests { schema: { type: 'array', items: { type: 'string', default: 'default', }, }, tests: [ {error: false, value: ['default'], action: (value) => { value[0] = 'string'; }}, {error: true, value: ['default'], action: (value) => { value[0] = null; }}, {error: false, value: ['default'], action: (value) => { delete value[0]; }}, {error: false, value: ['default'], action: (value) => { value[1] = 'string'; }}, { error: false, value: ['default'], action: (value) => { value[1] = 'string'; if (value.length !== 2) { throw new Error(`Invalid length; expected=2; actual=${value.length}`); } if (typeof value.push !== 'function') { throw new Error(`Invalid push; expected=function; actual=${typeof value.push}`); } }, }, ], },
// Reference tests { schema: { definitions: { example: { type: 'object', additionalProperties: false, properties: { test: { $ref: '#/definitions/example', }, }, }, }, $ref: '#/definitions/example', }, tests: [ {error: false, value: {}, action: (value) => { value.test = {}; }}, {error: false, value: {}, action: (value) => { value.test = {}; value.test.test = {}; }}, {error: false, value: {}, action: (value) => { value.test = {test: {}}; }}, {error: true, value: {}, action: (value) => { value.test = null; }}, {error: true, value: {}, action: (value) => { value.test = 'string'; }}, {error: true, value: {}, action: (value) => { value.test = {}; value.test.test = 'string'; }}, {error: true, value: {}, action: (value) => { value.test = {test: 'string'}; }}, ], }, ]; /* eslint-enable @stylistic/no-multi-spaces */
describe.each(data)('Schema %#', ({schema, tests}) => { test.each(tests)('proxy %#', ({error, value, action}) => { if (typeof value === 'undefined') { value = getValidValueOrDefault(schema, void 0); } value = structuredClone(value); expect(schemaValidate(schema, value)).toBe(true); const valueProxy = createProxy(schema, value); if (error) { expect(() => action(valueProxy)).toThrow(); } else { expect(() => action(valueProxy)).not.toThrow(); } }); });
for (const {schema, tests} of data) { for (let {error, value, action} of tests) { if (typeof value === 'undefined') { value = getValidValueOrDefault(schema, void 0); } value = structuredClone(value); expect(schemaValidate(schema, value)).toBe(true); const valueProxy = createProxy(schema, value); if (error) { expect(() => action(valueProxy)).toThrow(); } else { expect(() => action(valueProxy)).not.toThrow(); } } } });});