Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>`).
- `@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
Expand Down
53 changes: 52 additions & 1 deletion e2e-tests/fixtures/helpers.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -397,3 +397,54 @@ export async function waitForInterlinearizerReady(timeoutMs = 90_000): Promise<v
timeoutMs,
);
}

/**
* Open the Interlinearizer WebView from the Scripture Editor's top (≡) menu. Prerequisite stage
* shared by all e2e tests that require the Interlinearizer to be open. Assumes no project has been
* loaded yet.
*
* Steps:
*
* 1. Click the "Scripture Editor" dock tab to bring it to the front.
* 2. Enter the Scripture Editor iframe and click the ≡ ("Project") menu button.
* 3. Click "Open Interlinearizer for this Project".
* 4. In the "Open Interlinearizer" project-picker dialog, click the named project.
* 5. Wait for the "Interlinearizer" dock tab and click it to focus it.
*
* @param page The Playwright `Page` for the Platform.Bible renderer window.
* @param projectName Name of the project to select in the project-picker (default: `"WEB"`).
* @returns Resolves when the Interlinearizer tab is focused and visible.
* @throws If any step does not complete within its timeout.
*/
export async function openInterlinearizerFromScriptureEditor(
page: Page,
projectName = 'WEB',
): Promise<void> {
// 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();
}
6 changes: 2 additions & 4 deletions e2e-tests/tests/smoke/extension-launch.spec.ts
Original file line number Diff line number Diff line change
@@ -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 }) => {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
expect(electronApp.windows().length).toBeGreaterThanOrEqual(1);
});
Expand All @@ -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();
});
});
34 changes: 34 additions & 0 deletions e2e-tests/tests/smoke/open-interlinearizer.spec.ts
Original file line number Diff line number Diff line change
@@ -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 }) => {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
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,
});
});
});
Loading