diff --git a/.gitignore b/.gitignore index 9a71498..816f1dc 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,8 @@ notes-app # SQLite db notes.db + +# Playwright +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/main.go b/main.go index d3cf23c..18ef2b5 100644 --- a/main.go +++ b/main.go @@ -86,6 +86,19 @@ func main() { // API routes http.HandleFunc("/api/notes", handleNotes) http.HandleFunc("/api/notes/", handleNote) + http.HandleFunc("/api/test/reset", func(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + _, err := db.Exec("DELETE FROM notes") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusOK) + }) // Serve frontend http.HandleFunc("/", handleFrontend) diff --git a/package.json b/package.json index 96c8496..7d2dbba 100644 --- a/package.json +++ b/package.json @@ -13,9 +13,13 @@ "format": "prettier --write .", "lint": "prettier --check . && eslint .", "test:unit": "vitest", - "test": "npm run test:unit -- --run" + "test": "npm run test:unit -- --run", + "test:e2e": "playwright test", + "test:e2e:ui": "playwright test --ui", + "test:e2e:headed": "playwright test --headed" }, "devDependencies": { + "@playwright/test": "^1.42.1", "@eslint/compat": "^1.2.5", "@eslint/js": "^9.18.0", "@sveltejs/adapter-static": "^3.0.8", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..930c95b --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,20 @@ +import { defineConfig, devices } from '@playwright/test'; + +export default defineConfig({ + testDir: './tests', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: 0, + workers: 1, // Recommended for test isolation with database + reporter: 'html', + use: { + baseURL: 'http://localhost:3000', + trace: 'on', + }, + webServer: { + command: 'go run main.go', + url: 'http://localhost:3000', + reuseExistingServer: !process.env.CI, + timeout: 120 * 1000, + }, +}); diff --git a/tests/note-linking.spec.ts b/tests/note-linking.spec.ts new file mode 100644 index 0000000..339ebd7 --- /dev/null +++ b/tests/note-linking.spec.ts @@ -0,0 +1,53 @@ +import { test, expect } from '@playwright/test'; + +test.beforeEach(async ({ page }) => { + // Reset database before each test + await page.request.post('/api/test/reset'); +}); + +test.describe('Note Linking', () => { + test('should create new note from broken link', async ({ page }) => { + // Create initial note + await page.goto('/notes/new'); + await page.fill('input[name="title"]', 'Parent Note'); + await page.fill('textarea[name="content"]', 'Content with [[Unlinked Child Note]]'); + await page.click('button[type="submit"]'); + + // Click the unlinked note + await page.click('a.note-link'); + + // Verify new note form with title pre-filled + await expect(page).toHaveURL('/notes/new'); + await expect(page.locator('input[name="title"]')).toHaveValue('Unlinked Child Note'); + + // Create child note + await page.fill('textarea[name="content"]', 'Child content'); + await page.click('button[type="submit"]'); + + // Verify child note creation + await expect(page.locator('h1')).toHaveText('Unlinked Child Note'); + await expect(page.locator('.content')).toContainText('Child content'); + }); + + test('should navigate between linked notes', async ({ page }) => { + // Create two linked notes + await page.goto('/notes/new'); + await page.fill('input[name="title"]', 'First Note'); + await page.fill('textarea[name="content"]', 'Links to [[Second Note]]'); + await page.click('button[type="submit"]'); + const firstNoteUrl = page.url(); + + await page.click('a.note-link'); + await page.fill('textarea[name="content"]', 'Links back to [[First Note]]'); + await page.click('button[type="submit"]'); + const secondNoteUrl = page.url(); + + // Verify bidirectional linking + await page.goto(firstNoteUrl); + await page.click('a.note-link:has-text("Second Note")'); + await expect(page).toHaveURL(secondNoteUrl); + + await page.click('a.note-link:has-text("First Note")'); + await expect(page).toHaveURL(firstNoteUrl); + }); +});