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