mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-09-25 14:09:03 -04:00

the #shared alias used by Nuxt cannot be easily disabled and to prevent breackage with jiti, we make use of it
232 lines
9.8 KiB
TypeScript
232 lines
9.8 KiB
TypeScript
import type { SyncExpectationResult } from '@vitest/expect';
|
|
import { describe, expect, test } from 'vitest';
|
|
|
|
import { normaliseKey } from '#shared/buildPronoun.ts';
|
|
import { Example } from '#shared/language/examples.ts';
|
|
import type { VariantsFromBaseConverter } from '#shared/language/grammarTables.ts';
|
|
import { availableGenders, gendersWithNumerus, numeri } from '#shared/nouns.ts';
|
|
import type { NounsData } from '#shared/nouns.ts';
|
|
import type { Config } from '~~/locale/config.ts';
|
|
import type { NounTemplatesData, PronounGroupData, PronounExamplesData, PronounData } from '~~/locale/data.ts';
|
|
import allLocales from '~~/locale/locales.ts';
|
|
import { loadSuml, loadTsv } from '~~/server/loader.ts';
|
|
|
|
function toHaveValidMorphemes(actual: string, morphemes: string[]): SyncExpectationResult {
|
|
const containedMorphemes = Example.parse(actual).parts
|
|
.filter((part) => typeof part !== 'string' && part.type === 'morpheme')
|
|
.map((part) => part.morpheme);
|
|
const unknownMorphemes = containedMorphemes.filter((morpheme) => !morphemes.includes(morpheme));
|
|
if (unknownMorphemes.length > 0) {
|
|
return {
|
|
message: () => `expected example '${actual}' to have valid morphemes,` +
|
|
` but these are unknown:\n${unknownMorphemes.join(', ')}`,
|
|
pass: false,
|
|
};
|
|
} else {
|
|
return {
|
|
message: () => 'expected example to have invalid morphemes',
|
|
pass: true,
|
|
};
|
|
}
|
|
}
|
|
|
|
interface CustomMatchers<R> {
|
|
toHaveValidMorphemes(morphemes: string[]): R;
|
|
}
|
|
|
|
declare module 'vitest' {
|
|
interface Assertion<T> extends CustomMatchers<T> {}
|
|
}
|
|
|
|
expect.extend({ toHaveValidMorphemes });
|
|
|
|
describe.each(allLocales)('data files of $code', async ({ code }) => {
|
|
const config = await loadSuml<Config>(`locale/${code}/config.suml`);
|
|
|
|
const pronouns = await loadTsv<PronounData<string>>(`locale/${code}/pronouns/pronouns.tsv`);
|
|
const pronounGroups = await loadTsv<PronounGroupData>(`locale/${code}/pronouns/pronounGroups.tsv`);
|
|
const examples = await loadTsv<PronounExamplesData>(`locale/${code}/pronouns/examples.tsv`);
|
|
|
|
test('pronouns/pronouns.tsv match schema', async () => {
|
|
if (pronouns.length === 0) {
|
|
return;
|
|
}
|
|
const required = [
|
|
'key',
|
|
'description',
|
|
'normative',
|
|
'plural',
|
|
'pluralHonorific',
|
|
'pronounceable',
|
|
...config.pronouns.morphemes ?? [],
|
|
];
|
|
const optional = ['history', 'thirdForm', 'smallForm', 'sourcesInfo', 'hidden'];
|
|
const actual = Object.keys(pronouns[0]);
|
|
expect(actual).toEqual(expect.arrayContaining(required));
|
|
expect([...required, ...optional]).toEqual(expect.arrayContaining(actual));
|
|
});
|
|
test('pronouns/pronouns.tsv have unique keys', () => {
|
|
const keys = new Set();
|
|
for (const pronoun of pronouns) {
|
|
const pronounKeys = pronoun.key
|
|
.replace(/،/g, ',')
|
|
.split(',')
|
|
.map((key) => normaliseKey(key));
|
|
for (const key of pronounKeys) {
|
|
expect(keys).not.toContain(key);
|
|
keys.add(key);
|
|
}
|
|
}
|
|
});
|
|
test('pronouns/pronounGroups.tsv match schema', () => {
|
|
if (pronounGroups.length === 0) {
|
|
return;
|
|
}
|
|
const required = ['name', 'pronouns'];
|
|
const optional = ['key', 'description', 'hidden'];
|
|
const actual = Object.keys(pronounGroups[0]);
|
|
expect(actual).toEqual(expect.arrayContaining(required));
|
|
expect([...required, ...optional]).toEqual(expect.arrayContaining(actual));
|
|
});
|
|
test('pronouns/pronounGroups.tsv reference pronouns by canonical name', () => {
|
|
const knownPronounNames = pronouns.map((pronoun) => {
|
|
return pronoun.key.replace(/،/g, ',').split(',')[0];
|
|
});
|
|
for (const pronounGroup of pronounGroups) {
|
|
const pronounNames = pronounGroup.pronouns?.replace(/،/g, ',').split(',') ?? [];
|
|
for (const pronounName of pronounNames) {
|
|
expect(knownPronounNames).toContain(pronounName);
|
|
}
|
|
}
|
|
});
|
|
test('pronouns/examples.tsv match schema', () => {
|
|
if (examples.length === 0) {
|
|
return;
|
|
}
|
|
const required = [
|
|
'singular',
|
|
];
|
|
if (config.pronouns.plurals) {
|
|
required.push('plural');
|
|
}
|
|
if (config.pronouns.honorifics) {
|
|
required.push('isHonorific');
|
|
}
|
|
const actual = Object.keys(examples[0]);
|
|
expect(actual).toEqual(required);
|
|
});
|
|
test('pronouns/examples.tsv contain valid morphemes', async () => {
|
|
for (const example of examples) {
|
|
expect(example.singular).toHaveValidMorphemes(config.pronouns.morphemes ?? []);
|
|
if (example.plural) {
|
|
expect(example.plural).toHaveValidMorphemes(config.pronouns.morphemes ?? []);
|
|
}
|
|
}
|
|
});
|
|
test('pronouns/examples.tsv contains plural examples when language has plurals', () => {
|
|
const hasExamplesWithPlurals = examples.some((example) => {
|
|
return example.plural && example.plural !== example.singular;
|
|
});
|
|
expect(hasExamplesWithPlurals).toBe(!!config.pronouns.plurals);
|
|
});
|
|
test('pronouns/examples.tsv contains honorific examples when language has honorifics', () => {
|
|
const hasExamplesWithHonorifics = examples.some((example) => example.isHonorific);
|
|
expect(hasExamplesWithHonorifics).toBe(!!config.pronouns.honorifics);
|
|
});
|
|
|
|
if (config.nouns.templates?.enabled) {
|
|
const nounTemplates = await loadTsv<NounTemplatesData>(`locale/${code}/nouns/nounTemplates.tsv`);
|
|
|
|
describe('nouns/nounTemplates.tsv', () => {
|
|
test('match schema', () => {
|
|
if (nounTemplates.length === 0) {
|
|
return;
|
|
}
|
|
const required = [...availableGenders(config),
|
|
...availableGenders(config).map((gender) => `${gender}Pl`)];
|
|
const actual = Object.keys(nounTemplates[0]);
|
|
expect(actual).toEqual(required);
|
|
});
|
|
test('contains templates when templates are enabled', () => {
|
|
expect(nounTemplates.length > 0).toBe(config.nouns.templates?.enabled);
|
|
});
|
|
test('have exactly one hyphen as placeholder for root', () => {
|
|
for (const template of nounTemplates) {
|
|
for (const genderWithNumerus of gendersWithNumerus) {
|
|
const actual = template[genderWithNumerus];
|
|
if (typeof actual === 'string') {
|
|
expect(actual).toMatch(/^(?:[^-]*-[^-]*(?:\/|$))+/);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
let nounsData: NounsData | undefined;
|
|
try {
|
|
nounsData = await loadSuml<NounsData>(`locale/${code}/nouns/nounsData.suml`);
|
|
} catch (error) {
|
|
nounsData = undefined;
|
|
}
|
|
|
|
if (config.nouns.enabled && config.nouns.declension) {
|
|
test('config.nouns.declension.enabled requires properties in nouns/nounsData.suml', () => {
|
|
expect(nounsData?.declensions).toBeDefined();
|
|
});
|
|
}
|
|
if (config.nouns.enabled && config.nouns.conventions?.enabled) {
|
|
test('config.nouns.conventions.enabled requires properties in nouns/nounsData.suml', () => {
|
|
expect(nounsData?.morphemes).toBeDefined();
|
|
expect(nounsData?.conventions).toBeDefined();
|
|
expect(nounsData?.groups).toBeDefined();
|
|
expect(nounsData?.classes).toBeDefined();
|
|
expect(nounsData?.classExample).toBeDefined();
|
|
expect(nounsData?.examples).toBeDefined();
|
|
});
|
|
}
|
|
|
|
if (nounsData) {
|
|
describe('nouns/nounsData.suml', () => {
|
|
if (nounsData.grammarTables) {
|
|
test('grammar tables have valid variant types', async () => {
|
|
const grammarTableVariantsConverter = (await import(`~~/locale/${code}/language/grammarTableVariantsConverter.ts`)).default as VariantsFromBaseConverter;
|
|
|
|
for (const grammarTable of nounsData.grammarTables!) {
|
|
for (const section of grammarTable.sections) {
|
|
if (!Array.isArray(section.variants)) {
|
|
expect(Object.keys(grammarTableVariantsConverter)).toContain(section.variants.type);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
if (nounsData.conventions) {
|
|
test('conventions have valid morphemes', () => {
|
|
for (const convention of Object.values(nounsData.conventions!)) {
|
|
expect(nounsData.morphemes)
|
|
.toEqual(expect.arrayContaining(Object.keys(convention.morphemes)));
|
|
}
|
|
});
|
|
}
|
|
if (nounsData.conventions && nounsData.groups) {
|
|
test('convention groups reference conventions by key', () => {
|
|
for (const group of Object.values(nounsData.groups!)) {
|
|
expect(Object.keys(nounsData.conventions!)).toEqual(expect.arrayContaining(group.conventions));
|
|
}
|
|
});
|
|
}
|
|
if (nounsData.cases && nounsData.declensions) {
|
|
test('declensions have valid cases', () => {
|
|
for (const declension of Object.values(nounsData.declensions!)) {
|
|
for (const numerus of numeri) {
|
|
expect(Object.keys(nounsData.cases!))
|
|
.toEqual(expect.arrayContaining(Object.keys(declension[numerus] ?? {})));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
});
|