diff --git a/e2e/appFixture.ts b/e2e/appFixture.ts index 5a84c15..1448fc0 100644 --- a/e2e/appFixture.ts +++ b/e2e/appFixture.ts @@ -9,19 +9,13 @@ import { const repoRoot = join(__dirname, '..'); -/** Vault id used for the throwaway vault; the provider id becomes `vault-e2e`. */ const VAULT_ID = 'e2e'; -/** Enough body text that the document is comfortably taller than the viewport. */ const filler = (label: string, lines = 40) => Array.from({ length: lines }, (_, i) => `${label} body line ${i + 1}.`).join( '\n', ); -/** - * Notes every test shares. `Target.md` is deliberately long so that a heading - * partway down cannot be at the top of the viewport by accident. - */ export const NOTES: Record = { 'Target.md': [ '# Target', @@ -45,15 +39,7 @@ export const NOTES: Record = { 'Other.md': ['# Other', '', filler('other')].join('\n'), }; -/** - * The application window — explicitly not `app.firstWindow()`. - * - * `src/main.ts` opens DevTools unconditionally, and Playwright counts the - * DevTools page as a window. It frequently arrives first, and it is a plausible - * enough `Page` that everything appears to work while actually driving DevTools: - * `localStorage` writes land on the wrong origin and screenshots fail with - * "0 width" rather than anything that names the real problem. - */ +/** Not `firstWindow()`: DevTools is a window too, and driving it fails silently. */ async function appWindow(app: ElectronApplication): Promise { const deadline = Date.now() + 30_000; while (Date.now() < deadline) { @@ -74,19 +60,8 @@ export type LaunchedApp = { }; /** - * Launch the packaged app against a throwaway vault. - * - * The vault is seeded straight into `localStorage` and the window reloaded, - * rather than driven through the vault-manager UI: the registry reads - * `textile.openVaults` once at module load, so seeding then reloading is both - * simpler and less brittle than clicking through a dialog whose markup is not - * what these tests are about. - * - * `TEXTILE_USER_DATA` (a hook `src/main.ts` already provides for exactly this) - * points the app at a temp userData directory, so a run never inherits or - * clobbers the developer's real window state, vault list or tab layout — and it - * bypasses the single-instance lock that would otherwise make the app quit - * immediately when one is already running. + * Launch against a throwaway vault. The registry reads `localStorage` once at + * load, so seed-then-reload beats driving the vault dialog. */ export async function launchApp(): Promise { const vaultDir = await mkdtemp(join(tmpdir(), 'textile-e2e-vault-')); @@ -96,10 +71,7 @@ export async function launchApp(): Promise { await writeFile(join(vaultDir, name), body, 'utf-8'); } - // `ELECTRON_RUN_AS_NODE` must not survive into the child: it makes the binary - // run as plain Node, so `require('electron').app` is undefined and Chromium - // switches (including the debugging port Playwright needs) are rejected. Some - // tool environments export it, and inheriting it fails in a very confusing way. + // Inherited, this runs the binary as plain Node: no `electron.app`. const { ELECTRON_RUN_AS_NODE: _ignored, ...parentEnv } = process.env; const app = await electron.launch({ @@ -111,12 +83,7 @@ export async function launchApp(): Promise { const window = await appWindow(app); await window.waitForLoadState('domcontentloaded'); - // Close the DevTools `src/main.ts` always opens, and give the window a real - // size. Both matter: docked DevTools leaves the editor a sliver, and - // CodeMirror only renders its visible viewport — so in a small window most of - // a document is genuinely absent from the DOM and assertions about content - // further down fail for reasons that have nothing to do with the behaviour - // under test. + // CM renders only its viewport, so a sliver of a window hides the document. await app.evaluate(({ BrowserWindow }) => { const win = BrowserWindow.getAllWindows()[0]; if (!win) return; @@ -137,8 +104,6 @@ export async function launchApp(): Promise { await window.reload(); await window.waitForLoadState('domcontentloaded'); - // The sidebar polls the tree, so the first file appearing is the signal that - // the vault mounted and the app is ready to drive. await window.getByText('Target.md', { exact: true }).first().waitFor(); return { @@ -146,12 +111,7 @@ export async function launchApp(): Promise { window, vaultDir, close: async () => { - // Teardown has to be unconditional, not cooperative. On macOS the app - // deliberately survives its last window closing (`src/main.ts`), and - // asking it to quit is not reliable either — a stuck instance makes - // Playwright's worker teardown hang for 60s and report the timeout - // against whichever test happened to run first, which reads as random - // flakiness somewhere else entirely. + // The app outlives its last window on macOS, so `close()` alone can hang. const proc = app.process(); await Promise.race([ app.close().catch(() => {}), @@ -170,13 +130,30 @@ export async function launchApp(): Promise { }; } -/** Open a note by clicking it in the sidebar tree, and wait for its editor. */ +/** + * Reset between tests. Specs share one app because quitting Electron often + * hangs mid-exit on macOS. Restoring the notes matters: the editor persists + * edits, so a test that types corrupts the vault for later ones. + */ +export async function resetWorkspace(app: LaunchedApp): Promise { + for (const [name, body] of Object.entries(NOTES)) { + await writeFile(join(app.vaultDir, name), body, 'utf-8'); + } + await app.window.evaluate(() => { + for (const key of Object.keys(localStorage)) { + if (key.startsWith('textile.tiling.')) localStorage.removeItem(key); + } + }); + await app.window.reload(); + await app.window.waitForLoadState('domcontentloaded'); + await app.window.getByText('Target.md', { exact: true }).first().waitFor(); +} + export async function openNote(window: Page, fileName: string): Promise { await window.getByText(fileName, { exact: true }).first().click(); await window.locator('.cm-content').first().waitFor(); } -/** The editor's scrollable element — CodeMirror's own, not an outer wrapper. */ export function scroller(window: Page) { return window.locator('.cm-scroller').first(); } diff --git a/e2e/completion.spec.ts b/e2e/completion.spec.ts index 0ee68f1..5d6faf0 100644 --- a/e2e/completion.spec.ts +++ b/e2e/completion.spec.ts @@ -1,9 +1,13 @@ import { test, expect } from '@playwright/test'; -import { launchApp, openNote } from './appFixture'; +import { + launchApp, + resetWorkspace, + type LaunchedApp, + openNote, +} from './appFixture'; const popup = '.cm-tooltip-autocomplete'; -/** Put the caret at the very end of the open document. */ async function focusEndOfDoc( window: Awaited>['window'], ) { @@ -15,104 +19,133 @@ async function focusEndOfDoc( } test.describe('[[ completion popup', () => { + let app: LaunchedApp; + let window: LaunchedApp['window']; + + test.beforeAll(async () => { + app = await launchApp(); + window = app.window; + }); + test.afterAll(async () => { + await app.close(); + }); + test.beforeEach(async () => { + await resetWorkspace(app); + }); + test('offers note names, in the app font rather than monospace', async () => { - const { window, close } = await launchApp(); - try { - await openNote(window, 'Other.md'); - await focusEndOfDoc(window); - await window.keyboard.type('[['); - - await expect(window.locator(popup)).toBeVisible(); - await expect(window.locator(`${popup} li`).first()).toBeVisible(); - await expect(window.locator(popup)).toContainText('Target'); - - // CodeMirror's base theme hardcodes `font-family: monospace` on the list, - // and our override lost to it on specificity for two rounds. These are - // note names, not code. - const fontFamily = await window - .locator(`${popup} > ul`) - .evaluate((el) => getComputedStyle(el).fontFamily); - expect(fontFamily.toLowerCase()).not.toContain('monospace'); - } finally { - await close(); - } + await openNote(window, 'Other.md'); + await focusEndOfDoc(window); + await window.keyboard.type('[['); + + await expect(window.locator(popup)).toBeVisible(); + await expect(window.locator(`${popup} li`).first()).toBeVisible(); + await expect(window.locator(popup)).toContainText('Target'); + + // CM's base theme hardcodes monospace here; our override has to out-specify it. + const fontFamily = await window + .locator(`${popup} > ul`) + .evaluate((el) => getComputedStyle(el).fontFamily); + expect(fontFamily.toLowerCase()).not.toContain('monospace'); }); test('shows the modifier hints pinned below the option list', async () => { - const { window, close } = await launchApp(); - try { - await openNote(window, 'Other.md'); - await focusEndOfDoc(window); - await window.keyboard.type('[['); - - const footer = window.locator('.cm-wikilink-completion-footer'); - await expect(footer).toBeVisible(); - await expect(footer).toContainText('heading'); - await expect(footer).toContainText('display text'); - - // CodeMirror re-appends the
    on every update, which lands it AFTER - // the footer in DOM order; flex `order` is what keeps the footer visually - // last. Assert the rendered geometry, not the DOM order. - await window.keyboard.type('T'); - const listBox = await window.locator(`${popup} > ul`).boundingBox(); - const footerBox = await footer.boundingBox(); - expect(listBox).not.toBeNull(); - expect(footerBox).not.toBeNull(); - expect(footerBox!.y).toBeGreaterThanOrEqual( - listBox!.y + listBox!.height - 1, - ); - } finally { - await close(); - } + await openNote(window, 'Other.md'); + await focusEndOfDoc(window); + await window.keyboard.type('[['); + + const footer = window.locator('.cm-wikilink-completion-footer'); + await expect(footer).toBeVisible(); + await expect(footer).toContainText('heading'); + await expect(footer).toContainText('display text'); + + // CM re-appends the
      after the footer in DOM order, so assert geometry. + await window.keyboard.type('T'); + const listBox = await window.locator(`${popup} > ul`).boundingBox(); + const footerBox = await footer.boundingBox(); + expect(listBox).not.toBeNull(); + expect(footerBox).not.toBeNull(); + expect(footerBox!.y).toBeGreaterThanOrEqual( + listBox!.y + listBox!.height - 1, + ); }); test('completes headings after `#`, labelled with their level', async () => { - const { window, close } = await launchApp(); - try { - await openNote(window, 'Other.md'); - await focusEndOfDoc(window); - await window.keyboard.type('[[Target#'); - - await expect(window.locator(popup)).toBeVisible(); - await expect(window.locator(popup)).toContainText('Middle Section'); - await expect(window.locator(popup)).toContainText('Later Section'); - - const badges = window.locator('.cm-wikilink-level'); - await expect(badges.first()).toBeVisible(); - const levels = await badges.allTextContents(); - // Target.md is `# Target` then two `##` sections. - expect(levels).toContain('H1'); - expect(levels).toContain('H2'); - } finally { - await close(); - } + await openNote(window, 'Other.md'); + await focusEndOfDoc(window); + await window.keyboard.type('[[Target#'); + + await expect(window.locator(popup)).toBeVisible(); + await expect(window.locator(popup)).toContainText('Middle Section'); + await expect(window.locator(popup)).toContainText('Later Section'); + + const badges = window.locator('.cm-wikilink-level'); + await expect(badges.first()).toBeVisible(); + const levels = await badges.allTextContents(); + expect(levels).toContain('H1'); + expect(levels).toContain('H2'); }); test('completes the current document’s headings after `[[#`', async () => { - const { window, close } = await launchApp(); - try { - await openNote(window, 'Target.md'); - await focusEndOfDoc(window); - await window.keyboard.type('[[#'); - - await expect(window.locator(popup)).toBeVisible(); - await expect(window.locator(popup)).toContainText('Middle Section'); - } finally { - await close(); - } + await openNote(window, 'Target.md'); + await focusEndOfDoc(window); + await window.keyboard.type('[[#'); + + await expect(window.locator(popup)).toBeVisible(); + await expect(window.locator(popup)).toContainText('Middle Section'); }); test('closes once a `|` alias is started', async () => { - const { window, close } = await launchApp(); - try { - await openNote(window, 'Other.md'); - await focusEndOfDoc(window); - await window.keyboard.type('[[Target'); - await expect(window.locator(popup)).toBeVisible(); - await window.keyboard.type('|'); - await expect(window.locator(popup)).toBeHidden(); - } finally { - await close(); - } + await openNote(window, 'Other.md'); + await focusEndOfDoc(window); + await window.keyboard.type('[[Target'); + await expect(window.locator(popup)).toBeVisible(); + await window.keyboard.type('|'); + await expect(window.locator(popup)).toBeHidden(); + }); + + test('`#` accepts the highlighted note and goes straight to headings', async () => { + await openNote(window, 'Other.md'); + await focusEndOfDoc(window); + await window.keyboard.type('[[Targ'); + await expect(window.locator(popup)).toBeVisible(); + + await window.keyboard.type('#'); + + await expect(window.locator('.cm-content').first()).toContainText( + '[[Target#', + ); + await expect(window.locator(popup)).toContainText('Middle Section'); + }); + + test('Enter after choosing a heading writes the whole link', async () => { + await openNote(window, 'Other.md'); + await focusEndOfDoc(window); + await window.keyboard.type('[[Targ'); + await expect(window.locator(popup)).toBeVisible(); + await window.keyboard.type('#Middle'); + // Highlighted, not merely present: the heading list loads asynchronously. + await expect(window.locator(`${popup} li[aria-selected]`)).toContainText( + 'Middle Section', + ); + // Past CM's 75ms `interactionDelay`, which refuses a just-opened popup. + // Synthetic input lands inside that window; a human does not. + await window.waitForTimeout(150); + + await window.keyboard.press('Enter'); + + await expect(window.locator('.cm-content').first()).toContainText( + '[[Target#Middle Section]]', + ); + await expect(window.locator(popup)).toBeHidden(); + }); + + test('`#` is ordinary text when no option is highlighted', async () => { + await openNote(window, 'Other.md'); + await focusEndOfDoc(window); + await window.keyboard.type('plain # text'); + await expect(window.locator('.cm-content').first()).toContainText( + 'plain # text', + ); }); }); diff --git a/e2e/globalSetup.ts b/e2e/globalSetup.ts index b1e14a2..fa6ed2c 100644 --- a/e2e/globalSetup.ts +++ b/e2e/globalSetup.ts @@ -5,16 +5,8 @@ import { join } from 'node:path'; export const repoRoot = join(__dirname, '..'); /** - * Build the main, preload and renderer bundles the tests launch. - * - * `electron-forge package` rather than a bare `vite build`: Forge's Vite plugin - * supplies the entry points and the `MAIN_WINDOW_VITE_*` defines that - * `src/main.ts` compiles against, so a standalone Vite build produces a main - * bundle with undefined globals. - * - * Set `E2E_SKIP_BUILD=1` to reuse the existing bundles while iterating on the - * tests themselves. It is off by default because a stale bundle silently tests - * code you are not looking at. + * `electron-forge package`, not a bare `vite build`, which lacks Forge's entry + * points and defines. `E2E_SKIP_BUILD=1` reuses the last build. */ export default function globalSetup(): void { if ( diff --git a/e2e/headingNavigation.spec.ts b/e2e/headingNavigation.spec.ts index 232d023..9b7a024 100644 --- a/e2e/headingNavigation.spec.ts +++ b/e2e/headingNavigation.spec.ts @@ -1,61 +1,76 @@ import { test, expect } from '@playwright/test'; -import { launchApp, openNote, scroller } from './appFixture'; +import { + launchApp, + resetWorkspace, + type LaunchedApp, + openNote, + scroller, +} from './appFixture'; test.describe('heading navigation', () => { + let app: LaunchedApp; + let window: LaunchedApp['window']; + + test.beforeAll(async () => { + app = await launchApp(); + window = app.window; + }); + test.afterAll(async () => { + await app.close(); + }); + test.beforeEach(async () => { + await resetWorkspace(app); + }); + test('following [[Note#Heading]] scrolls that heading to the top', async () => { - const { window, close } = await launchApp(); - try { - await openNote(window, 'Source.md'); + await openNote(window, 'Source.md'); - // Rendered display text for `[[Target#Middle Section]]` — the brackets - // are hidden while the cursor is elsewhere. - const link = window - .locator('.cm-wikilink', { hasText: 'Target#Middle Section' }) - .first(); - await expect(link).toBeVisible(); - // Edit mode opens a link on cmd/ctrl-click; a plain click just moves the - // caret and reveals the raw markdown. - await link.click({ - modifiers: [process.platform === 'darwin' ? 'Meta' : 'Control'], - }); - - // The bug this guards: the scroll was dispatched immediately after - // `new EditorView(...)`, which CodeMirror drops while `editorHeight` is - // still zero — so the document opened at the very top. - await expect - .poll(() => scroller(window).evaluate((el) => el.scrollTop)) - .toBeGreaterThan(0); - - const heading = window - .locator('.cm-line', { hasText: 'Middle Section' }) - .first(); - const headingBox = await heading.boundingBox(); - const scrollerBox = await scroller(window).boundingBox(); - expect(headingBox).not.toBeNull(); - expect(scrollerBox).not.toBeNull(); - // "At the top" rather than merely on screen. - expect(headingBox!.y - scrollerBox!.y).toBeLessThan(80); - } finally { - await close(); - } + // Display text: the brackets are hidden while the cursor is elsewhere. + const link = window + .locator('.cm-wikilink', { hasText: 'Target#Middle Section' }) + .first(); + await expect(link).toBeVisible(); + // Edit mode opens on cmd/ctrl-click; a plain click just moves the caret. + await link.click({ + modifiers: [process.platform === 'darwin' ? 'Meta' : 'Control'], + }); + + // Guards: a scroll dispatched before the view has height is dropped. + await expect + .poll(() => scroller(window).evaluate((el) => el.scrollTop)) + .toBeGreaterThan(0); + + const heading = window + .locator('.cm-line', { hasText: 'Middle Section' }) + .first(); + const headingBox = await heading.boundingBox(); + const scrollerBox = await scroller(window).boundingBox(); + expect(headingBox).not.toBeNull(); + expect(scrollerBox).not.toBeNull(); + expect(headingBox!.y - scrollerBox!.y).toBeLessThan(80); }); test('a plain [[Note]] link opens at the top', async () => { - const { window, close } = await launchApp(); - try { - await openNote(window, 'Source.md'); - const link = window - .locator('.cm-wikilink', { hasText: /^Target$/ }) - .first(); - await link.click({ - modifiers: [process.platform === 'darwin' ? 'Meta' : 'Control'], - }); - await expect(window.locator('.cm-content').first()).toContainText( - 'intro body line 1', - ); - expect(await scroller(window).evaluate((el) => el.scrollTop)).toBe(0); - } finally { - await close(); - } + await openNote(window, 'Source.md'); + const link = window + .locator('.cm-wikilink', { hasText: /^Target$/ }) + .first(); + await link.click({ + modifiers: [process.platform === 'darwin' ? 'Meta' : 'Control'], + }); + await expect(window.locator('.cm-content').first()).toContainText( + 'intro body line 1', + ); + expect(await scroller(window).evaluate((el) => el.scrollTop)).toBe(0); + }); + + test('renders a heading link with the `#` separator intact', async () => { + await openNote(window, 'Source.md'); + // Without the separator this collapsed to `TargetMiddle Section`. + await expect( + window + .locator('.cm-wikilink', { hasText: 'Target#Middle Section' }) + .first(), + ).toBeVisible(); }); }); diff --git a/e2e/smoke.spec.ts b/e2e/smoke.spec.ts index 3c7207f..d734a10 100644 --- a/e2e/smoke.spec.ts +++ b/e2e/smoke.spec.ts @@ -1,19 +1,34 @@ import { test, expect } from '@playwright/test'; -import { launchApp, openNote } from './appFixture'; +import { + launchApp, + openNote, + resetWorkspace, + type LaunchedApp, +} from './appFixture'; -test('launches, mounts the seeded vault, and opens a note', async () => { - const { window, close } = await launchApp(); - try { +test.describe('app harness', () => { + let app: LaunchedApp; + let window: LaunchedApp['window']; + + test.beforeAll(async () => { + app = await launchApp(); + window = app.window; + }); + test.afterAll(async () => { + await app.close(); + }); + test.beforeEach(async () => { + await resetWorkspace(app); + }); + + test('launches, mounts the seeded vault, and opens a note', async () => { await expect( window.getByText('Source.md', { exact: true }).first(), ).toBeVisible(); await openNote(window, 'Target.md'); - // Asserted near the top of the document: CodeMirror only renders its - // visible viewport, so content further down is legitimately not in the DOM. + // Near the top: CM only renders its visible viewport. await expect(window.locator('.cm-content').first()).toContainText( 'intro body line 1', ); - } finally { - await close(); - } + }); }); diff --git a/e2e/tabViewState.spec.ts b/e2e/tabViewState.spec.ts index 3fbffad..cd0e1c6 100644 --- a/e2e/tabViewState.spec.ts +++ b/e2e/tabViewState.spec.ts @@ -1,63 +1,69 @@ import { test, expect } from '@playwright/test'; -import { launchApp, openNote, scroller } from './appFixture'; +import { + launchApp, + resetWorkspace, + type LaunchedApp, + openNote, + scroller, +} from './appFixture'; test.describe('per-tab view state', () => { + let app: LaunchedApp; + let window: LaunchedApp['window']; + + test.beforeAll(async () => { + app = await launchApp(); + window = app.window; + }); + test.afterAll(async () => { + await app.close(); + }); + test.beforeEach(async () => { + await resetWorkspace(app); + }); + test('restores scroll position when returning to a tab', async () => { - const { window, close } = await launchApp(); - try { - await openNote(window, 'Target.md'); - await scroller(window).evaluate((el) => { - el.scrollTop = 600; - }); - // CodeMirror reacts to the scroll event on its own scroller; give it a - // frame to record the position before we navigate away. - await window.waitForTimeout(200); - const before = await scroller(window).evaluate((el) => el.scrollTop); - expect(before).toBeGreaterThan(0); - - await openNote(window, 'Other.md'); - await openNote(window, 'Target.md'); - - // The regression this guards: the snapshot used to be captured in a - // passive effect cleanup, after React had detached the node, so - // `scrollTop` always read 0 and every tab came back at the top. - await expect - .poll(() => scroller(window).evaluate((el) => el.scrollTop)) - .toBeGreaterThan(before / 2); - } finally { - await close(); - } + await openNote(window, 'Target.md'); + await scroller(window).evaluate((el) => { + el.scrollTop = 600; + }); + // Let CM record the position before navigating away. + await window.waitForTimeout(200); + const before = await scroller(window).evaluate((el) => el.scrollTop); + expect(before).toBeGreaterThan(0); + + await openNote(window, 'Other.md'); + await openNote(window, 'Target.md'); + + // Guards: capturing after React detaches the node reads `scrollTop` 0. + await expect + .poll(() => scroller(window).evaluate((el) => el.scrollTop)) + .toBeGreaterThan(before / 2); }); test('restores the cursor position, not just the scroll', async () => { - const { window, close } = await launchApp(); - try { - await openNote(window, 'Target.md'); - await window.locator('.cm-content').first().click(); - // Move somewhere unambiguous and away from the document start. - for (let i = 0; i < 12; i++) await window.keyboard.press('ArrowDown'); - const before = await window.evaluate( - () => document.querySelectorAll('.cm-line').length, - ); - expect(before).toBeGreaterThan(0); - const cursorLineBefore = await window.evaluate(() => { - const active = document.querySelector('.cm-activeLine'); - return active ? active.textContent : null; - }); - - await openNote(window, 'Other.md'); - await openNote(window, 'Target.md'); - - await expect - .poll(() => - window.evaluate(() => { - const active = document.querySelector('.cm-activeLine'); - return active ? active.textContent : null; - }), - ) - .toBe(cursorLineBefore); - } finally { - await close(); - } + await openNote(window, 'Target.md'); + await window.locator('.cm-content').first().click(); + for (let i = 0; i < 12; i++) await window.keyboard.press('ArrowDown'); + const before = await window.evaluate( + () => document.querySelectorAll('.cm-line').length, + ); + expect(before).toBeGreaterThan(0); + const cursorLineBefore = await window.evaluate(() => { + const active = document.querySelector('.cm-activeLine'); + return active ? active.textContent : null; + }); + + await openNote(window, 'Other.md'); + await openNote(window, 'Target.md'); + + await expect + .poll(() => + window.evaluate(() => { + const active = document.querySelector('.cm-activeLine'); + return active ? active.textContent : null; + }), + ) + .toBe(cursorLineBefore); }); }); diff --git a/playwright.config.ts b/playwright.config.ts index c916015..338f6af 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,20 +1,11 @@ import { defineConfig } from '@playwright/test'; -/** - * End-to-end tests that drive the real Electron app. - * - * These exist because the vitest suite runs in `environment: 'node'` and - * collects `*.test.ts` only, so nothing that depends on layout, geometry or - * CodeMirror's DOM can be covered there — and that is exactly where this - * editor's bugs have been landing. - */ +/** Drives the real Electron app; the vitest suite runs in node and cannot. */ export default defineConfig({ testDir: './e2e', - // One Electron instance at a time: each test launches a real app window, and - // parallel windows fight over focus and make scroll assertions flaky. + // Parallel app windows fight over focus and make scroll assertions flaky. workers: 1, fullyParallel: false, - // Launching Electron and loading a vault is slow relative to a unit test. timeout: 60_000, expect: { timeout: 10_000 }, reporter: process.env.CI ? 'list' : [['list']], diff --git a/vite.renderer.config.ts b/vite.renderer.config.ts index f239536..8b4fccb 100644 --- a/vite.renderer.config.ts +++ b/vite.renderer.config.ts @@ -8,12 +8,9 @@ import { defineConfig } from 'vite'; export default defineConfig({ plugins: [tailwindcss(), react(), wasm(), topLevelAwait()], build: { - // The renderer only ever runs in the Electron-bundled Chromium, so Vite's - // default browser target is both wrong and harmful here: esbuild cannot - // downlevel some of what `vite-plugin-top-level-await` emits to es2020, - // which failed every production build ("Transforming destructuring to the - // configured target environment is not supported yet"). Dev was unaffected, - // so this only ever broke `package`/`make`. + // The renderer only runs in Electron's Chromium. Vite's default browser + // target broke every production build, since esbuild cannot downlevel what + // `vite-plugin-top-level-await` emits to es2020. Dev was unaffected. target: 'esnext', }, });