Files
edentext/tests/unit/caption.test.ts
T
Steffen Becker c4a74f92b1 fix: a caption stands under its picture, not at the margin
It was inserted as a plain paragraph, so a centred or right-aligned
picture got its caption at the left edge of the text column.
LibreOffice frames picture and caption together; a loose paragraph
carries that over as the alignment of the block it captions.
Measured in the live editor: a centred 240px picture at 520…760
now has its caption text at 580…700.
2026-08-14 22:40:42 +02:00

94 lines
3.8 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { Schema } from '@tiptap/pm/model';
import { Editor } from '@tiptap/core';
import Document from '@tiptap/extension-document';
import Paragraph from '@tiptap/extension-paragraph';
import Text from '@tiptap/extension-text';
import TextAlign from '@tiptap/extension-text-align';
import { sequenceFields, ODF_SEQ_CATEGORY, seqCategoryOf, SequenceField } from '../../src/lib/editor/extensions/caption';
// The numbering rule both word processors apply: one counter per category, in document
// order. A schema of its own keeps the check off the editor's full extension list.
const schema = new Schema({
nodes: {
doc: { content: 'block+' },
paragraph: { group: 'block', content: 'inline*', toDOM: () => ['p', 0] },
text: { group: 'inline' },
sequenceField: {
group: 'inline', inline: true, atom: true,
attrs: { category: { default: 'figure' }, number: { default: 1 } },
toDOM: () => ['span'],
},
},
});
const seq = (category: string, number = 1) => schema.nodes.sequenceField.create({ category, number });
const para = (...content: ReturnType<typeof seq>[]) => schema.nodes.paragraph.create(null, content);
describe('sequenceFields', () => {
it('counts each category on its own, in document order', () => {
const doc = schema.nodes.doc.create(null, [
para(seq('figure')), para(seq('table')), para(seq('figure')), para(seq('table')), para(seq('figure')),
]);
expect(sequenceFields(doc).map((f) => `${f.node.attrs.category}${f.number}`))
.toEqual(['figure1', 'table1', 'figure2', 'table2', 'figure3']);
});
it('numbers two fields in one paragraph left to right', () => {
const doc = schema.nodes.doc.create(null, [para(seq('figure'), seq('figure'))]);
expect(sequenceFields(doc).map((f) => f.number)).toEqual([1, 2]);
});
it('reports the stale cached number, which is what triggers a renumber', () => {
const doc = schema.nodes.doc.create(null, [para(seq('figure', 7))]);
const [only] = sequenceFields(doc);
expect(only.number).toBe(1);
expect(only.node.attrs.number).toBe(7);
});
});
describe('insertCaption', () => {
const makeEditor = (textAlign: string | null) => {
const el = document.createElement('div');
document.body.appendChild(el);
return new Editor({
element: el,
extensions: [Document, Paragraph, Text, SequenceField, TextAlign.configure({ types: ['paragraph'] })],
content: { type: 'doc', content: [{ type: 'paragraph', attrs: { textAlign }, content: [{ type: 'text', text: 'picture' }] }] },
});
};
const insert = (editor: Editor) =>
editor.chain().focus().insertCaption({ category: 'figure', label: 'Figure', separator: ': ', text: 'x', above: false }).run();
it('takes the alignment of the block it captions, so it stands under the picture', () => {
const editor = makeEditor('center');
insert(editor);
const [, caption] = editor.getJSON().content as any[];
expect(caption.attrs.textAlign).toBe('center');
editor.destroy();
});
it('leaves the caption of an unaligned block unaligned', () => {
const editor = makeEditor(null);
insert(editor);
const [, caption] = editor.getJSON().content as any[];
expect(caption.attrs.textAlign ?? null).toBe(null);
editor.destroy();
});
});
describe('category names', () => {
it('reads both spellings of the picture counter', () => {
expect(ODF_SEQ_CATEGORY.Illustration).toBe('figure'); // LibreOffice's
expect(ODF_SEQ_CATEGORY.Figure).toBe('figure'); // Word's
expect(ODF_SEQ_CATEGORY.Table).toBe('table');
expect(ODF_SEQ_CATEGORY.Drawing).toBeUndefined();
});
it('falls back to the picture category', () => {
expect(seqCategoryOf('table')).toBe('table');
expect(seqCategoryOf(null)).toBe('figure');
expect(seqCategoryOf('nonsense')).toBe('figure');
});
});