Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f65496c
Allow user to define their own segment boundaries
alex-rawlings-yyc Jun 22, 2026
bade6e9
"Prevent" verse zero from being included (currently broken, pushing s…
alex-rawlings-yyc Jun 24, 2026
ba196ee
Draw hard boundary at verse-0 segments
alex-rawlings-yyc Jun 24, 2026
a4f41f8
Update user questions document
alex-rawlings-yyc Jun 24, 2026
439807f
Fix formatting issue
alex-rawlings-yyc Jul 2, 2026
009da57
Remove the verse-0 hard-wall guards from the segmentation model
alex-rawlings-yyc Jul 2, 2026
f2f2bb3
Fix boundary edits not re-rendering the view
alex-rawlings-yyc Jul 2, 2026
b3355d8
Label segments with a per-chapter number and contained verse range
alex-rawlings-yyc Jul 2, 2026
964564b
Add straddled-phrase detection and an event-time phrase-link getter
alex-rawlings-yyc Jul 2, 2026
adf8888
Replace the boundary-edit mode with always-available controls
alex-rawlings-yyc Jul 2, 2026
8d79504
Keep focus and scroll stable across boundary edits
alex-rawlings-yyc Jul 2, 2026
d60ed58
Update user questions for the reworked boundary editing
alex-rawlings-yyc Jul 2, 2026
a05ca88
Fix code-review findings across segmentation persistence and boundary
alex-rawlings-yyc Jul 6, 2026
7b4be0d
Letter split-verse labels and resolve navigation by verse containment
alex-rawlings-yyc Jul 6, 2026
2fd26d4
Use SegmentLabel type in Interlinearizer test
alex-rawlings-yyc Jul 7, 2026
c90b481
Renamed adjacentSegment to boundarySegment for clarity
alex-rawlings-yyc Jul 7, 2026
3505710
Fix lint error
alex-rawlings-yyc Jul 7, 2026
520f565
Normalize empty segmentation delta to default in autosave
alex-rawlings-yyc Jul 7, 2026
6fff177
Expand E2E testing
alex-rawlings-yyc Jul 6, 2026
a822527
Fix test failures, lint errors
alex-rawlings-yyc Jul 7, 2026
66fc3fc
Improve e2e helpers
alex-rawlings-yyc Jul 7, 2026
056028f
Anchor Interlinearizer dock-tab locators in e2e helpers
alex-rawlings-yyc Jul 7, 2026
ca70ddd
Fix e2e helper project-selection and unify draft dirty-state gate
alex-rawlings-yyc Jul 7, 2026
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
30 changes: 30 additions & 0 deletions __mocks__/lucide-react.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,33 @@ export function Settings(props: Readonly<{ size?: number; className?: string }>)
export function Plus(props: Readonly<{ size?: number; className?: string }>): ReactElement {
return <svg data-testid="plus-icon" {...props} />;
}
/**
* Stub for the FoldVertical icon used by the row-gap merge control (join two stacked rows).
*
* @param props - SVG props forwarded from the component.
* @returns A ReactElement SVG element used as a vertical-merge icon stub in tests.
*/
export function FoldVertical(props: Readonly<{ className?: string }>): ReactElement {
return <svg data-testid="fold-vertical-icon" {...props} />;
}

/**
* Stub for the FoldHorizontal icon used by the continuous-strip merge control (join two adjacent
* segments).
*
* @param props - SVG props forwarded from the component.
* @returns A ReactElement SVG element used as a horizontal-merge icon stub in tests.
*/
export function FoldHorizontal(props: Readonly<{ className?: string }>): ReactElement {
return <svg data-testid="fold-horizontal-icon" {...props} />;
}

/**
* Stub for the Scissors icon used by the split boundary control.
*
* @param props - SVG props forwarded from the component.
* @returns A ReactElement SVG element used as a split icon stub in tests.
*/
export function Scissors(props: Readonly<{ size?: number; className?: string }>): ReactElement {
return <svg data-testid="scissors-icon" {...props} />;
}
5 changes: 4 additions & 1 deletion contributions/localizedStrings.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@
"%interlinearizer_morphemeGloss_label%": "Gloss for morpheme {form}",
"%interlinearizer_tokenChip_editMorphemes%": "Edit morpheme breakdown for {token}",
"%interlinearizer_tokenChip_defineMorphemes%": "Define morpheme breakdown for {token}",
"%interlinearizer_linkButton_crossSegmentDisabledTooltip%": "Cross-segment phrases are not supported. This link button is outside the current segment.",
"%interlinearizer_linkButton_crossSegmentDisabledTooltip%": "Only a free edge token of an adjacent segment can be linked across a boundary.",
"%interlinearizer_boundaryControl_merge%": "Merge segments",
"%interlinearizer_boundaryControl_split%": "Split segment here",
"%interlinearizer_boundaryControl_formerBoundary%": "Former verse boundary",

"%interlinearizer_modal_create_title%": "Create Interlinear Project",
"%interlinearizer_modal_create_name_label%": "Name (optional)",
Expand Down
4 changes: 4 additions & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"BBCCCVVV",
"believ",
"clickability",
"cmdk",
"cullable",
"deconflict",
"deconfliction",
Expand Down Expand Up @@ -55,6 +56,8 @@
"recentering",
"recenters",
"relayout",
"rescuable",
"resegment",
"resnap",
"sandboxed",
"scriptio",
Expand All @@ -65,6 +68,7 @@
"Stylesheet",
"typedefs",
"unanalyzed",
"unglossed",
"unhover",
"unobserves",
"unphrased",
Expand Down
19 changes: 18 additions & 1 deletion e2e-tests/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# e2e-tests

End-to-end tests for the interlinearizer extension using Playwright + Electron. The suite launches a real Platform.Bible instance with the extension loaded via `--extensions` and verifies the extension starts up correctly. Currently contains one smoke test confirming the extension activates and registers its PAPI command.
End-to-end tests for the interlinearizer extension using Playwright + Electron. The suite has two tiers:

- **Smoke tests** (`tests/smoke/`, `app.fixture`) launch a fresh Platform.Bible instance with the extension loaded via `--extensions` and verify the extension starts up correctly.
- **Feature tests** (`tests/features/`, `cdp.fixture`) attach to an already-running `npm run start:cdp` instance and exercise interlinearizer UI flows (glossing, draft persistence, project modals).

**Contents:**

Expand All @@ -17,3 +20,17 @@ These tests are adapted from `paranext-core`'s e2e suite with changes to support
- **Extension launch helper** — `fixtures/helpers.ts` uses `launchElectronWithExtension()` instead of `launchElectronApp()`. It passes `--extensions <dist>` to the Electron process, resolves the Electron binary from paranext-core's `node_modules`, and polls `rpc.discover` for the extension's PAPI method to confirm activation.
- **Window finding** — `fixtures/app.fixture.ts` manually polls `electronApp.windows()` by URL instead of calling `electronApp.firstWindow()`, because the extension injects content into an existing window rather than being the sole owner of the renderer.
- **Renderer readiness** — `global-setup.ts` adds an HTTP GET probe after the TCP port check to wait for webpack compilation to finish, rather than assuming the port being open means the bundle is ready.

## Writing feature tests

Feature tests run with `npm run test:e2e:cdp` against a shared, long-lived `npm run start:cdp` instance, so they must assume nothing about its state and must leave nothing behind that could poison the next run. The protocol:

- **Import from `cdp.fixture`, never `app.fixture`.** The CDP config already serializes execution (`workers: 1`), so tests never race each other on the shared instance.
- **The instance is only ever used with the WEB project.** This is an operating assumption, not something tests verify: `ensureInterlinearizerOpenOnWeb()` trusts an existing Interlinearizer tab and only picks WEB when opening fresh. Don't point `start:cdp` at other projects.
- **Mutating tests operate on the dedicated "E2E Test Project", never on a developer's own projects.** `ensureE2eProjectActive()` opens it (creating it on first use) at the start of each mutating test. Because the draft is the single per-source working buffer, replacing it could destroy unsaved developer work — so when the draft is dirty and the active project is _not_ the e2e project, the helper first saves the draft into a new `e2e-rescued-work-<timestamp>` project. Rescue projects are backups, not junk: delete them manually once recovered. Dirty state left while the e2e project is active is treated as leftover test data and discarded.
- **Self-establish every precondition.** Each mutating test starts with `ensureInterlinearizerOpenOnWeb()` → `ensureE2eProjectActive()` → `navigateToScriptureRef()` (the scroll-group reference could be anywhere) → `wipeDraft()`.
- **Reset at the start, tidy at the end.** The start-of-test sequence is what guarantees correctness — it self-heals whatever a failed run left behind. Mutating tests additionally end with `ensureE2eProjectActive(page, { rescueDirtyDraft: false })` to discard their own leftovers; this is a courtesy so the next run doesn't misread test junk as rescuable developer work, not something correctness depends on.
- **Use unique per-run values** (e.g. `` `e2e-gloss-${Date.now()}` ``) for anything written into the draft, so a stale leftover can never satisfy an assertion.
- **Drive only the visible UI.** No JSON-RPC/WebSocket calls to set up or assert state (the rpc.discover readiness polls in the shared helpers are the one sanctioned exception).
- **Prefer existing accessible selectors** (roles, aria-labels like `Gloss for {word}`, ModalShell title ids) over adding new `data-testid`s to production code.
- **Mutating tests must not overwrite or delete projects, and must not create any beyond what `ensureE2eProjectActive()` creates** (the e2e project itself, plus rescue projects). The current modal coverage is a read-only cancel tour; a create/delete lifecycle test needs its own self-healing cleanup (e.g. deleting leftover `e2e-*` projects at start) before it's safe on a shared instance.
Loading
Loading