Ask what a single note's export should contain, and fix drawings never being in one

Two questions instead of one, because the answers depend on each other:
Markdown holds text only, a PNG holds the drawing only, PDF holds both.
Listing every format flat would have offered combinations that cannot
exist. A note without a drawing skips the first step rather than showing
it mostly greyed out.

While testing that, the drawings turned out never to have made it into
any export at all — not the ZIP, not the PDF. strokeDocOf() *builds* a
stroke document from a bare array, and it was being handed one that was
already a document, so the strokes ended up nested one level too deep and
every length check read undefined. Nothing complained; the export simply
came out without the drawing. It went unnoticed because the notes the
earlier suite exported had none.

Now uses docStrokes(), and the suite draws on a note before exporting it,
checks the PNG's magic bytes, and checks it is 159 KB rather than an empty
white page — a check on the file merely existing would have passed against
the broken version too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-08-08 17:37:22 +02:00
1 parent 2611d87f8c
commit 8c36c6121b
5 files changed
+284 -57

No files matched your search

+79 -3
View File
@@ -72,9 +72,29 @@ function readZip(bytes) {
const openExportDialog = async (page) => {
await page.getByTitle("Export").click();
await page.waitForSelector("[data-export-dialog]", { timeout: 10000 });
await page.waitForTimeout(500);
await page.waitForTimeout(800);
};
/** Draws a couple of strokes so the note has something to export as a picture. */
async function drawSomething(page) {
await page.locator("button").filter({ hasText: "Draw" }).first().click();
await page.waitForTimeout(1200);
const box = await page.locator("canvas").first().boundingBox();
for (const [x1, y1, x2, y2] of [
[0.2, 0.25, 0.7, 0.4],
[0.3, 0.6, 0.7, 0.55],
]) {
await page.mouse.move(box.x + box.width * x1, box.y + box.height * y1);
await page.mouse.down();
await page.mouse.move(box.x + box.width * x2, box.y + box.height * y2, { steps: 12 });
await page.mouse.up();
await page.waitForTimeout(250);
}
await page.waitForTimeout(2500);
await page.locator("button").filter({ hasText: "Write" }).first().click();
await page.waitForTimeout(800);
}
const browser = await launch();
let session;
try {
@@ -104,7 +124,7 @@ try {
/* ── single note as Markdown ── */
await openExportDialog(p);
await p.getByRole("button", { name: /Als Markdown/ }).click();
await p.getByRole("button", { name: /Markdown/ }).click();
await p.waitForSelector("[data-export-done]", { timeout: 30000 });
await p.waitForTimeout(600);
@@ -143,7 +163,7 @@ try {
await p.evaluate(() => (window.__downloads = []));
await openExportDialog(p);
await p.getByRole("button", { name: /Als Markdown/ }).click();
await p.getByRole("button", { name: /Markdown/ }).click();
await p.waitForSelector("[data-export-done]", { timeout: 30000 });
await p.waitForTimeout(800);
files = await downloads(p);
@@ -165,6 +185,62 @@ try {
);
await p.screenshot({ path: `${OUT}/export-dialog.png` });
/* ── the wizard on a note that has a drawing ── */
await createNote(p);
await setTitle(p, "Mit Zeichnung");
await typeInEditor(p, "Text und Bild.");
await drawSomething(p);
await p.evaluate(() => (window.__downloads = []));
await openExportDialog(p);
check(
(await p.getByText("Was soll mit?").count()) > 0,
"a note with a drawing is asked what should come along"
);
check(
(await p.getByRole("button", { name: /Nur die Zeichnung/ }).count()) > 0,
"the drawing-only option is offered"
);
await p.screenshot({ path: `${OUT}/export-wizard.png` });
// drawing only → PNG
await p.getByRole("button", { name: /Nur die Zeichnung/ }).click();
await p.waitForTimeout(500);
check((await p.getByRole("button", { name: /PNG-Bild/ }).count()) > 0, "PNG is offered for the drawing");
check(
(await p.getByRole("button", { name: /Markdown/ }).count()) === 0,
"Markdown is not offered for a drawing, since it cannot hold one"
);
await p.getByRole("button", { name: /PNG-Bild/ }).click();
await p.waitForSelector("[data-export-done]", { timeout: 30000 });
await p.waitForTimeout(800);
files = await downloads(p);
check(files.length === 1 && files[0].type === "image/png", "the PNG was produced", files[0]?.type);
const png = Buffer.from(files[0].base64, "base64");
check(
png[0] === 0x89 && png[1] === 0x50 && png[2] === 0x4e && png[3] === 0x47,
"and it really is a PNG",
Array.from(png.slice(0, 4)).join(",")
);
check(/\.png$/.test(await lastName(p)), "named .png", await lastName(p));
check(png.length > 1000, "the image has actual content, not an empty canvas", `${png.length} bytes`);
// back, then text only → Markdown must carry no drawing
await p.getByRole("button", { name: /Zurück/ }).click();
await p.waitForTimeout(500);
await p.getByRole("button", { name: /Nur den Notiztext/ }).click();
await p.waitForTimeout(500);
await p.evaluate(() => (window.__downloads = []));
await p.getByRole("button", { name: /Markdown/ }).click();
await p.waitForSelector("[data-export-done]", { timeout: 30000 });
await p.waitForTimeout(800);
files = await downloads(p);
const textMd = Buffer.from(files[0].base64, "base64").toString("utf8");
check(textMd.includes("Text und Bild."), "the text-only export has the text");
await p.keyboard.press("Escape");
await p.waitForTimeout(600);
/* ── whole notebook, via the sidebar's context menu ── */
await p.evaluate(() => (window.__downloads = []));
await p.locator("[data-note-row]").first().click({ button: "right" });