(test) check that grammar tables contain valid morphemes

This commit is contained in:
Valentyne Stigloher 2025-05-01 11:39:59 +02:00
parent db8c3c97f3
commit 22161f04d7
5 changed files with 115 additions and 46 deletions

View File

@ -1,8 +1,12 @@
<script setup lang="ts">
import { loadGrammarTableVariantsConverter } from '~/src/data.ts';
import type { Example, ExampleValues } from '~/src/language/examples.ts';
import type { GrammarTableDefinition, Variant, SectionDefinition } from '~/src/language/grammarTables.ts';
import { toMorphemeValue } from '~/src/language/morphemes.ts';
import {
type GrammarTableDefinition,
type Variant,
type SectionDefinition,
expandVariantsForSection,
} from '~/src/language/grammarTables.ts';
const props = defineProps<{
grammarTable: GrammarTableDefinition;
@ -12,51 +16,15 @@ const props = defineProps<{
const variantsFromBaseConverter = await loadGrammarTableVariantsConverter();
const expandVariantsForSection = (sectionVariants: SectionDefinition['variants']): Variant[] => {
if (Array.isArray(sectionVariants)) {
return sectionVariants.map((sectionVariant) => {
const cells = sectionVariant.cells.map((cellDefinition) => {
if (cellDefinition === null) {
return [];
}
if (typeof cellDefinition === 'string') {
return [{ morpheme: cellDefinition }];
}
const cellPartDefinitions = Array.isArray(cellDefinition) ? cellDefinition : [cellDefinition];
return cellPartDefinitions
.map((cellPartDefinition) => {
if ('singular' in cellPartDefinition) {
return cellPartDefinition[!props.exampleValues.plural ? 'singular' : 'plural'];
}
return cellPartDefinition;
})
.map((cellPartDefinition) => ({
...cellPartDefinition,
prefix: cellPartDefinition.prefix ? toMorphemeValue(cellPartDefinition.prefix) : undefined,
suffix: cellPartDefinition.suffix ? toMorphemeValue(cellPartDefinition.suffix) : undefined,
highlightsMorphemes: cellPartDefinition.highlightsMorphemes
? new Set(cellPartDefinition.highlightsMorphemes)
: undefined,
}));
});
return { ...sectionVariant, cells };
});
} else {
if (Object.hasOwn(variantsFromBaseConverter, sectionVariants.type)) {
return variantsFromBaseConverter[sectionVariants.type](sectionVariants);
}
throw new Error(`variant type ${sectionVariants.type} is unknown`);
}
};
const buildVariantsForSection = (
sectionVariants: SectionDefinition['variants'],
): Variant[] => {
return expandVariantsForSection(sectionVariants).filter((variant) => {
return variant.cells.flatMap((cell) => cell).some((cellPart) => {
return props.exampleValues.morphemeValues.getSpelling(cellPart.morpheme) !== undefined;
return expandVariantsForSection(sectionVariants, variantsFromBaseConverter, props.exampleValues)
.filter((variant) => {
return variant.cells.flatMap((cell) => cell).some((cellPart) => {
return props.exampleValues.morphemeValues.getSpelling(cellPart.morpheme) !== undefined;
});
});
});
};
const sections = computed(() => {

View File

@ -23,9 +23,9 @@ export const loadTranslations = async (locale?: string): Promise<Translations> =
return (await import(`~/locale/${locale ?? getLocale()}/translations.suml`)).default;
};
export const loadGrammarTableVariantsConverter = async () => {
export const loadGrammarTableVariantsConverter = async (locale?: string) => {
try {
return (await import(`~/locale/${getLocale()}/language/grammarTableVariantsConverter.ts`)).default as
return (await import(`~/locale/${locale ?? getLocale()}/language/grammarTableVariantsConverter.ts`)).default as
VariantsFromBaseConverter;
} catch (error) {
return {};

View File

@ -1,4 +1,5 @@
import type { MorphemeValue } from '~/src/language/morphemes.ts';
import type { ExampleValues } from '~/src/language/examples.ts';
import { type MorphemeValue, toMorphemeValue } from '~/src/language/morphemes.ts';
export type GrammarTablesDefinition = (GrammarTableDefinition | string)[]
| { simple: (GrammarTableDefinition | string)[]; comprehensive: (GrammarTableDefinition | string)[] };
@ -56,3 +57,44 @@ export interface CellPart {
prefix?: MorphemeValue;
suffix?: MorphemeValue;
}
export const expandVariantsForSection = (
sectionVariants: SectionDefinition['variants'],
variantsFromBaseConverter: VariantsFromBaseConverter,
exampleValues: ExampleValues,
): Variant[] => {
if (Array.isArray(sectionVariants)) {
return sectionVariants.map((sectionVariant) => {
const cells = sectionVariant.cells.map((cellDefinition) => {
if (cellDefinition === null) {
return [];
}
if (typeof cellDefinition === 'string') {
return [{ morpheme: cellDefinition }];
}
const cellPartDefinitions = Array.isArray(cellDefinition) ? cellDefinition : [cellDefinition];
return cellPartDefinitions
.map((cellPartDefinition) => {
if ('singular' in cellPartDefinition) {
return cellPartDefinition[!exampleValues.plural ? 'singular' : 'plural'];
}
return cellPartDefinition;
})
.map((cellPartDefinition) => ({
...cellPartDefinition,
prefix: cellPartDefinition.prefix ? toMorphemeValue(cellPartDefinition.prefix) : undefined,
suffix: cellPartDefinition.suffix ? toMorphemeValue(cellPartDefinition.suffix) : undefined,
highlightsMorphemes: cellPartDefinition.highlightsMorphemes
? new Set(cellPartDefinition.highlightsMorphemes)
: undefined,
}));
});
return { ...sectionVariant, cells };
});
} else {
if (Object.hasOwn(variantsFromBaseConverter, sectionVariants.type)) {
return variantsFromBaseConverter[sectionVariants.type](sectionVariants);
}
throw new Error(`variant type ${sectionVariants.type} is unknown`);
}
};

View File

@ -0,0 +1,49 @@
import { expect } from 'vitest';
import type { ExampleValues } from '~/src/language/examples.ts';
import { expandVariantsForSection } from '~/src/language/grammarTables.ts';
import type {
CellPart,
GrammarTableDefinition,
GrammarTablesDefinition, VariantsFromBaseConverter,
} from '~/src/language/grammarTables.ts';
import { MorphemeValues } from '~/src/language/morphemes.ts';
const getGrammarTableDefinitions = (grammarTablesDefinition: GrammarTablesDefinition): GrammarTableDefinition[] => {
let grammarTableDefinitions: (GrammarTableDefinition | string)[];
if (Array.isArray(grammarTablesDefinition)) {
grammarTableDefinitions = grammarTablesDefinition;
} else {
grammarTableDefinitions = [...grammarTablesDefinition.simple, ...grammarTablesDefinition.comprehensive];
}
return grammarTableDefinitions.filter((grammarTableDefinition) => typeof grammarTableDefinition !== 'string');
};
const getCellParts = (
grammarTablesDefinition: GrammarTablesDefinition,
variantsFromBaseConverter: VariantsFromBaseConverter,
exampleValues: ExampleValues,
): CellPart[] => {
return getGrammarTableDefinitions(grammarTablesDefinition)
.flatMap((grammarTableDefinition) => grammarTableDefinition.sections)
.flatMap((sectionDefinition) => {
return expandVariantsForSection(sectionDefinition.variants, variantsFromBaseConverter, exampleValues);
})
.flatMap((variant) => variant.cells)
.flatMap((cell) => cell);
};
export const checkContainsValidMorphemes = (
grammarTablesDefinition: GrammarTablesDefinition,
variantsFromBaseConverter: VariantsFromBaseConverter,
morphemes: string[],
) => {
for (const plural of [false, true]) {
const exampleValues = { morphemeValues: new MorphemeValues({}), plural };
const cellParts = getCellParts(grammarTablesDefinition, variantsFromBaseConverter, exampleValues);
for (const cellPart of cellParts) {
expect(morphemes).toContain(cellPart.morpheme);
expect(morphemes).toEqual(expect.arrayContaining([...cellPart.highlightsMorphemes ?? []]));
}
}
};

View File

@ -5,11 +5,14 @@ import type { PronounData } from '~/locale/data.ts';
import allLocales from '~/locale/locales.ts';
import { loadSuml, loadTsv } from '~/server/loader.ts';
import { parsePronouns } from '~/src/buildPronoun.ts';
import { loadGrammarTableVariantsConverter } from '~/src/data.ts';
import { checkContainsValidMorphemes } from '~/test/language/grammarTables.ts';
describe.each(allLocales)('config for $code', async ({ code }) => {
const config = await loadSuml<Config>(`locale/${code}/config.suml`);
const pronounsRaw = await loadTsv<PronounData<string>>(`locale/${code}/pronouns/pronouns.tsv`);
const pronouns = parsePronouns(config, pronounsRaw);
const variantsFromBaseConverter = await loadGrammarTableVariantsConverter(code);
test('pronouns.generator.startPronoun must be a valid pronoun', () => {
if (!config.pronouns.enabled || !config.pronouns.generator.enabled) {
@ -29,4 +32,11 @@ describe.each(allLocales)('config for $code', async ({ code }) => {
}
expect(config.pronouns.morphemes).toEqual(expect.arrayContaining(Object.keys(config.pronouns.emoji.morphemes)));
});
test('pronouns.grammarTables contain valid morphemes', () => {
if (!config.pronouns.enabled || !config.pronouns.grammarTables) {
return;
}
checkContainsValidMorphemes(config.pronouns.grammarTables, variantsFromBaseConverter, config.pronouns.morphemes);
});
});