You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
An audit of the Jest test suite (all ~51 files under src/__tests__/) turned up
substantial duplication of fixture builders and mock scaffolding. Several helpers
that already exist in the shared files are reimplemented locally under new names,
and several fixture shapes are hand-rolled independently in 3+ files with no shared
factory. This is test-only cleanup: no product code changes, and behavior must be
unchanged (the suite should stay green with no test logic altered).
STUB_PROJECT / STUB_PROJECT_2 are byte-for-byte identical between SelectInterlinearProjectModal.test.tsx:24 and SaveAsProjectModal.test.tsx:32.
Promote to a single shared summary fixture (or derive from makeStubProject).
MOCK_FULL_PROJECT reimplements makeStubProject inline at ProjectModals.test.tsx:33 ({ ...MOCK_PROJECT, analysis: emptyAnalysis() } is makeStubProject's body). Same for its _WITH_TARGET / _WITH_SEGMENTATION variants. STUB_ACTIVE_PROJECT (InterlinearizerLoader.test.tsx:185) ≈ MOCK_PROJECT
(ProjectModals.test.tsx:16).
makeLargeBook(count) defined twice with the same intent: ContinuousView.test.tsx:362 and Interlinearizer.test.tsx:303
(differ only in token refs / textVersion). Unify.
defaultScrRef reinvented.GEN_1_1_SRC_REF at useInterlinearizerBookData.test.ts:90 equals defaultScrRef; verse-ref.test.ts makeRef(1,1) reproduces it. The raw { book:'GEN', chapterNum:1, verseNum:1 }
literal is inlined 40+ times across Interlinearizer, InterlinearizerLoader, InterlinearNavContext, and useSegmentWindow tests — swap for defaultScrRef
where it's the plain default.
Tier 2 — Promote a new shared helper (real duplication, needs a canonical signature)
Each bullet is a new helper to add to test-helpers.ts; pick the signature during the PR.
RawBook fixture appears 3×: makeRawBook (bookTokenizer.test.ts:14),
the inline RawBook inside resegmentBook.test.ts's makeBook, and TEST_RAW_BOOK
(useInterlinearizerBookData.test.ts:19). → makeRawBook (then makeVerseBook can
be tokenizeBook(makeRawBook(...))).
SegmentationDispatch mock { merge, split, move: jest.fn() } duplicated in TokenLinkIcon.test.tsx (makeDispatch), PhraseStripParts.test.tsx (inline in renderBoundary), Interlinearizer.test.tsx (makeRawDispatch). → makeSegmentationDispatch.
Mutable scroll-group hook — renderNavMutable (InterlinearNavContext.test.tsx)
and makeMutableScrollGroupHook/renderFadeLoader (InterlinearizerLoader.test.tsx)
share the same let current; hook = () => [current, ()=>{}, undefined, ()=>{}].
Consider a setRef-returning variant of makeScrollGroupHook.
mockUseProjectSettings — same name + near-identical body in useInterlinearizerBookData.test.ts and useOptimisticBooleanSetting.test.ts.
Punctuation-Token literal — mkPunct (PhraseStripParts, TokenLinkIcon, token-layout), TEST_PUNCT (PhraseBox), PUNCT_TOKEN (TokenChip), plus punctuation branches of makeMixedBook/makeWordFreeBook/PUNCT_SEGMENT. → makePunctToken.
Single-/token-less GEN Segment shape built independently by verse-ref.test.ts makeSegment, SegmentationStore.test.tsx (inline), useSegmentWindow.test.ts makeSegment, and every Book builder's embedded segments. → makeSegment.
@papi/backend mock type-guard — isPapiBackendTestMock/PapiBackendTestMock
(main.test.ts) vs isStorageMock/StorageMock (projectStorage.test.ts): same __mockReadUserData/__mockWriteUserData/__mockLogger guard pattern.
Tier 3 — Widespread inlining of an existing helper (lower risk, high churn; optional)
withAnalysisStore bypassed — AnalysisStore.test.tsx inlines <AnalysisStoreProvider analysisLanguage="und"> ~30×, TokenChip.test.tsx ~30×, PhraseBox.test.tsxrenderBox reimplements it. (Note: renderChip in the
suggestions test legitimately needs analysisLanguage="en" + extra props and can't
use the shared wrapper as-is.)
makePhraseLink bypassed despite being imported — SegmentView.test.tsx
inlines the approved-link literal ~6×, ContinuousView.test.tsx ~9×.
allFalseViewOptions rebuilt inline in ViewOptionsDropdown.test.tsxDEFAULT_PROPS.
Modal beforeEach scaffold — const mockSendCommand = jest.mocked(papi.commands.sendCommand)
(4 files), the LOCALIZED + useLocalizedStrings → [LOCALIZED, false] triad (~8 files),
and the defaultProps/testProps idiom recur across the modal tests. A shared
localized-strings setup / renderModal helper would cover these.
MOCK_SEGMENTATION{ removedVerseStarts:['GEN 1:2:0'], addedStarts:[] } inlined
in ProjectModals, InterlinearizerLoader, main, and projectStorage tests.
Out of scope / noted but not actionable
requiredProps() name collides across ContinuousView / SegmentView / PhraseBox /
TokenLinkIcon / TokenChip / ArcOverlay — same pattern, different bodies; not
consolidatable (per-component props).
ResizeObserver stub harnesses (installResizeObserver in useSegmentWindow vs installObserverHarness in useArcPaths) share a concept but genuinely diverge.
Constraints
Test-only changes; no product code touched.
No test logic changes — only swap fixtures/builders for equivalents. Suite stays green.
Follow repo conventions: no as casts (no-type-assertion is enforced), full JSDoc on
named helpers, American spelling. Run npm test and npm run lint-fix before finishing.
Suggest landing Tier 1 first (mechanical) as its own PR, then Tier 2, leaving Tier 3 optional.
Summary
An audit of the Jest test suite (all ~51 files under
src/__tests__/) turned upsubstantial duplication of fixture builders and mock scaffolding. Several helpers
that already exist in the shared files are reimplemented locally under new names,
and several fixture shapes are hand-rolled independently in 3+ files with no shared
factory. This is test-only cleanup: no product code changes, and behavior must be
unchanged (the suite should stay green with no test logic altered).
Shared helper files today:
src/__tests__/test-helpers.ts—makeWordToken,makePhraseLink,GEN_1_1_BOOK,makeStubProject,makePhraseStripContext,defaultScrRef,makeScrollGroupHook,makeWebViewState,createTestActivationContextsrc/__tests__/components/test-helpers.tsx—withAnalysisStore,allFalseViewOptionsTier 1 — Reuse an existing shared helper (mechanical, high confidence)
These reimplement a helper the suite already exports; route them through the shared one.
makeWordTokenreimplemented under other names. Replace with the shared helper:src/__tests__/utils/token-layout.test.tsmkWord(byte-for-byte identical)src/__tests__/components/TokenChip.suggestions.test.tsxwordToken(onlycharEnddiffers; never asserted)src/__tests__/components/TokenChip.test.tsx+MorphemeBox.test.tsxconst WORD_TOKEN(same literal in both)src/__tests__/components/PhraseBox.test.tsxTEST_TOKEN/TEST_TOKEN_2(file already importsmakeWordToken)STUB_PROJECT/STUB_PROJECT_2are byte-for-byte identical betweenSelectInterlinearProjectModal.test.tsx:24andSaveAsProjectModal.test.tsx:32.Promote to a single shared summary fixture (or derive from
makeStubProject).MOCK_FULL_PROJECTreimplementsmakeStubProjectinline atProjectModals.test.tsx:33({ ...MOCK_PROJECT, analysis: emptyAnalysis() }ismakeStubProject's body). Same for its_WITH_TARGET/_WITH_SEGMENTATIONvariants.STUB_ACTIVE_PROJECT(InterlinearizerLoader.test.tsx:185) ≈MOCK_PROJECT(
ProjectModals.test.tsx:16).makeLargeBook(count)defined twice with the same intent:ContinuousView.test.tsx:362andInterlinearizer.test.tsx:303(differ only in token refs /
textVersion). Unify.defaultScrRefreinvented.GEN_1_1_SRC_REFatuseInterlinearizerBookData.test.ts:90equalsdefaultScrRef;verse-ref.test.tsmakeRef(1,1)reproduces it. The raw{ book:'GEN', chapterNum:1, verseNum:1 }literal is inlined 40+ times across
Interlinearizer,InterlinearizerLoader,InterlinearNavContext, anduseSegmentWindowtests — swap fordefaultScrRefwhere it's the plain default.
Tier 2 — Promote a new shared helper (real duplication, needs a canonical signature)
Each bullet is a new helper to add to
test-helpers.ts; pick the signature during the PR.makeBook(verses[])appears 3× (near-identical):resegmentBook.test.ts:15,segmentation.test.ts:23,verse-superscripts.test.ts:16.→
makeVerseBook.RawBookfixture appears 3×:makeRawBook(bookTokenizer.test.ts:14),the inline
RawBookinsideresegmentBook.test.ts'smakeBook, andTEST_RAW_BOOK(
useInterlinearizerBookData.test.ts:19). →makeRawBook(thenmakeVerseBookcanbe
tokenizeBook(makeRawBook(...))).SegmentationDispatchmock{ merge, split, move: jest.fn() }duplicated inTokenLinkIcon.test.tsx(makeDispatch),PhraseStripParts.test.tsx(inline inrenderBoundary),Interlinearizer.test.tsx(makeRawDispatch). →makeSegmentationDispatch.renderNavMutable(InterlinearNavContext.test.tsx)and
makeMutableScrollGroupHook/renderFadeLoader(InterlinearizerLoader.test.tsx)share the same
let current; hook = () => [current, ()=>{}, undefined, ()=>{}].Consider a
setRef-returning variant ofmakeScrollGroupHook.mockUseProjectSettings— same name + near-identical body inuseInterlinearizerBookData.test.tsanduseOptimisticBooleanSetting.test.ts.mkPunct(PhraseStripParts, TokenLinkIcon, token-layout),TEST_PUNCT(PhraseBox),PUNCT_TOKEN(TokenChip), plus punctuation branches ofmakeMixedBook/makeWordFreeBook/PUNCT_SEGMENT. →makePunctToken.Segmentshape built independently byverse-ref.test.tsmakeSegment,SegmentationStore.test.tsx(inline),useSegmentWindow.test.tsmakeSegment, and every Book builder's embedded segments. →makeSegment.@papi/backendmock type-guard —isPapiBackendTestMock/PapiBackendTestMock(
main.test.ts) vsisStorageMock/StorageMock(projectStorage.test.ts): same__mockReadUserData/__mockWriteUserData/__mockLoggerguard pattern.Tier 3 — Widespread inlining of an existing helper (lower risk, high churn; optional)
withAnalysisStorebypassed —AnalysisStore.test.tsxinlines<AnalysisStoreProvider analysisLanguage="und">~30×,TokenChip.test.tsx~30×,PhraseBox.test.tsxrenderBoxreimplements it. (Note:renderChipin thesuggestions test legitimately needs
analysisLanguage="en"+ extra props and can'tuse the shared wrapper as-is.)
makePhraseLinkbypassed despite being imported —SegmentView.test.tsxinlines the approved-link literal ~6×,
ContinuousView.test.tsx~9×.allFalseViewOptionsrebuilt inline inViewOptionsDropdown.test.tsxDEFAULT_PROPS.beforeEachscaffold —const mockSendCommand = jest.mocked(papi.commands.sendCommand)(4 files), the
LOCALIZED+useLocalizedStrings → [LOCALIZED, false]triad (~8 files),and the
defaultProps/testPropsidiom recur across the modal tests. A sharedlocalized-strings setup /
renderModalhelper would cover these.MOCK_SEGMENTATION{ removedVerseStarts:['GEN 1:2:0'], addedStarts:[] }inlinedin ProjectModals, InterlinearizerLoader, main, and projectStorage tests.
Out of scope / noted but not actionable
requiredProps()name collides across ContinuousView / SegmentView / PhraseBox /TokenLinkIcon / TokenChip / ArcOverlay — same pattern, different bodies; not
consolidatable (per-component props).
installResizeObserverin useSegmentWindow vsinstallObserverHarnessin useArcPaths) share a concept but genuinely diverge.Constraints
ascasts (no-type-assertionis enforced), full JSDoc onnamed helpers, American spelling. Run
npm testandnpm run lint-fixbefore finishing.