From 0277744abc630d55c55da7d38b7952500e20b89d Mon Sep 17 00:00:00 2001 From: Tadeu Tupinamba Date: Thu, 16 Apr 2026 20:03:40 -0300 Subject: [PATCH] refactor(painter): remove legacy createDomPainter wrapper API The painter previously shipped a backward-compat API layer (setData, setResolvedLayout, paint(Layout) overload, blocks/measures constructor options, LegacyDomPainterState, buildLegacyPaintInput). This mixed-path logic existed only to smooth the migration to DomPainterInput. All production callers now use the modern contract directly. The benchmark is updated to call resolveLayout explicitly. Test call sites that still expressed fixtures as {blocks, measures} + raw Layout now route through a shared createTestPainter helper in painters/dom/src/test-utils/test-painter.ts, which wraps createDomPainter and runs resolveLayout internally. DomPainterOptions: blocks?/measures? removed DomPainterHandle: setData/setResolvedLayout removed, paint() takes only DomPainterInput Internal: LegacyDomPainterState, buildLegacyPaintInput, createEmptyResolvedLayout, isDomPainterInput, assertRequiredBlockMeasurePair all deleted. Closes SD-2563 criterion: painter does not carry mixed-path logic. No rendering change (layout-compare 0/407 changed). --- .../layout-engine/layout-bridge/package.json | 1 + .../layout-bridge/src/benchmarks/index.ts | 17 ++- packages/layout-engine/painters/dom/README.md | 15 ++- .../painters/dom/src/between-borders.test.ts | 6 +- .../src/clip-path-cache-invalidation.test.ts | 8 +- .../painters/dom/src/index.test.ts | 22 ++-- .../layout-engine/painters/dom/src/index.ts | 104 +----------------- .../painters/dom/src/link-click.test.ts | 12 +- .../dom/src/renderer-dispatch.test.ts | 16 +-- .../dom/src/renderer-hanging-indent.test.ts | 82 +++++++------- .../src/renderer-known-divergences.test.ts | 8 +- .../dom/src/renderer-marker-suffix.test.ts | 26 ++--- .../dom/src/renderer-marker-textwidth.test.ts | 34 +++--- .../dom/src/renderer-parity-contracts.test.ts | 4 +- .../src/renderer-shape-regressions.test.ts | 8 +- .../renderer-vector-shape-geometry.test.ts | 8 +- .../dom/src/test-utils/test-painter.ts | 76 +++++++++++++ .../dom/src/text-style-rendering.test.ts | 32 +++--- pnpm-lock.yaml | 3 + 19 files changed, 239 insertions(+), 243 deletions(-) create mode 100644 packages/layout-engine/painters/dom/src/test-utils/test-painter.ts diff --git a/packages/layout-engine/layout-bridge/package.json b/packages/layout-engine/layout-bridge/package.json index 201846977e..eae4fbb783 100644 --- a/packages/layout-engine/layout-bridge/package.json +++ b/packages/layout-engine/layout-bridge/package.json @@ -25,6 +25,7 @@ "@superdoc/contracts": "workspace:*", "@superdoc/dom-contract": "workspace:*", "@superdoc/layout-engine": "workspace:*", + "@superdoc/layout-resolved": "workspace:*", "@superdoc/measuring-dom": "workspace:*", "@superdoc/painter-dom": "workspace:*", "@superdoc/word-layout": "workspace:*" diff --git a/packages/layout-engine/layout-bridge/src/benchmarks/index.ts b/packages/layout-engine/layout-bridge/src/benchmarks/index.ts index 296b5d750f..6afd6fd251 100644 --- a/packages/layout-engine/layout-bridge/src/benchmarks/index.ts +++ b/packages/layout-engine/layout-bridge/src/benchmarks/index.ts @@ -3,6 +3,7 @@ import type { FlowBlock, Layout, ParagraphBlock, ParagraphMeasure, Run } from '@ import type { LayoutOptions } from '@superdoc/layout-engine'; import { measureBlock } from '@superdoc/measuring-dom'; import { createDomPainter } from '@superdoc/painter-dom'; +import { resolveLayout } from '@superdoc/layout-resolved'; import { layoutDocument } from '@superdoc/layout-engine'; import { incrementalLayout, measureCache, resolveMeasurementConstraints } from '../incrementalLayout'; @@ -88,11 +89,14 @@ export async function runBenchmarkScenario(config: BenchmarkConfig): Promise { const b1: FlowBlock = { kind: 'paragraph', id: 'b1', runs: [] }; const b2: FlowBlock = { kind: 'paragraph', id: 'b2', runs: [] }; - const painter = createDomPainter({ blocks: [b1, b2], measures: [makeMeasure(), makeMeasure()] }); + const painter = createTestPainter({ blocks: [b1, b2], measures: [makeMeasure(), makeMeasure()] }); painter.paint(layout, mount); const page = mount.querySelector('[data-page-number="1"]') as HTMLElement; @@ -1116,7 +1116,7 @@ describe('DomPainter between-border incremental update', () => { attrs: { borders: MATCHING_BORDERS }, }; - const painter = createDomPainter({ blocks: [b1, b2], measures: [makeMeasure(), makeMeasure()] }); + const painter = createTestPainter({ blocks: [b1, b2], measures: [makeMeasure(), makeMeasure()] }); painter.paint(layout, mount); const page = mount.querySelector('[data-page-number="1"]') as HTMLElement; diff --git a/packages/layout-engine/painters/dom/src/clip-path-cache-invalidation.test.ts b/packages/layout-engine/painters/dom/src/clip-path-cache-invalidation.test.ts index 08b8e86005..b41fe12788 100644 --- a/packages/layout-engine/painters/dom/src/clip-path-cache-invalidation.test.ts +++ b/packages/layout-engine/painters/dom/src/clip-path-cache-invalidation.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { createDomPainter } from './index.js'; +import { createTestPainter } from './test-utils/test-painter.js'; import type { FlowBlock, Layout, Measure } from '@superdoc/contracts'; const DATA_URL = @@ -72,7 +72,7 @@ describe('DomPainter clipPath cache invalidation', () => { ], }; - const painter = createDomPainter({ blocks: [imageBlock], measures: [imageMeasure] }); + const painter = createTestPainter({ blocks: [imageBlock], measures: [imageMeasure] }); painter.paint(imageLayout, mount); const wrapperBefore = mount.querySelector('.superdoc-inline-image-clip-wrapper') as HTMLElement; @@ -141,7 +141,7 @@ describe('DomPainter clipPath cache invalidation', () => { ], }; - const painter = createDomPainter({ blocks: [imageBlock], measures: [imageMeasure] }); + const painter = createTestPainter({ blocks: [imageBlock], measures: [imageMeasure] }); painter.paint(imageLayout, mount); const fragmentBefore = mount.querySelector('.superdoc-image-fragment') as HTMLElement; @@ -216,7 +216,7 @@ describe('DomPainter clipPath cache invalidation', () => { ], }; - const painter = createDomPainter({ blocks: [drawingBlock], measures: [drawingMeasure] }); + const painter = createTestPainter({ blocks: [drawingBlock], measures: [drawingMeasure] }); painter.paint(drawingLayout, mount); const fragmentBefore = mount.querySelector('.superdoc-drawing-fragment') as HTMLElement; diff --git a/packages/layout-engine/painters/dom/src/index.test.ts b/packages/layout-engine/painters/dom/src/index.test.ts index f38a089d4a..ada75a2eec 100644 --- a/packages/layout-engine/painters/dom/src/index.test.ts +++ b/packages/layout-engine/painters/dom/src/index.test.ts @@ -1529,7 +1529,7 @@ describe('DomPainter', () => { }); try { - const painter = createDomPainter({ blocks: [tableBlock], measures: [tableMeasure] }); + const painter = createTestPainter({ blocks: [tableBlock], measures: [tableMeasure] }); expect(() => painter.paint(tableLayout, mount)).not.toThrow(); const placeholder = mount.querySelector('.render-error-placeholder') as HTMLElement | null; @@ -6158,7 +6158,7 @@ describe('DomPainter', () => { ], }; - const painter = createDomPainter({ blocks: [imageBlock], measures: [imageMeasure] }); + const painter = createTestPainter({ blocks: [imageBlock], measures: [imageMeasure] }); painter.paint(imageLayout, mount); }; @@ -7763,7 +7763,7 @@ describe('ImageFragment (block-level images)', () => { ...(hyperlink ? { hyperlink } : {}), }; const measure: Measure = { kind: 'image', width: 100, height: 50 }; - return createDomPainter({ blocks: [block], measures: [measure] }); + return createTestPainter({ blocks: [block], measures: [measure] }); }; it('wraps linked image in with correct href', () => { @@ -7814,7 +7814,7 @@ describe('ImageFragment (block-level images)', () => { pageSize: { w: 400, h: 300 }, pages: [{ number: 1, fragments: [fragment] }], }; - const painter = createDomPainter({ blocks: [block], measures: [measure] }); + const painter = createTestPainter({ blocks: [block], measures: [measure] }); painter.paint(layout, mount); const anchor = mount.querySelector('a.superdoc-link') as HTMLAnchorElement | null; @@ -7835,7 +7835,7 @@ describe('ImageFragment (block-level images)', () => { pageSize: { w: 400, h: 300 }, pages: [{ number: 1, fragments: [fragment] }], }; - const painter = createDomPainter({ blocks: [block], measures: [measure] }); + const painter = createTestPainter({ blocks: [block], measures: [measure] }); painter.paint(layout, mount); const anchor = mount.querySelector('a.superdoc-link'); @@ -7861,7 +7861,7 @@ describe('ImageFragment (block-level images)', () => { pageSize: { w: 400, h: 300 }, pages: [{ number: 1, fragments: [fragment] }], }; - const painter = createDomPainter({ blocks: [block], measures: [measure] }); + const painter = createTestPainter({ blocks: [block], measures: [measure] }); painter.paint(layout, mount); const anchor = mount.querySelector('a.superdoc-link'); @@ -7928,7 +7928,7 @@ describe('URL sanitization security', () => { describe('normalizeAnchor XSS protection', () => { let mount: HTMLElement; - let painter: ReturnType; + let painter: ReturnType; const createFlowBlockWithLink = (link: unknown): FlowBlock => ({ kind: 'paragraph', @@ -8075,7 +8075,7 @@ describe('normalizeAnchor XSS protection', () => { describe('appendDocLocation XSS protection', () => { let mount: HTMLElement; - let painter: ReturnType; + let painter: ReturnType; const createFlowBlockWithLink = (link: unknown): FlowBlock => ({ kind: 'paragraph', @@ -8255,7 +8255,7 @@ describe('appendDocLocation XSS protection', () => { describe('appendDocLocation edge cases', () => { let mount: HTMLElement; - let painter: ReturnType; + let painter: ReturnType; const createFlowBlockWithLink = (link: unknown): FlowBlock => ({ kind: 'paragraph', @@ -8474,7 +8474,7 @@ describe('appendDocLocation edge cases', () => { describe('Tooltip truncation signaling', () => { let mount: HTMLElement; - let painter: ReturnType; + let painter: ReturnType; const createFlowBlockWithLink = (link: unknown): FlowBlock => ({ kind: 'paragraph', @@ -9224,7 +9224,7 @@ describe('Link accessibility - Tooltip aria-describedby', () => { describe('Link rendering metrics', () => { let mount: HTMLElement; - let painter: ReturnType; + let painter: ReturnType; const createFlowBlockWithLink = (link: unknown): FlowBlock => ({ kind: 'paragraph', diff --git a/packages/layout-engine/painters/dom/src/index.ts b/packages/layout-engine/painters/dom/src/index.ts index a7a701be12..743ad07c9d 100644 --- a/packages/layout-engine/painters/dom/src/index.ts +++ b/packages/layout-engine/painters/dom/src/index.ts @@ -1,10 +1,7 @@ -import type { FlowBlock, Layout, Measure, PageMargins, ResolvedLayout, Page } from '@superdoc/contracts'; import { DomPainter } from './renderer.js'; -import { resolveLayout } from '@superdoc/layout-resolved'; import type { PageStyles } from './styles.js'; import type { DomPainterInput, - PageDecorationPayload, PageDecorationProvider, PaintSnapshot, PositionMapping, @@ -67,16 +64,6 @@ export type { FlowMode } from './renderer.js'; export type { PageDecorationPayload, PageDecorationProvider } from './renderer.js'; export type DomPainterOptions = { - /** - * Legacy compatibility: initial body block data. - * New callers should pass block data through `paint(input, mount)`. - */ - blocks?: FlowBlock[]; - /** - * Legacy compatibility: initial body measures. - * New callers should pass measure data through `paint(input, mount)`. - */ - measures?: Measure[]; pageStyles?: PageStyles; layoutMode?: LayoutMode; flowMode?: FlowMode; @@ -113,24 +100,8 @@ export type DomPainterOptions = { onPaintSnapshot?: (snapshot: PaintSnapshot) => void; }; -type LegacyDomPainterState = { - blocks: FlowBlock[]; - measures: Measure[]; - resolvedLayout: ResolvedLayout | null; -}; - export type DomPainterHandle = { - paint(input: DomPainterInput | Layout, mount: HTMLElement, mapping?: PositionMapping): void; - /** - * Legacy compatibility API. - * New callers should pass block/measure data via `paint(input, mount)`. - */ - setData(blocks: FlowBlock[], measures: Measure[]): void; - /** - * Legacy compatibility API. - * New callers should pass resolved data via `paint(input, mount)`. - */ - setResolvedLayout(resolvedLayout: ResolvedLayout | null): void; + paint(input: DomPainterInput, mount: HTMLElement, mapping?: PositionMapping): void; setProviders(header?: PageDecorationProvider, footer?: PageDecorationProvider): void; setVirtualizationPins(pageIndices: number[] | null | undefined): void; getMountedPageIndices(): number[]; @@ -139,59 +110,7 @@ export type DomPainterHandle = { setScrollContainer(el: HTMLElement | null): void; }; -function assertRequiredBlockMeasurePair(label: string, blocks: FlowBlock[], measures: Measure[]): void { - if (blocks.length !== measures.length) { - throw new Error(`${label} blocks and measures must have the same length.`); - } -} - -function createEmptyResolvedLayout(flowMode: FlowMode | undefined, pageGap: number | undefined): ResolvedLayout { - return { - version: 1, - flowMode: flowMode ?? 'paginated', - pageGap: pageGap ?? 0, - pages: [], - }; -} - -function isDomPainterInput(value: DomPainterInput | Layout): value is DomPainterInput { - return 'resolvedLayout' in value && 'sourceLayout' in value; -} - -function buildLegacyPaintInput( - layout: Layout, - legacyState: LegacyDomPainterState, - flowMode: FlowMode | undefined, - pageGap: number | undefined, -): DomPainterInput { - // Derive a resolved layout from the legacy block/measure state when the caller - // has not supplied one via `setResolvedLayout`. The painter now reads all body - // fragment data from the resolved layout, so an empty resolved layout would - // produce a blank render. - let resolvedLayout: ResolvedLayout; - if (legacyState.resolvedLayout) { - resolvedLayout = legacyState.resolvedLayout; - } else if (legacyState.blocks.length === 0 && legacyState.measures.length === 0) { - resolvedLayout = createEmptyResolvedLayout(flowMode, pageGap); - } else { - resolvedLayout = resolveLayout({ - layout, - flowMode: flowMode ?? 'paginated', - blocks: legacyState.blocks, - measures: legacyState.measures, - }); - } - return { - resolvedLayout, - sourceLayout: layout, - }; -} - export const createDomPainter = (options: DomPainterOptions): DomPainterHandle => { - if ((options.blocks ?? []).length !== (options.measures ?? []).length) { - throw new Error('DomPainter requires the same number of blocks and measures'); - } - const painter = new DomPainter({ pageStyles: options.pageStyles, layoutMode: options.layoutMode, @@ -204,26 +123,9 @@ export const createDomPainter = (options: DomPainterOptions): DomPainterHandle = onPaintSnapshot: options.onPaintSnapshot, }); - const legacyState: LegacyDomPainterState = { - blocks: options.blocks ?? [], - measures: options.measures ?? [], - resolvedLayout: null, - }; - return { - paint(input: DomPainterInput | Layout, mount: HTMLElement, mapping?: PositionMapping) { - const normalizedInput = isDomPainterInput(input) - ? input - : buildLegacyPaintInput(input, legacyState, options.flowMode, options.pageGap); - painter.paint(normalizedInput, mount, mapping); - }, - setData(blocks: FlowBlock[], measures: Measure[]) { - assertRequiredBlockMeasurePair('body', blocks, measures); - legacyState.blocks = blocks; - legacyState.measures = measures; - }, - setResolvedLayout(resolvedLayout: ResolvedLayout | null) { - legacyState.resolvedLayout = resolvedLayout; + paint(input: DomPainterInput, mount: HTMLElement, mapping?: PositionMapping) { + painter.paint(input, mount, mapping); }, setProviders(header?: PageDecorationProvider, footer?: PageDecorationProvider) { painter.setProviders(header, footer); diff --git a/packages/layout-engine/painters/dom/src/link-click.test.ts b/packages/layout-engine/painters/dom/src/link-click.test.ts index d826043b00..2005f337ef 100644 --- a/packages/layout-engine/painters/dom/src/link-click.test.ts +++ b/packages/layout-engine/painters/dom/src/link-click.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, beforeEach, afterEach } from 'vitest'; -import { createDomPainter } from './index.js'; +import { createTestPainter } from './test-utils/test-painter.js'; import type { FlowBlock, Measure, Layout } from '@superdoc/contracts'; /** @@ -80,7 +80,7 @@ describe('DomPainter - Link Rendering', () => { ], }; - const painter = createDomPainter({ blocks: [linkBlock], measures: [measure] }); + const painter = createTestPainter({ blocks: [linkBlock], measures: [measure] }); painter.paint(layout, container); const linkElement = container.querySelector('a.superdoc-link') as HTMLAnchorElement; @@ -154,7 +154,7 @@ describe('DomPainter - Link Rendering', () => { ], }; - const painter = createDomPainter({ blocks: [linkBlock], measures: [measure] }); + const painter = createTestPainter({ blocks: [linkBlock], measures: [measure] }); painter.paint(layout, container); const linkElement = container.querySelector('a.superdoc-link') as HTMLAnchorElement; @@ -222,7 +222,7 @@ describe('DomPainter - Link Rendering', () => { ], }; - const painter = createDomPainter({ blocks: [linkBlock], measures: [measure] }); + const painter = createTestPainter({ blocks: [linkBlock], measures: [measure] }); painter.paint(layout, container); const linkElement = container.querySelector('a.superdoc-link') as HTMLAnchorElement; @@ -307,7 +307,7 @@ describe('DomPainter - Link Rendering', () => { ], }; - const painter = createDomPainter({ blocks: [linkBlock], measures: [measure] }); + const painter = createTestPainter({ blocks: [linkBlock], measures: [measure] }); painter.paint(layout, container); const linkElements = container.querySelectorAll('a.superdoc-link'); @@ -384,7 +384,7 @@ describe('DomPainter - Link Rendering', () => { ], }; - const painter = createDomPainter({ blocks: [mixedBlock], measures: [measure] }); + const painter = createTestPainter({ blocks: [mixedBlock], measures: [measure] }); painter.paint(layout, container); // Should have one link and spans for regular text diff --git a/packages/layout-engine/painters/dom/src/renderer-dispatch.test.ts b/packages/layout-engine/painters/dom/src/renderer-dispatch.test.ts index f0777dd695..67b4163fec 100644 --- a/packages/layout-engine/painters/dom/src/renderer-dispatch.test.ts +++ b/packages/layout-engine/painters/dom/src/renderer-dispatch.test.ts @@ -7,7 +7,7 @@ */ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import { createDomPainter } from './index.js'; +import { createTestPainter } from './test-utils/test-painter.js'; import { DomPainter } from './renderer.js'; import type { FlowBlock, Measure, Layout } from '@superdoc/contracts'; @@ -331,7 +331,7 @@ describe('renderFragment dispatch', () => { const dummyDiv = document.createElement('div'); const spy = vi.spyOn(DomPainter.prototype as any, 'renderParagraphFragment').mockReturnValue(dummyDiv); const { blocks, measures, layout } = paragraphFixtures(); - const painter = createDomPainter({ blocks, measures }); + const painter = createTestPainter({ blocks, measures }); painter.paint(layout, container); expect(spy).toHaveBeenCalledTimes(1); expect(spy.mock.calls[0]![0].kind).toBe('para'); @@ -341,7 +341,7 @@ describe('renderFragment dispatch', () => { const dummyDiv = document.createElement('div'); const spy = vi.spyOn(DomPainter.prototype as any, 'renderListItemFragment').mockReturnValue(dummyDiv); const { blocks, measures, layout } = listItemFixtures(); - const painter = createDomPainter({ blocks, measures }); + const painter = createTestPainter({ blocks, measures }); painter.paint(layout, container); expect(spy).toHaveBeenCalledTimes(1); expect(spy.mock.calls[0]![0].kind).toBe('list-item'); @@ -351,7 +351,7 @@ describe('renderFragment dispatch', () => { const dummyDiv = document.createElement('div'); const spy = vi.spyOn(DomPainter.prototype as any, 'renderImageFragment').mockReturnValue(dummyDiv); const { blocks, measures, layout } = imageFixtures(); - const painter = createDomPainter({ blocks, measures }); + const painter = createTestPainter({ blocks, measures }); painter.paint(layout, container); expect(spy).toHaveBeenCalledTimes(1); expect(spy.mock.calls[0]![0].kind).toBe('image'); @@ -361,7 +361,7 @@ describe('renderFragment dispatch', () => { const dummyDiv = document.createElement('div'); const spy = vi.spyOn(DomPainter.prototype as any, 'renderDrawingFragment').mockReturnValue(dummyDiv); const { blocks, measures, layout } = drawingFixtures(); - const painter = createDomPainter({ blocks, measures }); + const painter = createTestPainter({ blocks, measures }); painter.paint(layout, container); expect(spy).toHaveBeenCalledTimes(1); expect(spy.mock.calls[0]![0].kind).toBe('drawing'); @@ -371,7 +371,7 @@ describe('renderFragment dispatch', () => { const dummyDiv = document.createElement('div'); const spy = vi.spyOn(DomPainter.prototype as any, 'renderDrawingFragment').mockReturnValue(dummyDiv); const { blocks, measures, layout } = chartDrawingFixtures(); - const painter = createDomPainter({ blocks, measures }); + const painter = createTestPainter({ blocks, measures }); painter.paint(layout, container); expect(spy).toHaveBeenCalledTimes(1); expect(spy.mock.calls[0]![0].drawingKind).toBe('chart'); @@ -381,7 +381,7 @@ describe('renderFragment dispatch', () => { const dummyDiv = document.createElement('div'); const spy = vi.spyOn(DomPainter.prototype as any, 'renderTableFragment').mockReturnValue(dummyDiv); const { blocks, measures, layout } = tableFixtures(); - const painter = createDomPainter({ blocks, measures }); + const painter = createTestPainter({ blocks, measures }); painter.paint(layout, container); expect(spy).toHaveBeenCalledTimes(1); expect(spy.mock.calls[0]![0].kind).toBe('table'); @@ -406,7 +406,7 @@ describe('renderFragment dispatch', () => { }, ], }; - const painter = createDomPainter({ blocks, measures }); + const painter = createTestPainter({ blocks, measures }); expect(() => painter.paint(layout, container)).toThrow('unsupported fragment kind'); }); }); diff --git a/packages/layout-engine/painters/dom/src/renderer-hanging-indent.test.ts b/packages/layout-engine/painters/dom/src/renderer-hanging-indent.test.ts index 003142cb6e..624674164d 100644 --- a/packages/layout-engine/painters/dom/src/renderer-hanging-indent.test.ts +++ b/packages/layout-engine/painters/dom/src/renderer-hanging-indent.test.ts @@ -11,7 +11,7 @@ */ import { describe, it, expect, beforeEach } from 'vitest'; -import { createDomPainter } from './index.js'; +import { createTestPainter } from './test-utils/test-painter.js'; import type { FlowBlock, Measure, Layout, Line } from '@superdoc/contracts'; describe('DomPainter hanging indent with tabs', () => { @@ -165,7 +165,7 @@ describe('DomPainter hanging indent with tabs', () => { const measure = createMeasure(13, true); // true = has explicit positioning const layout = createLayout(blockId, 13); - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -187,7 +187,7 @@ describe('DomPainter hanging indent with tabs', () => { const measure = createMeasure(13, true); const layout = createLayout(blockId, 13); - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -209,7 +209,7 @@ describe('DomPainter hanging indent with tabs', () => { const measure = createMeasure(8, true); const layout = createLayout(blockId, 8); - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -231,7 +231,7 @@ describe('DomPainter hanging indent with tabs', () => { const measure = createMeasure(17, false); // false = no explicit positioning const layout = createLayout(blockId, 17); - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -253,7 +253,7 @@ describe('DomPainter hanging indent with tabs', () => { const measure = createMeasure(12, false); const layout = createLayout(blockId, 12); - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -274,7 +274,7 @@ describe('DomPainter hanging indent with tabs', () => { const measure = createMeasure(8, true); // Has tabs const layout = createLayout(blockId, 8); - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -296,7 +296,7 @@ describe('DomPainter hanging indent with tabs', () => { const measure = createMeasure(8, true); const layout = createLayout(blockId, 8); - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -342,7 +342,7 @@ describe('DomPainter hanging indent with tabs', () => { ], }; - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lines = container.querySelectorAll('.superdoc-line'); @@ -368,7 +368,7 @@ describe('DomPainter hanging indent with tabs', () => { const measure = createMeasure(13, true); const layout = createLayout(blockId, 13, true); // continuesFromPrev = true - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -391,7 +391,7 @@ describe('DomPainter hanging indent with tabs', () => { const measure = createMeasure(8, true); const layout = createLayout(blockId, 8); - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -413,7 +413,7 @@ describe('DomPainter hanging indent with tabs', () => { const measure = createMeasure(8, true); const layout = createLayout(blockId, 8); - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -432,7 +432,7 @@ describe('DomPainter hanging indent with tabs', () => { const measure = createMeasure(13, true); const layout = createLayout(blockId, 13); - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -475,7 +475,7 @@ describe('DomPainter hanging indent with tabs', () => { const layout = createLayout(blockId, 14); - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -495,7 +495,7 @@ describe('DomPainter hanging indent with tabs', () => { const measure = createMeasure(8, true); const layout = createLayout(blockId, 8); - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -516,7 +516,7 @@ describe('DomPainter hanging indent with tabs', () => { const measure = createMeasure(8, true); const layout = createLayout(blockId, 8); - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -599,7 +599,7 @@ describe('DomPainter hanging indent with tabs', () => { ], }; - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -683,7 +683,7 @@ describe('DomPainter hanging indent with tabs', () => { ], }; - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -768,7 +768,7 @@ describe('DomPainter hanging indent with tabs', () => { ], }; - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -857,7 +857,7 @@ describe('DomPainter hanging indent with tabs', () => { ], }; - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -944,7 +944,7 @@ describe('DomPainter hanging indent with tabs', () => { ], }; - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -972,7 +972,7 @@ describe('DomPainter hanging indent with tabs', () => { const measure = createMeasure(8, true); const layout = createLayout(blockId, 8); - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -1057,7 +1057,7 @@ describe('DomPainter hanging indent with tabs', () => { ], }; - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -1142,7 +1142,7 @@ describe('DomPainter hanging indent with tabs', () => { ], }; - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -1227,7 +1227,7 @@ describe('DomPainter hanging indent with tabs', () => { ], }; - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -1314,7 +1314,7 @@ describe('DomPainter hanging indent with tabs', () => { ], }; - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -1402,7 +1402,7 @@ describe('DomPainter hanging indent with tabs', () => { ], }; - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -1488,7 +1488,7 @@ describe('DomPainter hanging indent with tabs', () => { ], }; - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -1565,7 +1565,7 @@ describe('DomPainter hanging indent with tabs', () => { ], }; - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); // Verify segments are rendered with correct positioning @@ -1645,7 +1645,7 @@ describe('DomPainter hanging indent with tabs', () => { ], }; - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); // Verify segments are rendered with correct positioning @@ -1743,7 +1743,7 @@ describe('DomPainter hanging indent with tabs', () => { ], }; - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); // Get the second line's segments @@ -1823,7 +1823,7 @@ describe('DomPainter hanging indent with tabs', () => { ], }; - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); // Verify segments are rendered with correct positioning @@ -1901,7 +1901,7 @@ describe('DomPainter hanging indent with tabs', () => { ], }; - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); // With all zero indents, indentOffset = 0 @@ -1965,7 +1965,7 @@ describe('DomPainter hanging indent with tabs', () => { }; const layout = createLayout(blockId, 13); - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -2011,7 +2011,7 @@ describe('DomPainter hanging indent with tabs', () => { }; const layout = createLayout(blockId, 13); - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -2096,7 +2096,7 @@ describe('DomPainter hanging indent with tabs', () => { ], }; - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lines = container.querySelectorAll('.superdoc-line'); @@ -2141,7 +2141,7 @@ describe('DomPainter hanging indent with tabs', () => { }; const layout = createLayout(blockId, 17); - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -2225,7 +2225,7 @@ describe('DomPainter hanging indent with tabs', () => { ], }; - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lines = container.querySelectorAll('.superdoc-line'); @@ -2276,7 +2276,7 @@ describe('DomPainter hanging indent with tabs', () => { }; const layout = createLayout(blockId, 13); - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -2323,7 +2323,7 @@ describe('DomPainter hanging indent with tabs', () => { }; const layout = createLayout(blockId, 13); - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; @@ -2427,7 +2427,7 @@ describe('DomPainter hanging indent with tabs', () => { ], }; - const painter = createDomPainter({ blocks: [block], measures: [measure], container }); + const painter = createTestPainter({ blocks: [block], measures: [measure], container }); painter.paint(layout, container); const lineEl = container.querySelector('.superdoc-line') as HTMLElement; expect(lineEl).toBeTruthy(); diff --git a/packages/layout-engine/painters/dom/src/renderer-known-divergences.test.ts b/packages/layout-engine/painters/dom/src/renderer-known-divergences.test.ts index b08c4164f0..3c315606c4 100644 --- a/packages/layout-engine/painters/dom/src/renderer-known-divergences.test.ts +++ b/packages/layout-engine/painters/dom/src/renderer-known-divergences.test.ts @@ -10,7 +10,7 @@ */ import { describe, it, expect } from 'vitest'; -import { createDomPainter } from './index.js'; +import { createTestPainter } from './test-utils/test-painter.js'; import type { FlowBlock, Measure, Layout, Line } from '@superdoc/contracts'; import { normalizeLines } from './test-utils/normalize-line.js'; @@ -235,7 +235,7 @@ function tableCellFixtures(alignment: string) { function renderAndNormalize(fixtures: { blocks: FlowBlock[]; measures: Measure[]; layout: Layout }) { const container = document.createElement('div'); - const painter = createDomPainter({ blocks: fixtures.blocks, measures: fixtures.measures }); + const painter = createTestPainter({ blocks: fixtures.blocks, measures: fixtures.measures }); painter.paint(fixtures.layout, container); return normalizeLines(container); } @@ -253,7 +253,7 @@ describe('known divergences (frozen — delete when resolved)', () => { it('list-item fragment forces textAlign to left even when paragraph alignment is justify', () => { const container = document.createElement('div'); const fix = listItemFixtures({ alignment: 'justify' }); - const painter = createDomPainter({ blocks: fix.blocks, measures: fix.measures }); + const painter = createTestPainter({ blocks: fix.blocks, measures: fix.measures }); painter.paint(fix.layout, container); // The list-item content div overrides textAlign to 'left' @@ -265,7 +265,7 @@ describe('known divergences (frozen — delete when resolved)', () => { it('list-item fragment forces textAlign to left even when paragraph alignment is center', () => { const container = document.createElement('div'); const fix = listItemFixtures({ alignment: 'center' }); - const painter = createDomPainter({ blocks: fix.blocks, measures: fix.measures }); + const painter = createTestPainter({ blocks: fix.blocks, measures: fix.measures }); painter.paint(fix.layout, container); const contentEl = container.querySelector('.superdoc-list-content') as HTMLElement | null; diff --git a/packages/layout-engine/painters/dom/src/renderer-marker-suffix.test.ts b/packages/layout-engine/painters/dom/src/renderer-marker-suffix.test.ts index 2c39672bee..4a1d43427e 100644 --- a/packages/layout-engine/painters/dom/src/renderer-marker-suffix.test.ts +++ b/packages/layout-engine/painters/dom/src/renderer-marker-suffix.test.ts @@ -5,7 +5,7 @@ */ import { describe, it, expect, beforeEach } from 'vitest'; -import { createDomPainter } from './index.js'; +import { createTestPainter } from './test-utils/test-painter.js'; import type { FlowBlock, Measure, Layout, WordParagraphLayoutOutput } from '@superdoc/contracts'; describe('DomPainter marker suffix rendering', () => { @@ -110,7 +110,7 @@ describe('DomPainter marker suffix rendering', () => { const measure = createListMeasure(); const layout = createListLayout(blockId, 24); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -139,7 +139,7 @@ describe('DomPainter marker suffix rendering', () => { const measure = createListMeasure(); const layout = createListLayout(blockId, 24); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -159,7 +159,7 @@ describe('DomPainter marker suffix rendering', () => { const measure = createListMeasure(); const layout = createListLayout(blockId, 24); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -180,7 +180,7 @@ describe('DomPainter marker suffix rendering', () => { const measure = createListMeasure(); const layout = createListLayout(blockId, 24); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -201,7 +201,7 @@ describe('DomPainter marker suffix rendering', () => { const measure = createListMeasure(); const layout = createListLayout(blockId, 24); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -222,7 +222,7 @@ describe('DomPainter marker suffix rendering', () => { const measure = createListMeasure(); const layout = createListLayout(blockId, 24); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -245,7 +245,7 @@ describe('DomPainter marker suffix rendering', () => { const measure = createListMeasure(); const layout = createListLayout(blockId, 24); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -269,7 +269,7 @@ describe('DomPainter marker suffix rendering', () => { const measure = createListMeasure(); const layout = createListLayout(blockId, 24); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -292,7 +292,7 @@ describe('DomPainter marker suffix rendering', () => { const measure = createListMeasure(); const layout = createListLayout(blockId, 24); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -317,7 +317,7 @@ describe('DomPainter marker suffix rendering', () => { const measure = createListMeasure(); const layout = createListLayout(blockId, 24); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -358,7 +358,7 @@ describe('DomPainter marker suffix rendering', () => { const measure = createListMeasure(); const layout = createListLayout(blockId, 24); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -433,7 +433,7 @@ describe('DomPainter marker suffix rendering', () => { ], }; - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); diff --git a/packages/layout-engine/painters/dom/src/renderer-marker-textwidth.test.ts b/packages/layout-engine/painters/dom/src/renderer-marker-textwidth.test.ts index cf3e7cddd5..1f5b4f73e6 100644 --- a/packages/layout-engine/painters/dom/src/renderer-marker-textwidth.test.ts +++ b/packages/layout-engine/painters/dom/src/renderer-marker-textwidth.test.ts @@ -10,7 +10,7 @@ */ import { describe, it, expect, beforeEach } from 'vitest'; -import { createDomPainter } from './index.js'; +import { createTestPainter } from './test-utils/test-painter.js'; import type { FlowBlock, Measure, Layout, WordParagraphLayoutOutput } from '@superdoc/contracts'; describe('DomPainter markerTextWidth feature', () => { @@ -124,7 +124,7 @@ describe('DomPainter markerTextWidth feature', () => { // markerTextWidth is undefined const layout = createListLayout(blockId, markerBoxWidth, undefined); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -146,7 +146,7 @@ describe('DomPainter markerTextWidth feature', () => { // @ts-expect-error Testing null case explicitly const layout = createListLayout(blockId, markerBoxWidth, null); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -170,7 +170,7 @@ describe('DomPainter markerTextWidth feature', () => { const markerTextWidth = 18; // Actual text is narrower const layout = createListLayout(blockId, markerBoxWidth, markerTextWidth); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -196,7 +196,7 @@ describe('DomPainter markerTextWidth feature', () => { const markerTextWidth = 15; // Significantly narrower than box const layout = createListLayout(blockId, markerBoxWidth, markerTextWidth); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -223,7 +223,7 @@ describe('DomPainter markerTextWidth feature', () => { const markerGutter = 12; const layout = createListLayout(blockId, markerBoxWidth, markerTextWidth, markerGutter); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -248,7 +248,7 @@ describe('DomPainter markerTextWidth feature', () => { const markerTextWidth = 0; // Zero width text const layout = createListLayout(blockId, markerBoxWidth, markerTextWidth); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -268,7 +268,7 @@ describe('DomPainter markerTextWidth feature', () => { const markerGutter = 16; const layout = createListLayout(blockId, markerBoxWidth, markerTextWidth, markerGutter); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -289,7 +289,7 @@ describe('DomPainter markerTextWidth feature', () => { const markerTextWidth = -10; // Invalid negative value const layout = createListLayout(blockId, markerBoxWidth, markerTextWidth); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -315,7 +315,7 @@ describe('DomPainter markerTextWidth feature', () => { const markerTextWidth = Infinity; // Invalid infinite value const layout = createListLayout(blockId, markerBoxWidth, markerTextWidth); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -339,7 +339,7 @@ describe('DomPainter markerTextWidth feature', () => { const markerTextWidth = NaN; // Invalid NaN value const layout = createListLayout(blockId, markerBoxWidth, markerTextWidth); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -360,7 +360,7 @@ describe('DomPainter markerTextWidth feature', () => { const markerTextWidth = 18; const layout = createListLayout(blockId, markerBoxWidth, markerTextWidth); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -382,7 +382,7 @@ describe('DomPainter markerTextWidth feature', () => { const markerTextWidth = 18; const layout = createListLayout(blockId, markerBoxWidth, markerTextWidth); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -405,7 +405,7 @@ describe('DomPainter markerTextWidth feature', () => { const markerTextWidth = 20; const layout = createListLayout(blockId, markerBoxWidth, markerTextWidth); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -430,7 +430,7 @@ describe('DomPainter markerTextWidth feature', () => { const markerTextWidth = 45; // Text is narrower const layout = createListLayout(blockId, markerBoxWidth, markerTextWidth); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -460,7 +460,7 @@ describe('DomPainter markerTextWidth feature', () => { const markerTextWidth = 8; // Bullet is very narrow const layout = createListLayout(blockId, markerBoxWidth, markerTextWidth); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -486,7 +486,7 @@ describe('DomPainter markerTextWidth feature', () => { const markerTextWidth = 22; // Same as box width const layout = createListLayout(blockId, markerBoxWidth, markerTextWidth); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); diff --git a/packages/layout-engine/painters/dom/src/renderer-parity-contracts.test.ts b/packages/layout-engine/painters/dom/src/renderer-parity-contracts.test.ts index a7776ccee5..515473f6c3 100644 --- a/packages/layout-engine/painters/dom/src/renderer-parity-contracts.test.ts +++ b/packages/layout-engine/painters/dom/src/renderer-parity-contracts.test.ts @@ -10,7 +10,7 @@ */ import { describe, it, expect, beforeEach } from 'vitest'; -import { createDomPainter } from './index.js'; +import { createTestPainter } from './test-utils/test-painter.js'; import type { FlowBlock, Measure, Layout, ParagraphMeasure, Line, Run } from '@superdoc/contracts'; import { normalizeLines, type NormalizedLine } from './test-utils/normalize-line.js'; @@ -187,7 +187,7 @@ function tableCellFixtures(opts: { text?: string; runs?: Run[]; attrs?: Record; @@ -79,7 +79,7 @@ describe('DomPainter shape regressions', () => { }; const { blocks, measures, layout } = createDrawingFixtures(drawingBlock); - const painter = createDomPainter({ blocks, measures }); + const painter = createTestPainter({ blocks, measures }); painter.paint(layout, mount); const renderedPath = mount.querySelector(`.superdoc-vector-shape svg path[d="${customPath}"]`); @@ -103,7 +103,7 @@ describe('DomPainter shape regressions', () => { }; const { blocks, measures, layout } = createDrawingFixtures(drawingBlock); - const painter = createDomPainter({ blocks, measures }); + const painter = createTestPainter({ blocks, measures }); painter.paint(layout, mount); const path = mount.querySelector('.superdoc-vector-shape svg path') as SVGPathElement | null; @@ -146,7 +146,7 @@ describe('DomPainter shape regressions', () => { }; const { blocks, measures, layout } = createDrawingFixtures(drawingBlock); - const painter = createDomPainter({ blocks, measures }); + const painter = createTestPainter({ blocks, measures }); painter.paint(layout, mount); const textOverlay = mount.querySelector( diff --git a/packages/layout-engine/painters/dom/src/renderer-vector-shape-geometry.test.ts b/packages/layout-engine/painters/dom/src/renderer-vector-shape-geometry.test.ts index e880659bfb..1db3663267 100644 --- a/packages/layout-engine/painters/dom/src/renderer-vector-shape-geometry.test.ts +++ b/packages/layout-engine/painters/dom/src/renderer-vector-shape-geometry.test.ts @@ -7,7 +7,7 @@ */ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; -import { createDomPainter } from './index.js'; +import { createTestPainter } from './test-utils/test-painter.js'; import type { FlowBlock, Measure, Layout, DrawingGeometry } from '@superdoc/contracts'; describe('DomPainter vector shape geometry', () => { @@ -89,7 +89,7 @@ describe('DomPainter vector shape geometry', () => { ], }; - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [drawingBlock], measures: [drawingMeasure], }); @@ -171,7 +171,7 @@ describe('DomPainter vector shape geometry', () => { ], }; - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [drawingBlock], measures: [drawingMeasure], }); @@ -243,7 +243,7 @@ describe('DomPainter vector shape geometry', () => { ], }; - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [drawingBlock], measures: [drawingMeasure], }); diff --git a/packages/layout-engine/painters/dom/src/test-utils/test-painter.ts b/packages/layout-engine/painters/dom/src/test-utils/test-painter.ts new file mode 100644 index 0000000000..bc59ce4578 --- /dev/null +++ b/packages/layout-engine/painters/dom/src/test-utils/test-painter.ts @@ -0,0 +1,76 @@ +/** + * Test-only bridge around `createDomPainter`. + * + * The production painter accepts a pre-computed `DomPainterInput` + * (`{ resolvedLayout, sourceLayout }`). Most tests, however, still express + * their fixtures as `{ blocks, measures }` plus a raw `Layout`. This helper + * lets those tests keep that shape by running `resolveLayout` internally + * on every `paint(layout, mount)` call. + * + * Use this in tests only. Production code must call `createDomPainter` + * directly with a `DomPainterInput`. + */ + +import { createDomPainter } from '../index.js'; +import type { DomPainterHandle, DomPainterOptions } from '../index.js'; +import { resolveLayout } from '@superdoc/layout-resolved'; +import type { DomPainterInput, PositionMapping } from '../renderer.js'; +import type { FlowBlock, Layout, Measure, ResolvedLayout } from '@superdoc/contracts'; + +const emptyResolved: ResolvedLayout = { + version: 1, + flowMode: 'paginated', + pageGap: 0, + pages: [], +}; + +export type TestPainterOptions = { blocks?: FlowBlock[]; measures?: Measure[] } & DomPainterOptions; + +export type TestPainterHandle = Omit & { + /** Accepts either a raw `Layout` (resolved internally) or a full `DomPainterInput`. */ + paint(input: DomPainterInput | Layout, mount: HTMLElement, mapping?: PositionMapping): void; + /** Update the blocks/measures used for subsequent `paint(layout, mount)` calls. */ + setData(blocks: FlowBlock[], measures: Measure[]): void; +}; + +export function createTestPainter(opts: TestPainterOptions): TestPainterHandle { + const { blocks: initBlocks, measures: initMeasures, ...painterOpts } = opts; + const painter = createDomPainter(painterOpts); + + let currentBlocks: FlowBlock[] = initBlocks ?? []; + let currentMeasures: Measure[] = initMeasures ?? []; + + return { + paint(input: DomPainterInput | Layout, mount: HTMLElement, mapping?: PositionMapping) { + const resolvedInput: DomPainterInput = isDomPainterInput(input) + ? input + : { + resolvedLayout: + currentBlocks.length === 0 && currentMeasures.length === 0 + ? emptyResolved + : resolveLayout({ + layout: input, + flowMode: opts.flowMode ?? 'paginated', + blocks: currentBlocks, + measures: currentMeasures, + }), + sourceLayout: input, + }; + painter.paint(resolvedInput, mount, mapping); + }, + setData(blocks: FlowBlock[], measures: Measure[]) { + currentBlocks = blocks; + currentMeasures = measures; + }, + setProviders: painter.setProviders, + setVirtualizationPins: painter.setVirtualizationPins, + getMountedPageIndices: painter.getMountedPageIndices, + onScroll: painter.onScroll, + setZoom: painter.setZoom, + setScrollContainer: painter.setScrollContainer, + }; +} + +function isDomPainterInput(value: DomPainterInput | Layout): value is DomPainterInput { + return typeof value === 'object' && value !== null && 'resolvedLayout' in value && 'sourceLayout' in value; +} diff --git a/packages/layout-engine/painters/dom/src/text-style-rendering.test.ts b/packages/layout-engine/painters/dom/src/text-style-rendering.test.ts index 536ef9a44b..78ddb15d84 100644 --- a/packages/layout-engine/painters/dom/src/text-style-rendering.test.ts +++ b/packages/layout-engine/painters/dom/src/text-style-rendering.test.ts @@ -5,7 +5,7 @@ */ import { describe, it, expect, beforeEach } from 'vitest'; -import { createDomPainter } from './index.js'; +import { createTestPainter } from './test-utils/test-painter.js'; import type { FlowBlock, Measure, Layout } from '@superdoc/contracts'; const expectCssColor = (actual: string, expectedHex: string): void => { @@ -105,7 +105,7 @@ describe('DomPainter text style CSS rendering', () => { const measure = createParagraphMeasure(); const layout = createParagraphLayout('para-1'); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -132,7 +132,7 @@ describe('DomPainter text style CSS rendering', () => { const measure = createParagraphMeasure(); const layout = createParagraphLayout('para-2'); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -159,7 +159,7 @@ describe('DomPainter text style CSS rendering', () => { const measure = createParagraphMeasure(); const layout = createParagraphLayout('para-3'); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -186,7 +186,7 @@ describe('DomPainter text style CSS rendering', () => { const measure = createParagraphMeasure(); const layout = createParagraphLayout('para-4'); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -212,7 +212,7 @@ describe('DomPainter text style CSS rendering', () => { const measure = createParagraphMeasure(); const layout = createParagraphLayout('para-5'); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -270,7 +270,7 @@ describe('DomPainter text style CSS rendering', () => { const layout = createParagraphLayout('para-6'); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -307,7 +307,7 @@ describe('DomPainter text style CSS rendering', () => { const measure = createParagraphMeasure(); const layout = createParagraphLayout('para-7'); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -337,7 +337,7 @@ describe('DomPainter text style CSS rendering', () => { const measure = createParagraphMeasure(); const layout = createParagraphLayout('para-va-1'); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -366,7 +366,7 @@ describe('DomPainter text style CSS rendering', () => { const measure = createParagraphMeasure(); const layout = createParagraphLayout('para-va-2'); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -395,7 +395,7 @@ describe('DomPainter text style CSS rendering', () => { const measure = createParagraphMeasure(); const layout = createParagraphLayout('para-va-3'); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -422,7 +422,7 @@ describe('DomPainter text style CSS rendering', () => { const measure = createParagraphMeasure(); const layout = createParagraphLayout('para-va-4'); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -450,7 +450,7 @@ describe('DomPainter text style CSS rendering', () => { const measure = createParagraphMeasure(); const layout = createParagraphLayout('para-va-5'); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -479,7 +479,7 @@ describe('DomPainter text style CSS rendering', () => { const measure = createParagraphMeasure(); const layout = createParagraphLayout('para-va-5-zero'); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -507,7 +507,7 @@ describe('DomPainter text style CSS rendering', () => { const measure = createParagraphMeasure(); const layout = createParagraphLayout('para-va-6'); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); @@ -550,7 +550,7 @@ describe('DomPainter text style CSS rendering', () => { const layout = createParagraphLayout('para-8'); - const painter = createDomPainter({ + const painter = createTestPainter({ blocks: [block], measures: [measure], }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9dfef890ef..9e373594c2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2479,6 +2479,9 @@ importers: '@superdoc/layout-engine': specifier: workspace:* version: link:../layout-engine + '@superdoc/layout-resolved': + specifier: workspace:* + version: link:../layout-resolved '@superdoc/measuring-dom': specifier: workspace:* version: link:../measuring/dom