From f4973caadeb29f0e6ad7616484abdfbc889e5f74 Mon Sep 17 00:00:00 2001 From: Steffen Becker Date: Mon, 27 Jul 2026 20:38:13 +0200 Subject: [PATCH] test: add unit test for display of font size --- src/lib/components/ToolbarExpanded.svelte | 22 +++------------- src/lib/utils/fontSize.ts | 19 ++++++++++++++ tests/unit/font-size-display.test.ts | 32 +++++++++++++++++++++++ 3 files changed, 54 insertions(+), 19 deletions(-) create mode 100644 src/lib/utils/fontSize.ts create mode 100644 tests/unit/font-size-display.test.ts diff --git a/src/lib/components/ToolbarExpanded.svelte b/src/lib/components/ToolbarExpanded.svelte index c39e4cc..1e00305 100644 --- a/src/lib/components/ToolbarExpanded.svelte +++ b/src/lib/components/ToolbarExpanded.svelte @@ -18,7 +18,7 @@ import type { Orientation } from '../storage/pageOrientation'; import { pageDimsCm, PAGE_FORMAT_CM, type PageFormat } from '../storage/pageFormat'; import { DEFAULT_HF_DISTANCES, clampHfDistance, type HfDistances } from '../storage/headerFooter'; - import { HEADING_STYLE_OVERRIDES } from '../export/odt'; + import { blockFontSize, DEFAULT_FONT_SIZE, type SizedBlock } from '../utils/fontSize'; import { listContext } from '../editor/extensions/indent'; import { findColumns, DEFAULT_COLUMN_GAP_CM } from '../editor/extensions/columns'; import { t } from '../i18n/i18n.svelte'; @@ -129,27 +129,11 @@ return mixed ? '' : (font ?? DEFAULT_EDITOR_FONT); }); - // Must match the common document default; heading sizes come from the export overrides - // (the single source editor.css and both importers agree with). - const DEFAULT_FONT_SIZE = '12pt'; - const HEADING_SIZES: Record = - Object.fromEntries(HEADING_STYLE_OVERRIDES.map((h, i) => [i + 1, h.fontSize])); const FONT_SIZES = [8, 9, 10, 11, 12, 14, 16, 18, 20, 24, 28, 36, 48, 72]; - type SizedBlock = { type: { name: string }; attrs: Record } | null; - - // What a block renders its unmarked text at: its paragraph-mark size (blockFontSize, - // set by the style gallery and by imported sized lines), else the heading/body default. - function blockSize(parent: SizedBlock): string { - const block = parent?.attrs?.fontSize; - if (typeof block === 'string' && block) return block; - if (parent?.type.name === 'heading') return HEADING_SIZES[Number(parent.attrs.level)] ?? DEFAULT_FONT_SIZE; - return DEFAULT_FONT_SIZE; - } - function effectiveSize(node: { isText: boolean; marks: readonly { type: { name: string }; attrs: Record }[] }, parent: SizedBlock): string { const explicit = node.marks.find(m => m.type.name === 'textStyle')?.attrs.fontSize; - return explicit || blockSize(parent); + return explicit || blockFontSize(parent); } let currentFontSize = $derived.by(() => { @@ -160,7 +144,7 @@ const marks = editor.state.storedMarks ?? head.marks(); const explicit = marks.find(m => m.type.name === 'textStyle')?.attrs.fontSize; if (explicit) return explicit; - return blockSize(head.parent as SizedBlock); + return blockFontSize(head.parent as SizedBlock); } let size: string | undefined; let mixed = false; diff --git a/src/lib/utils/fontSize.ts b/src/lib/utils/fontSize.ts new file mode 100644 index 0000000..82c50e8 --- /dev/null +++ b/src/lib/utils/fontSize.ts @@ -0,0 +1,19 @@ +import { HEADING_STYLE_OVERRIDES } from '../export/odt'; + +// The size a block renders its unmarked text at — what the toolbar's size box must show. +// Keep in sync with editor.css (tests/unit/font-size-display.test.ts asserts it). + +export const DEFAULT_FONT_SIZE = '12pt'; + +export const HEADING_SIZES: Record = + Object.fromEntries(HEADING_STYLE_OVERRIDES.map((h, i) => [i + 1, h.fontSize])); + +export type SizedBlock = { type: { name: string }; attrs?: Record } | null; + +// Paragraph-mark size (blockFontSize attr) wins, else the heading/body default. +export function blockFontSize(block: SizedBlock): string { + const explicit = block?.attrs?.fontSize; + if (typeof explicit === 'string' && explicit) return explicit; + if (block?.type.name === 'heading') return HEADING_SIZES[Number(block.attrs?.level)] ?? DEFAULT_FONT_SIZE; + return DEFAULT_FONT_SIZE; +} diff --git a/tests/unit/font-size-display.test.ts b/tests/unit/font-size-display.test.ts new file mode 100644 index 0000000..d960e14 --- /dev/null +++ b/tests/unit/font-size-display.test.ts @@ -0,0 +1,32 @@ +import { describe, it, expect } from 'vitest'; +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { blockFontSize, DEFAULT_FONT_SIZE, HEADING_SIZES } from '../../src/lib/utils/fontSize'; + +// The toolbar's size box shows blockFontSize() for text without its own size mark, while +// the page renders what editor.css says — so the two tables must not drift apart. +const css = readFileSync(resolve(process.cwd(), 'src/styles/editor.css'), 'utf8'); + +function cssFontSize(selector: string): string | null { + const re = new RegExp(`${selector.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}\\s*\\{[^}]*?font-size:\\s*([^;]+);`); + return css.match(re)?.[1].trim() ?? null; +} + +describe('toolbar font size vs. rendered font size', () => { + it('shows the paragraph-mark size, else the block default', () => { + const para = { type: { name: 'paragraph' }, attrs: {} }; + expect(blockFontSize(para)).toBe(DEFAULT_FONT_SIZE); + expect(blockFontSize({ ...para, attrs: { fontSize: '22pt' } })).toBe('22pt'); + expect(blockFontSize({ type: { name: 'heading' }, attrs: { level: 5 } })).toBe(HEADING_SIZES[5]); + // An explicit paragraph-mark size beats the heading default (style gallery, imports). + expect(blockFontSize({ type: { name: 'heading' }, attrs: { level: 1, fontSize: '9pt' } })).toBe('9pt'); + expect(blockFontSize(null)).toBe(DEFAULT_FONT_SIZE); + }); + + it('matches the sizes editor.css renders', () => { + expect(cssFontSize('.paper .tiptap')).toBe(DEFAULT_FONT_SIZE); + for (const [level, size] of Object.entries(HEADING_SIZES)) { + expect(cssFontSize(`.paper .tiptap h${level}`), `h${level}`).toBe(size); + } + }); +});