Skip to content

Commit 3f3f5ee

Browse files
test: add E2E tests for markdown import and export (#112) (#116)
Co-authored-by: Ona <no-reply@ona.com>
1 parent a045758 commit 3f3f5ee

1 file changed

Lines changed: 151 additions & 0 deletions

File tree

e2e/import-export.spec.ts

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import { test, expect } from "./fixtures/auth";
2+
import { navigateToEditorPage } from "./fixtures/editor-helpers";
3+
4+
test.describe("Markdown import and export", () => {
5+
test("user can export a page as markdown via the page menu", async ({
6+
authenticatedPage: page,
7+
}) => {
8+
await navigateToEditorPage(page);
9+
10+
const editor = page.locator('[contenteditable="true"]');
11+
await expect(editor).toBeVisible({ timeout: 10_000 });
12+
13+
// Type some structured content: heading, paragraph, list
14+
await editor.click();
15+
await page.keyboard.press("End");
16+
await page.keyboard.press("Enter");
17+
18+
// Insert a heading via slash command
19+
await page.keyboard.type("/");
20+
const heading1Option = page
21+
.locator('[role="option"]')
22+
.filter({ hasText: "Heading 1" });
23+
await expect(heading1Option).toBeVisible({ timeout: 3_000 });
24+
await heading1Option.click();
25+
await page.keyboard.type("Export Test Heading");
26+
await page.keyboard.press("Enter");
27+
28+
// Type a paragraph
29+
await page.keyboard.type("This is a test paragraph for export.");
30+
await page.keyboard.press("Enter");
31+
32+
// Insert a bullet list via slash command
33+
await page.keyboard.type("/");
34+
const bulletOption = page
35+
.locator('[role="option"]')
36+
.filter({ hasText: "Bullet List" });
37+
await expect(bulletOption).toBeVisible({ timeout: 3_000 });
38+
await bulletOption.click();
39+
await page.keyboard.type("First item");
40+
await page.keyboard.press("Enter");
41+
await page.keyboard.type("Second item");
42+
43+
// Wait for auto-save to persist
44+
await page.waitForTimeout(1_500);
45+
46+
// Open the page menu — scoped to <main> to avoid sidebar "Page actions" buttons
47+
const main = page.locator("main");
48+
const menuTrigger = main.locator('button[aria-label="Page actions"]');
49+
await expect(menuTrigger).toBeVisible({ timeout: 5_000 });
50+
await menuTrigger.click();
51+
52+
const exportItem = page
53+
.getByRole("menuitem")
54+
.filter({ hasText: "Export as Markdown" });
55+
await expect(exportItem).toBeVisible({ timeout: 3_000 });
56+
57+
// Intercept the download
58+
const downloadPromise = page.waitForEvent("download");
59+
await exportItem.click();
60+
const download = await downloadPromise;
61+
62+
// Verify the downloaded file
63+
expect(download.suggestedFilename()).toMatch(/\.md$/);
64+
65+
const content = await (
66+
await download.createReadStream()
67+
)
68+
.toArray()
69+
.then((chunks) => Buffer.concat(chunks).toString("utf-8"));
70+
71+
expect(content).toContain("# Export Test Heading");
72+
expect(content).toContain("This is a test paragraph for export.");
73+
expect(content).toContain("First item");
74+
expect(content).toContain("Second item");
75+
});
76+
77+
test("user can import a markdown file via the page menu to create a new page", async ({
78+
authenticatedPage: page,
79+
}) => {
80+
await navigateToEditorPage(page);
81+
82+
const editor = page.locator('[contenteditable="true"]');
83+
await expect(editor).toBeVisible({ timeout: 10_000 });
84+
85+
// Capture the current URL to detect navigation after import
86+
const urlBefore = page.url();
87+
88+
// Prepare the markdown content to import
89+
const markdownContent = [
90+
"# Imported Heading",
91+
"",
92+
"A paragraph with **bold** and *italic* text.",
93+
"",
94+
"- List item alpha",
95+
"- List item beta",
96+
"",
97+
"```",
98+
"const x = 42;",
99+
"```",
100+
"",
101+
"[Example link](https://example.com)",
102+
].join("\n");
103+
104+
// Set the file on the hidden input scoped to <main> (sidebar also has page action menus)
105+
const main = page.locator("main");
106+
const fileInput = main.locator('input[type="file"][accept=".md,.markdown"]');
107+
108+
// Setting input files triggers the onChange handler directly
109+
await fileInput.setInputFiles({
110+
name: "imported-doc.md",
111+
mimeType: "text/markdown",
112+
buffer: Buffer.from(markdownContent, "utf-8"),
113+
});
114+
115+
// Wait for navigation to the newly created page
116+
await page.waitForURL((url) => url.href !== urlBefore, {
117+
timeout: 15_000,
118+
});
119+
120+
// The new page should have an editor with the imported content
121+
const newEditor = page.locator('[contenteditable="true"]');
122+
await expect(newEditor).toBeVisible({ timeout: 10_000 });
123+
124+
// Verify the imported content is rendered in the editor
125+
// Heading
126+
const heading = newEditor.locator("h1");
127+
await expect(heading).toBeVisible({ timeout: 5_000 });
128+
await expect(heading).toContainText("Imported Heading");
129+
130+
// Paragraph with bold/italic
131+
await expect(newEditor).toContainText("A paragraph with");
132+
await expect(newEditor).toContainText("bold");
133+
await expect(newEditor).toContainText("italic");
134+
135+
// List items
136+
await expect(newEditor).toContainText("List item alpha");
137+
await expect(newEditor).toContainText("List item beta");
138+
139+
// Code block
140+
await expect(newEditor).toContainText("const x = 42;");
141+
142+
// Link
143+
const link = newEditor.locator('a[href="https://example.com"]');
144+
await expect(link).toBeVisible({ timeout: 3_000 });
145+
await expect(link).toContainText("Example link");
146+
147+
// Verify the page title matches the imported filename (minus extension)
148+
const titleInput = page.locator('input[aria-label="Page title"]');
149+
await expect(titleInput).toHaveValue("imported-doc", { timeout: 5_000 });
150+
});
151+
});

0 commit comments

Comments
 (0)