Files
openmaic/OpenMAIC/tests/video-export/cover-config.test.ts
2026-08-16 14:58:47 +08:00

216 lines
7.6 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, expect, it } from 'vitest';
import {
getVideoExportCoverLabels,
resolveVideoExportCta,
} from '@/lib/video-export-app/cover-config';
import type { Locale } from '@/lib/i18n';
const BIDI_CONTROL_CASES = [
['U+061C', '\u061c', '%D8%9C'],
['U+200E', '\u200e', '%E2%80%8E'],
['U+200F', '\u200f', '%E2%80%8F'],
['U+202A', '\u202a', '%E2%80%AA'],
['U+202B', '\u202b', '%E2%80%AB'],
['U+202C', '\u202c', '%E2%80%AC'],
['U+202D', '\u202d', '%E2%80%AD'],
['U+202E', '\u202e', '%E2%80%AE'],
['U+2066', '\u2066', '%E2%81%A6'],
['U+2067', '\u2067', '%E2%81%A7'],
['U+2068', '\u2068', '%E2%81%A8'],
['U+2069', '\u2069', '%E2%81%A9'],
] as const;
describe('resolveVideoExportCta', () => {
it.each([undefined, '', ' '])('defaults an absent or blank value (%j)', (raw) => {
expect(resolveVideoExportCta(raw)).toEqual({ destination: 'open.maic.chat' });
});
it.each(['off', 'OFF', ' Off '])('disables the CTA for %j', (raw) => {
expect(resolveVideoExportCta(raw)).toBeNull();
});
it.each([
['https://Example.COM/learn/', 'example.com/learn'],
['http://open.maic.chat/', 'open.maic.chat'],
['courses.example.com/start/', 'courses.example.com/start'],
])('normalizes %j to the display destination %j', (raw, destination) => {
expect(resolveVideoExportCta(raw)).toEqual({ destination });
});
it.each([
'ftp://example.com',
'javascript:alert(1)',
'https://user:pass@example.com',
'https://@example.com',
'https://example.com/path?source=video',
'https://example.com/path?',
'https://example.com/path#section',
'https://example.com/path#',
'example.com/path?source=video',
'example.com/path#section',
'example.com/\u0000path',
])('rejects unsafe destination %j', (raw) => {
expect(resolveVideoExportCta(raw)).toBeNull();
});
it('accepts 96 raw characters and rejects 97', () => {
const at96 = `example.com/${'a'.repeat(84)}`;
const at97 = `${at96}a`;
expect(at96).toHaveLength(96);
expect(at97).toHaveLength(97);
expect(resolveVideoExportCta(at96)).toEqual({ destination: at96 });
expect(resolveVideoExportCta(at97)).toBeNull();
});
it('keeps an accepted 96-character Unicode path human-readable and bounded', () => {
const at96 = `a.co/${'学'.repeat(91)}`;
expect(at96).toHaveLength(96);
expect(resolveVideoExportCta(at96)).toEqual({ destination: at96 });
expect(resolveVideoExportCta(at96)?.destination).toHaveLength(96);
});
it('preserves a Unicode IDN while lowercasing only ASCII host letters', () => {
expect(resolveVideoExportCta('https://例え.Example/学ぶ/')).toEqual({
destination: '例え.example/学ぶ',
});
expect(resolveVideoExportCta('https://İ.Example/Path')).toEqual({
destination: 'İ.example/Path',
});
});
it('decodes an unambiguous percent-encoded path for display', () => {
expect(resolveVideoExportCta('https://Example.COM/%E5%AD%A6%E3%81%B6/')).toEqual({
destination: 'example.com/学ぶ',
});
});
it('normalizes ASCII host casing and trailing slashes without changing path casing', () => {
expect(resolveVideoExportCta('HTTPS://Example.COM/LEARN///')).toEqual({
destination: 'example.com/LEARN',
});
});
it.each([
['https://Example.COM/a%2Fb', 'example.com/a%2Fb'],
['https://Example.COM/a%5Cb', 'example.com/a%5Cb'],
['https://Example.COM/a%3Fb', 'example.com/a%3Fb'],
['https://Example.COM/a%23b', 'example.com/a%23b'],
['https://Example.COM/a%25b', 'example.com/a%25b'],
])('preserves an encoded HTTP path delimiter in %j', (raw, destination) => {
expect(resolveVideoExportCta(raw)).toEqual({ destination });
});
it.each(['https://example.com/%ZZ', 'https://example.com\\different-target'])(
'rejects ambiguous or malformed display destination %j',
(raw) => {
expect(resolveVideoExportCta(raw)).toBeNull();
},
);
it.each(BIDI_CONTROL_CASES)('rejects raw bidi control %s in the host', (_, raw) => {
expect(resolveVideoExportCta(`https://safe${raw}.example/learn`)).toBeNull();
});
it.each(BIDI_CONTROL_CASES)('rejects raw bidi control %s in the path', (_, raw) => {
expect(resolveVideoExportCta(`https://safe.example/a${raw}b`)).toBeNull();
});
it.each(BIDI_CONTROL_CASES)(
'rejects percent-encoded bidi control %s in the host',
(_, __, encoded) => {
expect(resolveVideoExportCta(`https://safe${encoded}.example/learn`)).toBeNull();
},
);
it.each(BIDI_CONTROL_CASES)(
'rejects percent-encoded bidi control %s in the path',
(_, __, encoded) => {
expect(resolveVideoExportCta(`https://safe.example/a${encoded}b`)).toBeNull();
},
);
it('preserves raw and percent-encoded ZWJ emoji sequences', () => {
expect(resolveVideoExportCta('https://example.com/👩‍💻')).toEqual({
destination: 'example.com/👩‍💻',
});
expect(resolveVideoExportCta('https://example.com/%F0%9F%91%A9%E2%80%8D%F0%9F%92%BB')).toEqual({
destination: 'example.com/👩‍💻',
});
});
});
describe('getVideoExportCoverLabels', () => {
const expectedCtaLabels: Record<
Locale,
Pick<
ReturnType<typeof getVideoExportCoverLabels>,
'quizCtaPrompt' | 'pblCtaPrompt' | 'ctaVisit'
>
> = {
'en-US': {
quizCtaPrompt: 'Want to try an interactive quiz?',
pblCtaPrompt: 'Want to explore project-based learning?',
ctaVisit: 'Visit',
},
'zh-CN': {
quizCtaPrompt: '想亲自体验互动测验?',
pblCtaPrompt: '想亲自参与项目学习?',
ctaVisit: '访问',
},
'zh-TW': {
quizCtaPrompt: '想親自體驗互動測驗?',
pblCtaPrompt: '想親自參與專題式學習?',
ctaVisit: '前往',
},
'ja-JP': {
quizCtaPrompt: 'インタラクティブなクイズを体験しませんか?',
pblCtaPrompt: 'プロジェクト型学習を体験しませんか?',
ctaVisit: 'アクセス',
},
'ko-KR': {
quizCtaPrompt: '인터랙티브 퀴즈를 직접 체험해 보세요',
pblCtaPrompt: '프로젝트 기반 학습을 직접 체험해 보세요',
ctaVisit: '방문',
},
'es-MX': {
quizCtaPrompt: '¿Quieres probar un cuestionario interactivo?',
pblCtaPrompt: '¿Quieres explorar el aprendizaje basado en proyectos?',
ctaVisit: 'Visita',
},
'fr-FR': {
quizCtaPrompt: 'Envie d’essayer un quiz interactif ?',
pblCtaPrompt: 'Envie d’explorer l’apprentissage par projet ?',
ctaVisit: 'Découvrir',
},
'vi-VN': {
quizCtaPrompt: 'Muốn thử một bài trắc nghiệm tương tác?',
pblCtaPrompt: 'Muốn khám phá học tập theo dự án?',
ctaVisit: 'Ghé thăm',
},
'pt-BR': {
quizCtaPrompt: 'Quer experimentar um quiz interativo?',
pblCtaPrompt: 'Quer explorar a aprendizagem baseada em projetos?',
ctaVisit: 'Acesse',
},
'ru-RU': {
quizCtaPrompt: 'Хотите пройти интерактивный тест?',
pblCtaPrompt: 'Хотите попробовать проектное обучение?',
ctaVisit: 'Посетите',
},
'ar-SA': {
quizCtaPrompt: 'هل تريد تجربة اختبار تفاعلي؟',
pblCtaPrompt: 'هل تريد استكشاف التعلم القائم على المشاريع؟',
ctaVisit: 'تفضل بزيارة',
},
};
it.each(Object.entries(expectedCtaLabels) as Array<[Locale, (typeof expectedCtaLabels)[Locale]]>)(
'returns the approved CTA strings for %s',
(locale, expected) => {
expect(getVideoExportCoverLabels(locale)).toMatchObject(expected);
},
);
});