Files
edentext/tests/unit/wrap-through.test.ts
Steffen Becker 5b287ef57b feat: behind and in front of the text, pickable and draggable
Both frame toolbars and the ribbon's Arrange group now offer the two
run-through entries; they are one wrap mode, told apart by inFront.
Picking any mode by hand drops the offsets and the coordinate system
they were measured in (droppedFrameAttrs), so a page-placed frame set
to a side wrap no longer exports page-relative coordinates for a
paragraph-relative offset.
A frame out of the flow has no text position to re-anchor to, so the
drag adds the pointer delta to its own offsets (startFreeMove).
2026-09-12 02:35:00 +02:00

70 lines
3.6 KiB
TypeScript

// Word's in-front-of / behind-text (wp:wrapNone + behindDoc) and ODF's run-through:
// the text runs over or under the frame, so it reserves neither width nor height.
import { describe, it, expect } from 'vitest';
import { buildOdt } from '../../src/lib/export/odt';
import { buildDocx } from '../../src/lib/export/docx';
import { importOdt } from '../../src/lib/import/odt';
import { importDocx } from '../../src/lib/import/docx';
import { droppedFrameAttrs } from '../../src/lib/editor/extensions/image';
type N = any;
const PNG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGNwaDgAAAKEAYEml6crAAAAAElFTkSuQmCC';
const IMG = (attrs: N): N => ({ type: 'image', attrs: { src: PNG, width: 200, height: 120, ...attrs } });
const docWith = (attrs: N): N => ({
type: 'doc',
content: [{ type: 'paragraph', content: [IMG(attrs), { type: 'text', text: 'anchor text' }] }],
});
const frame = (d: N) => d.content[0].content.find((n: N) => n.type === 'image');
const margins = { top: 2, bottom: 2, left: 2, right: 2 };
describe('a run-through frame', () => {
it('round-trips the mode and its z-order through ODF', async () => {
const front = frame((await importOdt(await buildOdt(docWith({ wrap: 'through', inFront: true }), margins, 'portrait'))).content);
expect(front.attrs.wrap).toBe('through');
expect(front.attrs.inFront).toBe(true);
const behind = frame((await importOdt(await buildOdt(docWith({ wrap: 'through' }), margins, 'portrait'))).content);
expect(behind.attrs.wrap).toBe('through');
expect(behind.attrs.inFront).not.toBe(true);
});
it('round-trips the mode and its z-order through DOCX', async () => {
const front = frame(importDocx(await buildDocx(docWith({ wrap: 'through', inFront: true }), margins, 'portrait')).content);
expect(front.attrs.wrap).toBe('through');
expect(front.attrs.inFront).toBe(true);
const behind = frame(importDocx(await buildDocx(docWith({ wrap: 'through' }), margins, 'portrait')).content);
expect(behind.attrs.wrap).toBe('through');
expect(behind.attrs.inFront).toBe(false);
});
it('keeps its offsets, which place it against the anchor rather than reserve space', async () => {
const img = frame((await importOdt(await buildOdt(docWith({ wrap: 'through', wrapOffset: 1.5, wrapOffsetY: 4.25 }), margins, 'portrait'))).content);
expect(img.attrs.wrapOffset).toBeCloseTo(1.5, 2);
expect(img.attrs.wrapOffsetY).toBeCloseTo(4.25, 2);
});
// ODF's `none` is the one that means above-and-below; only run-through reserves nothing.
it('is not what ODF style:wrap="none" means', async () => {
const img = frame((await importOdt(await buildOdt(docWith({ wrap: 'topBottom' }), margins, 'portrait'))).content);
expect(img.attrs.wrap).toBe('topBottom');
});
});
// Picking a mode by hand is "put it there": the offsets go, and so do the coordinate
// systems they were measured in — a page-placed frame set to a side wrap would
// otherwise export as vertical-rel="page" with an offset that now counts from a paragraph.
describe('picking a wrap mode by hand', () => {
it('drops the offsets and the frame of reference they belonged to', () => {
const dropped = droppedFrameAttrs('left', false);
expect(dropped).toMatchObject({ wrapOffset: null, wrapOffsetY: null, wrapFromPage: false, anchorPage: null });
});
it('keeps inFront for run-through alone', () => {
expect(droppedFrameAttrs('through', true).inFront).toBe(true);
expect(droppedFrameAttrs('through', false).inFront).toBe(false);
expect(droppedFrameAttrs('topBottom', true).inFront).toBe(false);
});
});