diff --git a/AGENTS.md b/AGENTS.md index 1496a079..084b9f46 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -149,13 +149,15 @@ The ESLint rule `no-type-assertion/no-type-assertion` is enforced. **Never use ` ## Documentation -Every function and method — exported or internal — must have a JSDoc block with: +Every named function and method — exported or internal — must have a JSDoc block with: - A summary sentence describing what the function does and why it exists (non-obvious behavior only; don't restate the name). - `@param` for every parameter. - `@returns` describing the return value (omit only for `void`/`Promise`). - `@throws` for every error condition the caller must handle; omit if the function never throws. +This function rule does not apply to unnamed inline callbacks passed directly to framework APIs (e.g., `describe()`, `test()`) — in those cases the test-title string serves as the documentation. + Type declarations (interfaces, type aliases, enums) must have a JSDoc summary on the type itself and on each field or member whose purpose is not self-evident from its name and type. We document each field individually rather than describing the fields in the type-level summary. ## Spelling diff --git a/e2e-tests/fixtures/helpers.ts b/e2e-tests/fixtures/helpers.ts index 40598ff0..a8c604a0 100644 --- a/e2e-tests/fixtures/helpers.ts +++ b/e2e-tests/fixtures/helpers.ts @@ -1,5 +1,5 @@ // Adapted from paranext-core/e2e-tests/fixtures/helpers.ts -import { _electron as electron, ElectronApplication, Page } from '@playwright/test'; +import { _electron as electron, ElectronApplication, expect, Page } from '@playwright/test'; import fs from 'fs'; import { createRequire } from 'module'; import os from 'os'; @@ -397,3 +397,54 @@ export async function waitForInterlinearizerReady(timeoutMs = 90_000): Promise { + // Focus the Scripture Editor tab (opens without a project at fresh start). + const editorTab = page.locator('.dock-tab', { hasText: 'Scripture Editor' }).first(); + await expect(editorTab).toBeVisible({ timeout: 15_000 }); + await editorTab.click(); + + // The Scripture Editor renders its own toolbar inside its iframe. Click the ≡ ("Project") button. + const editorFrame = page.frameLocator('iframe[title*="Scripture Editor" i]'); + await editorFrame.locator("button[aria-label='Project']").first().click(); + + // Click the "Open Interlinearizer for this Project" item contributed by this extension. + await editorFrame + .getByRole('menuitem', { name: /Open Interlinearizer for this Project/i }) + .first() + .click(); + + // The command calls papi.dialogs.selectProject (because no project is selected in a fresh start), + // which opens a floating "Open Interlinearizer" dock tab with the project list. + const selectProjectDialog = page.locator('.select-project-dialog'); + await expect(selectProjectDialog).toBeVisible({ timeout: 15_000 }); + const escapedProjectName = projectName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const projectNameRegex = new RegExp(`^${escapedProjectName}$`, 'i'); + await selectProjectDialog.getByRole('button', { name: projectNameRegex }).click(); + + // Wait for the Interlinearizer tab to appear and focus it. + const interlinearizerTab = page.locator('.dock-tab', { hasText: 'Interlinearizer' }).first(); + await expect(interlinearizerTab).toBeVisible({ timeout: 15_000 }); + await interlinearizerTab.click(); +} diff --git a/e2e-tests/tests/smoke/extension-launch.spec.ts b/e2e-tests/tests/smoke/extension-launch.spec.ts index a7ad9b5c..077bce75 100644 --- a/e2e-tests/tests/smoke/extension-launch.spec.ts +++ b/e2e-tests/tests/smoke/extension-launch.spec.ts @@ -1,7 +1,7 @@ import { test, expect } from '../../fixtures/app.fixture'; import { waitForAppReady, waitForInterlinearizerReady } from '../../fixtures/helpers'; -test.describe('Interlinearizer Extension Smoke Tests', () => { +test.describe('Launch app and register Interlinearizer', () => { test('should launch Platform.Bible and create at least one window', async ({ electronApp }) => { expect(electronApp.windows().length).toBeGreaterThanOrEqual(1); }); @@ -18,10 +18,8 @@ test.describe('Interlinearizer Extension Smoke Tests', () => { await expect(dock).toBeAttached({ timeout: 60_000 }); }); - test('should register interlinearizer PAPI commands', async ({ mainPage }) => { + test('should register Interlinearizer PAPI commands', async ({ mainPage }) => { await waitForAppReady(mainPage); - // Waits for interlinearizer.openForWebView to appear in rpc.discover, confirming the - // extension activated successfully. await waitForInterlinearizerReady(); }); }); diff --git a/e2e-tests/tests/smoke/open-interlinearizer.spec.ts b/e2e-tests/tests/smoke/open-interlinearizer.spec.ts new file mode 100644 index 00000000..af440cc8 --- /dev/null +++ b/e2e-tests/tests/smoke/open-interlinearizer.spec.ts @@ -0,0 +1,34 @@ +import { expect, test } from '../../fixtures/app.fixture'; +import { + openInterlinearizerFromScriptureEditor, + waitForAppReady, + waitForInterlinearizerReady, +} from '../../fixtures/helpers'; + +test.describe('Open Interlinearizer', () => { + test('should open the interlinearizer and see its menus', async ({ mainPage }) => { + await waitForAppReady(mainPage); + await waitForInterlinearizerReady(); + + await openInterlinearizerFromScriptureEditor(mainPage); + + // The Interlinearizer WebView renders its toolbar (with menu buttons) inside its iframe. + const interlinearizerFrame = mainPage.frameLocator('iframe[title*="Interlinearizer" i]'); + + // Verify the ≡ (Project) menu button is visible and opens a menu. + const projectMenuButton = interlinearizerFrame.locator("button[aria-label='Project']").first(); + await expect(projectMenuButton).toBeVisible({ timeout: 15_000 }); + await projectMenuButton.click(); + await expect(interlinearizerFrame.locator('[role="menu"]')).toBeVisible({ timeout: 5_000 }); + await mainPage.keyboard.press('Escape'); + await expect(interlinearizerFrame.locator('[role="menu"]')).not.toBeVisible({ timeout: 3_000 }); + + // The ⚙ (View options) button only appears once the book data has loaded. + const viewOptionsButton = interlinearizerFrame.getByTestId('view-options-button'); + await expect(viewOptionsButton).toBeVisible({ timeout: 30_000 }); + await viewOptionsButton.click(); + await expect(interlinearizerFrame.getByTestId('view-options-panel')).toBeVisible({ + timeout: 5_000, + }); + }); +});