test: fuzz round 2 — cell merges, list types, inline images

colspan merges (grid-consistent), orderedList listStyleType (upper-* only on
nested lists: lower/decimal forms are depth defaults the importers suppress),
and inline PNG images. 300 sweep seeds clean on both legs.
This commit is contained in:
Steffen Becker committed 2026-08-16 07:58:24 +02:00
1 parent 5258feb146
commit 5dab73ac3a
1 file changed
+24 -8
+24 -8
View File
@@ -94,6 +94,9 @@ function paragraph(r: Rng, indents = true, top = false): N {
body.push(run);
if (maybe(r, 0.1)) body.push({ type: 'hardBreak' });
}
if (maybe(r, 0.06)) {
body.push({ type: 'image', attrs: { src: PNG, width: int(r, 40, 200), height: int(r, 30, 120) } });
}
return { type: 'paragraph', ...(attrs ? { attrs } : {}), content: body };
}
@@ -105,22 +108,35 @@ function list(r: Rng, kind: 'bulletList' | 'orderedList', depth: number): N {
});
// start only at the top level: applyListStartValues writes text:start-value for
// top-level lists only, a nested list's start does not round-trip.
const attrs: N = kind === 'orderedList' && depth === 0 && maybe(r, 0.2) ? { start: 3 } : null;
return { type: kind, ...(attrs ? { attrs } : {}), content: items };
const attrs: N = {};
if (kind === 'orderedList' && depth === 0 && maybe(r, 0.2)) attrs.start = 3;
// Nested lists get only upper-* types: those are never the depth default, which the
// importers suppress to null (defaultOrderedTypeAt yields lower/decimal forms only).
if (kind === 'orderedList' && maybe(r, 0.25)) {
attrs.listStyleType = depth === 0
? pick(r, ['lower-alpha', 'upper-roman', 'lower-alpha-paren'])
: pick(r, ['upper-roman', 'upper-alpha-paren']);
}
return { type: kind, ...(Object.keys(attrs).length ? { attrs } : {}), content: items };
}
// A tiny valid PNG; only its bytes matter for the round-trip (no image decoding).
const PNG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
function table(r: Rng): N {
const cols = int(r, 1, 3);
const rows = Array.from({ length: int(r, 1, 3) }, () => ({
type: 'tableRow',
content: Array.from({ length: cols }, () => {
const attrs: N = { colspan: 1, rowspan: 1, colwidth: null };
const rows = Array.from({ length: int(r, 1, 3) }, () => {
// one two-column merge per row at most, so the grid stays consistent
const merge = cols >= 2 && maybe(r, 0.15);
const cells = Array.from({ length: merge ? cols - 1 : cols }, (_, i) => {
const attrs: N = { colspan: merge && i === 0 ? 2 : 1, rowspan: 1, colwidth: null };
// not #F2F2F2: that exact shade is HEADER_SHADE, whose bold is presentational
if (maybe(r, 0.15)) attrs.backgroundColor = pick(r, ['#FFFF00', '#DDEEFF']);
if (maybe(r, 0.1)) attrs.verticalAlign = pick(r, ['middle', 'bottom']);
return { type: 'tableCell', attrs, content: [paragraph(r)] };
}),
}));
});
return { type: 'tableRow', content: cells };
});
return { type: 'table', content: rows };
}