chore: establish learning module baseline
This commit is contained in:
commit
2d04197f3f
2409 files changed
+533050
No files matched your search
@@ -0,0 +1,154 @@
|
||||
// @vitest-environment jsdom
|
||||
import { afterAll, afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { act, cleanup, render } from '@testing-library/react';
|
||||
import { BaseLatexElement } from '../../../src/elements/latex/BaseLatexElement';
|
||||
import type { PPTLatexElement } from '@openmaic/dsl';
|
||||
|
||||
/**
|
||||
* KaTeX lays out large delimiters (\left\{ … \begin{cases}) using the metrics of
|
||||
* its KaTeX_Size faces, which load asynchronously. The shrink-to-fit measurement
|
||||
* in BaseLatexElement must re-run once those fonts settle, or a cold export bakes
|
||||
* the stale fallback-metric scale and the brace desyncs from the piecewise body
|
||||
* (bug: "大括号后面的分段函数与大括号错位"). These tests drive that re-measure
|
||||
* with a fake `document.fonts` and stubbed layout metrics.
|
||||
*/
|
||||
|
||||
// jsdom has no layout, so scrollWidth/scrollHeight are 0. Make them return a
|
||||
// controllable value that changes after "fonts load" to simulate the metric
|
||||
// shift a real KaTeX_Size face causes.
|
||||
let naturalW = 0;
|
||||
let naturalH = 0;
|
||||
const origW = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'scrollWidth');
|
||||
const origH = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'scrollHeight');
|
||||
Object.defineProperty(HTMLElement.prototype, 'scrollWidth', {
|
||||
configurable: true,
|
||||
get: () => naturalW,
|
||||
});
|
||||
Object.defineProperty(HTMLElement.prototype, 'scrollHeight', {
|
||||
configurable: true,
|
||||
get: () => naturalH,
|
||||
});
|
||||
|
||||
/** A controllable fake of `document.fonts` (FontFaceSet-ish). */
|
||||
function installFakeFonts() {
|
||||
let resolveReady: () => void = () => {};
|
||||
const ready = new Promise<void>((r) => {
|
||||
resolveReady = r;
|
||||
});
|
||||
const listeners = new Map<string, Set<() => void>>();
|
||||
const fonts = {
|
||||
ready,
|
||||
load: vi.fn(() => Promise.resolve([])),
|
||||
addEventListener: (type: string, cb: () => void) => {
|
||||
(listeners.get(type) ?? listeners.set(type, new Set()).get(type)!).add(cb);
|
||||
},
|
||||
removeEventListener: (type: string, cb: () => void) => {
|
||||
listeners.get(type)?.delete(cb);
|
||||
},
|
||||
};
|
||||
Object.defineProperty(document, 'fonts', { configurable: true, value: fonts });
|
||||
return {
|
||||
resolveReady,
|
||||
emitLoadingDone: () => listeners.get('loadingdone')?.forEach((cb) => cb()),
|
||||
};
|
||||
}
|
||||
|
||||
function latex(html: string, overrides: Partial<PPTLatexElement> = {}): PPTLatexElement {
|
||||
return {
|
||||
id: 'l1',
|
||||
type: 'latex',
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 100,
|
||||
height: 100,
|
||||
rotate: 0,
|
||||
html,
|
||||
...overrides,
|
||||
} as unknown as PPTLatexElement;
|
||||
}
|
||||
|
||||
/** Read the scale multiplier off the inner prose div's transform. */
|
||||
function readScale(container: HTMLElement): number {
|
||||
const inner = container.querySelector('.slide-renderer-prose') as HTMLElement | null;
|
||||
const m = inner?.style.transform.match(/scale\(([-\d.]+)\)/);
|
||||
return m ? Number(m[1]) : NaN;
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
naturalW = 0;
|
||||
naturalH = 0;
|
||||
});
|
||||
|
||||
describe('BaseLatexElement — fit-scale re-measures after fonts settle', () => {
|
||||
it('recomputes scale on document.fonts.ready', async () => {
|
||||
const { resolveReady } = installFakeFonts();
|
||||
// Cold metrics: fallback font measures the formula as 100×100 → scale 1.
|
||||
naturalW = 100;
|
||||
naturalH = 100;
|
||||
|
||||
const { container } = render(<BaseLatexElement elementInfo={latex('\\left\\{ x \\right.')} />);
|
||||
expect(readScale(container)).toBeCloseTo(1);
|
||||
|
||||
// KaTeX_Size faces load: the real brace is wider (200px) → must shrink to 0.5.
|
||||
naturalW = 200;
|
||||
await act(async () => {
|
||||
resolveReady();
|
||||
await Promise.resolve();
|
||||
});
|
||||
expect(readScale(container)).toBeCloseTo(0.5);
|
||||
});
|
||||
|
||||
it('recomputes scale on a loadingdone event too', async () => {
|
||||
const { emitLoadingDone } = installFakeFonts();
|
||||
naturalW = 100;
|
||||
naturalH = 100;
|
||||
const { container } = render(<BaseLatexElement elementInfo={latex('E=mc^2')} />);
|
||||
expect(readScale(container)).toBeCloseTo(1);
|
||||
|
||||
naturalH = 400; // taller than the box → shrink to 0.25
|
||||
await act(async () => {
|
||||
emitLoadingDone();
|
||||
await Promise.resolve();
|
||||
});
|
||||
expect(readScale(container)).toBeCloseTo(0.25);
|
||||
});
|
||||
|
||||
it('never enlarges past scale 1', async () => {
|
||||
const { resolveReady } = installFakeFonts();
|
||||
naturalW = 50; // smaller than the 100px box
|
||||
naturalH = 50;
|
||||
const { container } = render(<BaseLatexElement elementInfo={latex('x')} />);
|
||||
await act(async () => {
|
||||
resolveReady();
|
||||
await Promise.resolve();
|
||||
});
|
||||
expect(readScale(container)).toBeCloseTo(1);
|
||||
});
|
||||
|
||||
it('keeps KaTeX HTML, formula color, and horizontal alignment in the shared render path', () => {
|
||||
naturalW = 100;
|
||||
naturalH = 100;
|
||||
const { container } = render(
|
||||
<BaseLatexElement
|
||||
elementInfo={latex('<span class="katex-display">E = mc<sup>2</sup></span>', {
|
||||
color: '#2563eb',
|
||||
align: 'right',
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
|
||||
const content = container.querySelector('.element-content') as HTMLElement;
|
||||
const formula = container.querySelector('.slide-renderer-prose') as HTMLElement;
|
||||
expect(content.style.color).toBe('rgb(37, 99, 235)');
|
||||
expect(formula.innerHTML).toContain('katex-display');
|
||||
expect(formula.style.transformOrigin).toBe('right center');
|
||||
expect(formula.parentElement?.style.justifyContent).toBe('flex-end');
|
||||
});
|
||||
});
|
||||
|
||||
// Restore the prototype descriptors so other suites see real jsdom behavior.
|
||||
afterAll(() => {
|
||||
if (origW) Object.defineProperty(HTMLElement.prototype, 'scrollWidth', origW);
|
||||
if (origH) Object.defineProperty(HTMLElement.prototype, 'scrollHeight', origH);
|
||||
});
|
||||
Reference in new issue
Block a user