test: add unit test for display of font size

This commit is contained in:
Steffen Becker committed 2026-07-27 20:38:13 +02:00
1 parent 56f884a412
commit f4973caade
3 files changed
+54 -19

No files matched your search

+3 -19
View File
@@ -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<number, string> =
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<string, unknown> } | 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<string, string> }[] }, 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;
+19
View File
@@ -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<number, string> =
Object.fromEntries(HEADING_STYLE_OVERRIDES.map((h, i) => [i + 1, h.fontSize]));
export type SizedBlock = { type: { name: string }; attrs?: Record<string, unknown> } | 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;
}
+32
View File
@@ -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);
}
});
});