import { describe, expect, it } from 'vitest';
import {
renderQuizQuestionListSurface,
type QuizQuestionListContent,
} from '@/lib/video-export/emit-hyperframes/quiz-question-list';
import {
planQuizScriptFonts,
type QuizScriptFont,
} from '@/lib/video-export/emit-hyperframes/quiz-script-font-plan';
describe('Quiz script-font plan', () => {
it('does not select vendored script fonts for Latin-only rendered markup', () => {
expect(planQuizScriptFonts(['Find the value of x.'])).toEqual({
scripts: [],
measurementCss: '',
exportCss: '',
assets: [],
licenses: [],
requiredFontLoads: [],
});
});
it('does not treat a decomposed Latin accent as Cyrillic text', () => {
expect(planQuizScriptFonts(['Cafe\u0301']).scripts).toEqual([]);
});
it.each(['Latin ؟ punctuation', '؟،؛ـ'])(
'selects Arabic for extension-only rendered markup: %s',
(markup) => {
const plan = planQuizScriptFonts([`${markup}`]);
expect(plan.scripts).toEqual(['arabic']);
expect(plan.assets.map(({ path }) => path)).toEqual([
'assets/fonts/noto-sans-arabic-arabic-400-normal.woff2',
]);
},
);
it('selects both Noto Sans Cyrillic subsets and their required load sample for Cyrillic markup', () => {
const plan = planQuizScriptFonts(['Решите уравнение Ёж']);
expect(plan.scripts).toEqual(['cyrillic']);
expect(plan.measurementCss).toContain('font-family:"OpenMAIC Noto Sans Cyrillic"');
expect(plan.measurementCss).toContain('/vendor/video-export/fonts/');
expect(plan.exportCss).toContain('assets/fonts/');
expect(plan.assets.map(({ path }) => path)).toEqual([
'assets/fonts/noto-sans-cyrillic-400-normal.woff2',
'assets/fonts/noto-sans-cyrillic-ext-400-normal.woff2',
]);
expect(plan.licenses.map(({ path }) => path)).toEqual(['LICENSES/Noto-Sans-OFL-1.1.txt']);
expect(plan.requiredFontLoads).toEqual([
{ family: 'OpenMAIC Noto Sans Cyrillic', text: 'Привет Ёж Ԁ' },
]);
expect(plan.requiredFontLoads[0].text).toMatch(/[\u0500-\u052f]/u);
});
it('selects Noto Sans Arabic and its required load sample for Arabic markup', () => {
const plan = planQuizScriptFonts(['حل المسألة']);
expect(plan.scripts).toEqual(['arabic']);
expect(plan.measurementCss).toContain('font-family:"OpenMAIC Noto Sans Arabic"');
expect(plan.measurementCss).toContain('/vendor/video-export/fonts/');
expect(plan.exportCss).toContain('assets/fonts/');
expect(plan.assets.map(({ path }) => path)).toEqual([
'assets/fonts/noto-sans-arabic-arabic-400-normal.woff2',
]);
expect(plan.licenses.map(({ path }) => path)).toEqual([
'LICENSES/Noto-Sans-Arabic-OFL-1.1.txt',
]);
expect(plan.requiredFontLoads).toEqual([
{ family: 'OpenMAIC Noto Sans Arabic', text: 'العربية' },
]);
});
it('uses a stable Cyrillic-before-Arabic union across separate rendered surfaces', () => {
const plan = planQuizScriptFonts(['العربية', 'Русский']);
expect(plan.scripts).toEqual(['cyrillic', 'arabic']);
expect(plan.assets.map(({ path }) => path)).toEqual([
'assets/fonts/noto-sans-cyrillic-400-normal.woff2',
'assets/fonts/noto-sans-cyrillic-ext-400-normal.woff2',
'assets/fonts/noto-sans-arabic-arabic-400-normal.woff2',
]);
expect(new Set(plan.assets.map(({ path }) => path)).size).toBe(plan.assets.length);
expect(new Set(plan.licenses.map(({ path }) => path)).size).toBe(plan.licenses.length);
expect(new Set(plan.requiredFontLoads.map(({ family }) => family)).size).toBe(
plan.requiredFontLoads.length,
);
});
it.each([
{
name: 'outside the declared Cyrillic ranges',
character: '\u1d2b',
script: 'cyrillic',
family: 'OpenMAIC Noto Sans Cyrillic',
sample: 'Привет Ёж Ԁ',
},
{
name: 'declared by Fontsource metadata but absent from the Cyrillic WOFF2 cmap',
character: '\u1c89',
script: 'cyrillic',
family: 'OpenMAIC Noto Sans Cyrillic',
sample: 'Привет Ёж Ԁ',
},
{
name: 'declared by Fontsource metadata but absent from the Arabic WOFF2 cmap',
character: '\u0897',
script: 'arabic',
family: 'OpenMAIC Noto Sans Arabic',
sample: 'العربية',
},
] as const)('adds a fail-closed load for $name', ({ character, script, family, sample }) => {
const plan = planQuizScriptFonts([`Unsupported character: ${character}`]);
expect(plan.scripts).toEqual([script]);
expect(plan.requiredFontLoads).toEqual([
{ family, text: sample },
{ family, text: character },
]);
});
it('selects only the localized question-type label that the rendered question uses', () => {
const cases: {
question: QuizQuestionListContent['questions'][number];
labels: { singleChoice: string; multipleChoice: string };
expected: QuizScriptFont[];
}[] = [
{
question: { id: 'single', type: 'single', question: 'Visible prompt', options: [] },
labels: { singleChoice: 'Кириллица', multipleChoice: 'غير مستخدم' },
expected: ['cyrillic'],
},
{
question: { id: 'multiple', type: 'multiple', question: 'Visible prompt', options: [] },
labels: { singleChoice: 'Не используется', multipleChoice: 'العربية' },
expected: ['arabic'],
},
];
for (const { question, labels, expected } of cases) {
const surfaceMarkup = renderQuizQuestionListSurface(
{ title: 'Visible Latin title', questions: [question] },
{ ...labels, shortAnswer: 'Short answer', answerPlaceholder: 'Write your answer' },
'ltr',
);
expect(planQuizScriptFonts([surfaceMarkup]).scripts).toEqual(expected);
}
});
it('does not select a font for authored non-visible data omitted by the rendered Quiz markup', () => {
const surfaceMarkup = renderQuizQuestionListSurface(
{
title: 'Visible Latin title',
questions: [
{
type: 'single',
question: 'Visible Latin question',
options: [{ value: 'A', label: 'Visible choice' }],
answer: ['إجابة مخفية'],
analysis: 'Скрытый анализ',
} as never,
],
},
{
singleChoice: 'Single choice',
multipleChoice: 'Multiple choice',
shortAnswer: 'Short answer',
answerPlaceholder: 'Write your answer',
},
'ltr',
);
expect(surfaceMarkup).not.toContain('إجابة مخفية');
expect(surfaceMarkup).not.toContain('Скрытый анализ');
expect(planQuizScriptFonts([surfaceMarkup]).scripts).toEqual([]);
});
});