Batch 2-4 of the test-strategy rollout. Test-only except one minimal, behaviour-preserving extraction in file.utils.ts. All suites green: server 82 suites/836+1todo, editor-ext 86, mcp 270, client (new files) 86. integrations (server): - file.utils.ts: extract pure `isEntryPathSafe(entryName, targetDir)` from extractZipInternal so the zip-slip/path-traversal guard is unit-testable; call site rerouted, behaviour identical (only a warn-message string merged). - file.utils.zip-safety.spec.ts: traversal/strip/__MACOSX/prefix-confusion cases (mutation-resistant: fails if containment loses the path.sep). - import-formatter / import.utils / table-utils / export utils / import.service extractTitleAndRemoveHeading: pure import/export transforms, Notion/XWiki formatting, table colspan widths (idempotent), slug/link rewriting. client: - safeRedirectPath: open-redirect guard, every reject branch independently. - buildChatMarkdown (fence anti-breakout), label-colors, normalize-label, share tree build, page URL builders, notification time-grouping (fake clock). packages: - editor-ext: deriveFootnoteId golden table, parseHtmlEmbedHeight crafted values, orphan footnote extraction. - mcp: deriveFootnoteId parity (drift guard vs editor-ext), applyTextEdits idempotency + cross-block replaceAll, diffDocs/summarizeChange on reorder. Reviewed (APPROVE): extraction behaviour-preserving, assertions mutation-resistant. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
64 lines
2.9 KiB
TypeScript
64 lines
2.9 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import {
|
|
parseHtmlEmbedHeight,
|
|
renderHtmlEmbedHeight,
|
|
} from "./html-embed";
|
|
|
|
/**
|
|
* PIN the CURRENT behavior of `parseHtmlEmbedHeight` for crafted/corrupt
|
|
* `data-height` attribute values. The function is a thin parseInt + Number.isFinite
|
|
* guard; these tests document EXACTLY what it does today (including the cases
|
|
* where today's behavior is arguably wrong) so any future change is a conscious
|
|
* one and shows up as a failing test rather than a silent regression.
|
|
*/
|
|
describe("parseHtmlEmbedHeight: crafted / corrupt data-height", () => {
|
|
it('"-5" passes through as -5 (DOCUMENTED QUIRK: negative height is not rejected)', () => {
|
|
// Number.isFinite(-5) is true, so the guard does NOT catch it. A negative
|
|
// fixed height is almost certainly wrong downstream (it disables auto-resize
|
|
// and yields a negative/clamped iframe height), but the function as written
|
|
// returns it verbatim. This asserts the REAL behavior, not the ideal one.
|
|
expect(parseHtmlEmbedHeight("-5")).toBe(-5);
|
|
});
|
|
|
|
it('"0" returns 0 (NOT null) — note: renderHtmlEmbedHeight treats 0 as auto-resize, so parse/render are asymmetric at 0', () => {
|
|
// parseInt("0") === 0 and Number.isFinite(0) is true, so parse keeps 0.
|
|
expect(parseHtmlEmbedHeight("0")).toBe(0);
|
|
// But the render side treats a falsy 0 as "auto-resize" => emits NO attribute.
|
|
// So a stored height of 0 does not round-trip back to data-height="0".
|
|
expect(renderHtmlEmbedHeight(0)).toEqual({});
|
|
});
|
|
|
|
it('" 300 " (surrounding whitespace) parses to 300 — parseInt trims leading space', () => {
|
|
expect(parseHtmlEmbedHeight(" 300 ")).toBe(300);
|
|
});
|
|
|
|
it('"3.9" truncates to 3 — parseInt drops the fractional part', () => {
|
|
expect(parseHtmlEmbedHeight("3.9")).toBe(3);
|
|
});
|
|
|
|
it('a huge "99999999999" passes through unclamped (finite => no upper bound here)', () => {
|
|
// The guard only rejects NaN/Infinity; it does not clamp magnitude. Any
|
|
// clamping is a downstream concern, NOT this function's job.
|
|
expect(parseHtmlEmbedHeight("99999999999")).toBe(99999999999);
|
|
});
|
|
|
|
it('"12px" parses the leading integer (12) — parseInt stops at the first non-digit', () => {
|
|
expect(parseHtmlEmbedHeight("12px")).toBe(12);
|
|
});
|
|
|
|
it("null / empty / whitespace-only / non-numeric => null (the auto-resize sentinel)", () => {
|
|
expect(parseHtmlEmbedHeight(null)).toBeNull();
|
|
expect(parseHtmlEmbedHeight("")).toBeNull();
|
|
expect(parseHtmlEmbedHeight(" ")).toBeNull();
|
|
expect(parseHtmlEmbedHeight("abc")).toBeNull();
|
|
});
|
|
|
|
it("never returns NaN for a non-numeric value (the Number.isFinite guard's point)", () => {
|
|
// NaN is typeof "number" and would slip past a naive `typeof n === number`
|
|
// check; the guard must map it to null. This is the core invariant.
|
|
const out = parseHtmlEmbedHeight("not-a-number");
|
|
expect(out).toBeNull();
|
|
expect(Number.isNaN(out as unknown as number)).toBe(false);
|
|
});
|
|
});
|