Files
wehub-resource-sync c48612c494
CI / E2E Tests (push) Has been cancelled
CI / Lint, Typecheck & Unit Tests (push) Has been cancelled
Docs Build / Build docs site (push) Has been cancelled
Publish @openmaic packages / Build, validate & publish (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:03:23 +08:00

145 lines
5.5 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
isLikelyStandaloneMathText,
parseQuizMathText,
renderQuizMathText,
} from '@/lib/quiz/math-text';
describe('parseQuizMathText', () => {
it('splits explicit inline math from surrounding prose', () => {
const segments = parseQuizMathText('Solve $x^2=4$ before moving on.');
expect(segments).toHaveLength(3);
expect(segments[0]).toEqual({ type: 'text', value: 'Solve ' });
expect(segments[1]).toMatchObject({ type: 'math', value: 'x^2=4', displayMode: false });
expect(segments[1].type === 'math' ? segments[1].html : '').toContain('katex');
expect(segments[2]).toEqual({ type: 'text', value: ' before moving on.' });
});
it('renders explicit inline math with subtraction', () => {
const segments = parseQuizMathText('Solve $x-2$ before moving on.');
expect(segments).toHaveLength(3);
expect(segments[1]).toMatchObject({ type: 'math', value: 'x-2', displayMode: false });
});
it('supports display delimiters', () => {
const segments = parseQuizMathText('$$\\frac{1}{2}$$');
expect(segments).toHaveLength(1);
expect(segments[0]).toMatchObject({ type: 'math', value: '\\frac{1}{2}', displayMode: true });
});
it('leaves escaped dollars as text', () => {
const segments = parseQuizMathText('Cost is \\$5, not math.');
expect(segments).toEqual([{ type: 'text', value: 'Cost is \\$5, not math.' }]);
});
it('does not treat ordinary currency as dollar-delimited math', () => {
const segments = parseQuizMathText('Cost is $5 and $7');
expect(segments).toEqual([{ type: 'text', value: 'Cost is $5 and $7' }]);
});
it('falls back to the original delimited text when KaTeX rejects it', () => {
const segments = parseQuizMathText('Bad $\\definitelyunknown{1}$ input');
expect(segments).toEqual([{ type: 'text', value: 'Bad $\\definitelyunknown{1}$ input' }]);
});
});
describe('isLikelyStandaloneMathText', () => {
it('recognizes delimiter-free algebra like the issue report', () => {
expect(isLikelyStandaloneMathText('a(x-2)+b(2-x)^2=a(x-2)+b(x-2)^2')).toBe(true);
});
it('does not classify ordinary quiz prose as math', () => {
expect(isLikelyStandaloneMathText('Chapter 2 review question')).toBe(false);
});
it('does not classify short instruction prompts as standalone math', () => {
expect(isLikelyStandaloneMathText('Solve x=2')).toBe(false);
expect(isLikelyStandaloneMathText('Simplify x^2')).toBe(false);
});
it('does not classify prose that mentions a LaTeX command as standalone math', () => {
expect(isLikelyStandaloneMathText('Use \\sqrt{2} not 2 here')).toBe(false);
});
it('does not classify code-like answer options as standalone math', () => {
expect(isLikelyStandaloneMathText('i = i + 1')).toBe(false);
expect(isLikelyStandaloneMathText('Answer = True')).toBe(false);
});
it('still recognizes delimiter-free LaTeX command expressions', () => {
expect(isLikelyStandaloneMathText('\\sqrt{2}')).toBe(true);
});
it('does not classify slash, protocol, date, or hyphenated prose as math', () => {
expect(isLikelyStandaloneMathText('A/B test')).toBe(false);
expect(isLikelyStandaloneMathText('HTTP/2')).toBe(false);
expect(isLikelyStandaloneMathText('COVID-19')).toBe(false);
expect(isLikelyStandaloneMathText('2026-07-01')).toBe(false);
});
});
describe('renderQuizMathText', () => {
it('renders delimiter-free algebra as one math segment', () => {
const segments = renderQuizMathText('a(x-2)+b(2-x)^2=a(x-2)+b(x-2)^2');
expect(segments).toHaveLength(1);
expect(segments[0]).toMatchObject({
type: 'math',
value: 'a(x-2)+b(2-x)^2=a(x-2)+b(x-2)^2',
displayMode: false,
});
});
it('preserves non-math text as plain text', () => {
expect(renderQuizMathText('Which option is correct?')).toEqual([
{ type: 'text', value: 'Which option is correct?' },
]);
});
it('preserves percent content in delimiter-free math', () => {
const segments = renderQuizMathText('25%*4 = 100%');
expect(segments).toHaveLength(1);
expect(segments[0]).toMatchObject({
type: 'math',
value: '25%*4 = 100%',
displayMode: false,
});
expect(segments[0].type === 'math' ? segments[0].html : '').toContain('%');
expect(segments[0].type === 'math' ? segments[0].html : '').toContain('100');
});
it('preserves prose prompts as plain text even with math-looking expressions', () => {
expect(renderQuizMathText('Solve x=2')).toEqual([{ type: 'text', value: 'Solve x=2' }]);
expect(renderQuizMathText('Simplify x^2')).toEqual([{ type: 'text', value: 'Simplify x^2' }]);
expect(renderQuizMathText('Use \\sqrt{2} not 2 here')).toEqual([
{ type: 'text', value: 'Use \\sqrt{2} not 2 here' },
]);
});
it('preserves code-like answer options as plain text', () => {
expect(renderQuizMathText('i = i + 1')).toEqual([{ type: 'text', value: 'i = i + 1' }]);
expect(renderQuizMathText('Answer = True')).toEqual([{ type: 'text', value: 'Answer = True' }]);
});
it('renders embedded algebra without consuming surrounding prose', () => {
const formula = 'a(x-2)+b(2-x)^2=a(x-2)+b(x-2)^2';
const segments = renderQuizMathText(`The simplification is ${formula}.`);
expect(segments).toHaveLength(3);
expect(segments[0]).toEqual({ type: 'text', value: 'The simplification is ' });
expect(segments[1]).toMatchObject({
type: 'math',
value: formula,
displayMode: false,
});
expect(segments[2]).toEqual({ type: 'text', value: '.' });
});
});