diff --git a/src/__tests__/analysis-repository-conformance.ts b/src/__tests__/analysis-repository-conformance.ts index 13db04eed..3359b941e 100644 --- a/src/__tests__/analysis-repository-conformance.ts +++ b/src/__tests__/analysis-repository-conformance.ts @@ -5,7 +5,7 @@ import type { AnalysisRepository, DraftExport } from '../types/analysis-reposito import { emptyAnalysis, emptyDraft } from '../types/empty-factories'; import { CURRENT_MODEL_VERSION } from '../types/model-version'; import { ReadOnlyAnalysisError } from '../types/read-only-analysis-error'; -import { FIXTURE_STAMPS, makePhraseLink } from './test-helpers'; +import { FIXTURE_STAMPS, makePhraseLink, makeTokenLink } from './test-helpers'; /** One empty store an {@link AnalysisRepository} implementation keeps drafts in. */ export interface ConformanceStore { @@ -49,16 +49,6 @@ export interface ConformanceStore { const SOURCE = 'source-project'; -function tokenLink(id: string, analysisId: string, tokenRef: string, surfaceText: string) { - return { - id, - analysisId, - ...FIXTURE_STAMPS, - status: 'approved' as const, - token: { tokenRef, surfaceText }, - }; -} - /** * Every record kind and every optional field, spread over GEN and EXO. One payload is linked from * both books and one from neither. Arrays inside records are out of sorted order, so a repository @@ -119,11 +109,14 @@ function fullAnalysis(): TextAnalysis { { id: 'ta-unlinked', ...FIXTURE_STAMPS, surfaceText: 'spare', gloss: { en: 'spare' } }, ], tokenAnalysisLinks: [ - tokenLink('tl-gen-and', 'ta-and', 'GEN 1:1:0', 'and'), - tokenLink('tl-exo-and', 'ta-and', 'EXO 1:1:0', 'and'), + makeTokenLink('ta-and', 'GEN 1:1:0', { id: 'tl-gen-and', surfaceText: 'and' }), + makeTokenLink('ta-and', 'EXO 1:1:0', { id: 'tl-exo-and', surfaceText: 'and' }), { - ...tokenLink('tl-gen-beginning', 'ta-beginning', 'GEN 1:1:9', 'beginning'), - status: 'suggested', + ...makeTokenLink('ta-beginning', 'GEN 1:1:9', { + id: 'tl-gen-beginning', + surfaceText: 'beginning', + status: 'suggested', + }), confidence: 'low', }, ], @@ -340,7 +333,9 @@ export function describeAnalysisRepositoryConformance( analysis: { ...emptyAnalysis(), tokenAnalyses: [{ id: 'ta-and', ...FIXTURE_STAMPS, surfaceText: 'and' }], - tokenAnalysisLinks: [tokenLink('tl-and', 'ta-and', 'GEN 1:1:0', 'and')], + tokenAnalysisLinks: [ + makeTokenLink('ta-and', 'GEN 1:1:0', { id: 'tl-and', surfaceText: 'and' }), + ], }, }; await (await store.open(SOURCE)).replaceAll(minimal); diff --git a/src/__tests__/test-helpers.ts b/src/__tests__/test-helpers.ts index 745cb1093..d379a3c71 100644 --- a/src/__tests__/test-helpers.ts +++ b/src/__tests__/test-helpers.ts @@ -1,6 +1,14 @@ import type { SerializedVerseRef } from '@sillsdev/scripture'; import type { ExecutionActivationContext, UseWebViewScrollGroupScrRefHook } from '@papi/core'; -import type { Book, InterlinearProject, PhraseAnalysisLink, Segment, Token } from 'interlinearizer'; +import type { + AssignmentStatus, + Book, + InterlinearProject, + PhraseAnalysisLink, + Segment, + Token, + TokenAnalysisLink, +} from 'interlinearizer'; import { UnsubscriberAsyncList } from 'platform-bible-utils'; import { useEffect, useState } from 'react'; import { tokenizeBook } from 'parsers/papi/bookTokenizer'; @@ -460,6 +468,31 @@ export function makePhraseLink( }; } +type TokenLinkFixtureFields = { + /** + * The form the link was written against. Defaults to the token ref, which suits a fixture only + * when drift detection is not under test. + */ + surfaceText?: string; + /** Defaults to `'approved'`. */ + status?: AssignmentStatus; + /** Defaults to an id that differs whenever the token ref, analysis id, or status does. */ + id?: string; +}; + +/** Builds a token link fixture joining `tokenRef` to the payload `analysisId`. */ +export function makeTokenLink( + analysisId: string, + tokenRef: string, + { + surfaceText = tokenRef, + status = 'approved', + id = JSON.stringify([tokenRef, analysisId, status]), + }: TokenLinkFixtureFields = {}, +): TokenAnalysisLink { + return { ...FIXTURE_STAMPS, id, analysisId, status, token: { tokenRef, surfaceText } }; +} + /** * Reports the host as macOS to the user-agent check behind the platform's `isMacOs`, so a test can * assert the Mac wording of a keyboard hint. diff --git a/src/__tests__/types/type-guards.test.ts b/src/__tests__/types/type-guards.test.ts index a9ac63852..e91e058cc 100644 --- a/src/__tests__/types/type-guards.test.ts +++ b/src/__tests__/types/type-guards.test.ts @@ -1,5 +1,10 @@ -import type { AssignmentStatus, PhraseAnalysisLink, TextAnalysis } from 'interlinearizer'; -import { FIXTURE_STAMPS } from '../test-helpers'; +import type { + AssignmentStatus, + PhraseAnalysisLink, + TextAnalysis, + TokenAnalysisLink, +} from 'interlinearizer'; +import { FIXTURE_STAMPS, makeTokenLink } from '../test-helpers'; import { emptyAnalysis } from '../../types/empty-factories'; import { hasLinkWithoutId, @@ -95,14 +100,9 @@ describe('isTextAnalysis', () => { }); it('rejects a token link that carries no id', () => { - expect( - isTextAnalysis({ - ...emptyAnalysis(), - tokenAnalysisLinks: [ - { ...FIXTURE_STAMPS, analysisId: 'ta-1', status: 'approved', token: TOKEN }, - ], - }), - ).toBe(false); + const link: Partial = makeTokenLink('ta-1', 'GEN 1:1:0'); + delete link.id; + expect(isTextAnalysis({ ...emptyAnalysis(), tokenAnalysisLinks: [link] })).toBe(false); }); it('rejects a segment link that carries no id', () => { @@ -163,15 +163,7 @@ describe('isTextAnalysis', () => { expect( isTextAnalysis({ ...emptyAnalysis(), - tokenAnalysisLinks: [ - { - id: 'tl-1', - analysisId: 'ta-1', - status: 'approved', - confidence: 'certain', - token: TOKEN, - }, - ], + tokenAnalysisLinks: [{ ...makeTokenLink('ta-1', 'GEN 1:1:0'), confidence: 'certain' }], }), ).toBe(false); }); @@ -181,20 +173,6 @@ function tokenAnalysis(id: string) { return { ...FIXTURE_STAMPS, id, surfaceText: 'word' }; } -function tokenLink( - analysisId: string, - status: AssignmentStatus = 'approved', - tokenRef = TOKEN.tokenRef, -) { - return { - ...FIXTURE_STAMPS, - id: `${tokenRef}:${analysisId}:${status}`, - analysisId, - status, - token: { ...TOKEN, tokenRef }, - }; -} - function phraseLink( analysisId: string, tokenRefs: string[], @@ -224,7 +202,7 @@ describe('validateTextAnalysis', () => { validateTextAnalysis( tokenLayer({ tokenAnalyses: [tokenAnalysis('ta-1')], - tokenAnalysisLinks: [tokenLink('ta-1')], + tokenAnalysisLinks: [makeTokenLink('ta-1', 'GEN 1:1:0')], }), ), ).toEqual([]); @@ -235,7 +213,10 @@ describe('validateTextAnalysis', () => { validateTextAnalysis( tokenLayer({ tokenAnalyses: [tokenAnalysis('ta-1'), tokenAnalysis('ta-2')], - tokenAnalysisLinks: [tokenLink('ta-1'), tokenLink('ta-2')], + tokenAnalysisLinks: [ + makeTokenLink('ta-1', 'GEN 1:1:0'), + makeTokenLink('ta-2', 'GEN 1:1:0'), + ], }), ), ).toEqual([{ kind: 'multipleApproved', layer: 'token', count: 1, sample: ['GEN 1:1:0'] }]); @@ -246,7 +227,10 @@ describe('validateTextAnalysis', () => { validateTextAnalysis( tokenLayer({ tokenAnalyses: [tokenAnalysis('ta-1'), tokenAnalysis('ta-2')], - tokenAnalysisLinks: [tokenLink('ta-1'), tokenLink('ta-2', 'suggested')], + tokenAnalysisLinks: [ + makeTokenLink('ta-1', 'GEN 1:1:0'), + makeTokenLink('ta-2', 'GEN 1:1:0', { status: 'suggested' }), + ], }), ), ).toEqual([]); @@ -254,7 +238,9 @@ describe('validateTextAnalysis', () => { it('reports a link whose analysisId names no payload', () => { expect( - validateTextAnalysis(tokenLayer({ tokenAnalysisLinks: [tokenLink('ta-missing')] })), + validateTextAnalysis( + tokenLayer({ tokenAnalysisLinks: [makeTokenLink('ta-missing', 'GEN 1:1:0')] }), + ), ).toEqual([{ kind: 'danglingLink', layer: 'token', count: 1, sample: ['GEN 1:1:0'] }]); }); @@ -350,8 +336,8 @@ describe('validateTextAnalysis', () => { tokenLayer({ tokenAnalyses: [tokenAnalysis('ta-1')], tokenAnalysisLinks: [ - { ...tokenLink('ta-1', 'approved', 'GEN 1:1:0'), id: 'tl-1' }, - { ...tokenLink('ta-1', 'approved', 'GEN 1:1:5'), id: 'tl-1' }, + makeTokenLink('ta-1', 'GEN 1:1:0', { id: 'tl-1' }), + makeTokenLink('ta-1', 'GEN 1:1:5', { id: 'tl-1' }), ], }), ), @@ -395,7 +381,7 @@ describe('validateTextAnalysis', () => { validateTextAnalysis( tokenLayer({ tokenAnalyses: [tokenAnalysis('ta-1'), tokenAnalysis('ta-1')], - tokenAnalysisLinks: [tokenLink('ta-1')], + tokenAnalysisLinks: [makeTokenLink('ta-1', 'GEN 1:1:0')], }), ), ).toEqual([{ kind: 'duplicateAnalysisId', layer: 'token', count: 1, sample: ['ta-1'] }]); @@ -406,7 +392,10 @@ describe('validateTextAnalysis', () => { validateTextAnalysis( tokenLayer({ tokenAnalyses: [tokenAnalysis('ta-1'), tokenAnalysis('ta-2')], - tokenAnalysisLinks: [tokenLink('ta-1'), tokenLink('ta-2', 'suggested')], + tokenAnalysisLinks: [ + makeTokenLink('ta-1', 'GEN 1:1:0'), + makeTokenLink('ta-2', 'GEN 1:1:0', { status: 'suggested' }), + ], }), ), ).toEqual([]); @@ -462,14 +451,9 @@ describe('validateTextAnalysis', () => { describe('hasLinkWithoutId', () => { it('finds a token link with no id', () => { - expect( - hasLinkWithoutId({ - ...emptyAnalysis(), - tokenAnalysisLinks: [ - { ...FIXTURE_STAMPS, analysisId: 'ta-1', status: 'approved', token: TOKEN }, - ], - }), - ).toBe(true); + const link: Partial = makeTokenLink('ta-1', 'GEN 1:1:0'); + delete link.id; + expect(hasLinkWithoutId({ ...emptyAnalysis(), tokenAnalysisLinks: [link] })).toBe(true); }); it('finds a segment link with no id', () => { @@ -487,7 +471,7 @@ describe('hasLinkWithoutId', () => { expect( hasLinkWithoutId( tokenLayer({ - tokenAnalysisLinks: [tokenLink('ta-1')], + tokenAnalysisLinks: [makeTokenLink('ta-1', 'GEN 1:1:0')], segmentAnalysisLinks: [ { ...FIXTURE_STAMPS, diff --git a/src/__tests__/utils/analysis-book.test.ts b/src/__tests__/utils/analysis-book.test.ts index 3ce9ba685..eba0be78a 100644 --- a/src/__tests__/utils/analysis-book.test.ts +++ b/src/__tests__/utils/analysis-book.test.ts @@ -5,7 +5,6 @@ import type { SegmentAnalysisLink, TextAnalysis, TokenAnalysis, - TokenAnalysisLink, } from 'interlinearizer'; import { BOOKLESS_PARTITION, @@ -15,7 +14,7 @@ import { removeBookFromSegmentation, splitAnalysisByBook, } from '../../utils/analysis-book'; -import { makePhraseLink, FIXTURE_STAMPS } from '../test-helpers'; +import { makePhraseLink, makeTokenLink, FIXTURE_STAMPS } from '../test-helpers'; /** * Creates a minimal {@link TokenAnalysis} payload record fixture, with `surfaceText` defaulting to @@ -25,17 +24,6 @@ function mkTokenAnalysis(id: string, surfaceText = id): TokenAnalysis { return { ...FIXTURE_STAMPS, id, surfaceText }; } -/** Creates a {@link TokenAnalysisLink} joining a token ref to an analysis id. */ -function mkTokenLink(analysisId: string, tokenRef: string): TokenAnalysisLink { - return { - id: `${tokenRef}:${analysisId}`, - ...FIXTURE_STAMPS, - analysisId, - status: 'approved', - token: { tokenRef, surfaceText: tokenRef }, - }; -} - /** * Creates a minimal {@link SegmentAnalysis} payload record fixture, with `surfaceText` defaulting to * `id`. @@ -77,7 +65,7 @@ describe('splitAnalysisByBook', () => { it('gives each book only its own records', () => { const analysis: TextAnalysis = { tokenAnalyses: [mkTokenAnalysis('ta-gen'), mkTokenAnalysis('ta-exo')], - tokenAnalysisLinks: [mkTokenLink('ta-gen', 'GEN 1:1'), mkTokenLink('ta-exo', 'EXO 1:1')], + tokenAnalysisLinks: [makeTokenLink('ta-gen', 'GEN 1:1'), makeTokenLink('ta-exo', 'EXO 1:1')], segmentAnalyses: [], segmentAnalysisLinks: [], phraseAnalyses: [], @@ -110,7 +98,7 @@ describe('splitAnalysisByBook', () => { it('copies a payload shared across books into each book that links it', () => { const analysis: TextAnalysis = { tokenAnalyses: [mkTokenAnalysis('shared')], - tokenAnalysisLinks: [mkTokenLink('shared', 'GEN 1:1'), mkTokenLink('shared', 'EXO 1:1')], + tokenAnalysisLinks: [makeTokenLink('shared', 'GEN 1:1'), makeTokenLink('shared', 'EXO 1:1')], segmentAnalyses: [], segmentAnalysisLinks: [], phraseAnalyses: [], @@ -144,7 +132,7 @@ describe('splitAnalysisByBook', () => { it('carries a payload once when two links in the same book share it', () => { const analysis: TextAnalysis = { tokenAnalyses: [mkTokenAnalysis('shared')], - tokenAnalysisLinks: [mkTokenLink('shared', 'GEN 1:1'), mkTokenLink('shared', 'GEN 1:2')], + tokenAnalysisLinks: [makeTokenLink('shared', 'GEN 1:1'), makeTokenLink('shared', 'GEN 1:2')], segmentAnalyses: [], segmentAnalysisLinks: [], phraseAnalyses: [], @@ -160,7 +148,7 @@ describe('splitAnalysisByBook', () => { it('keeps a payload no link references out of the linked book’s partition', () => { const analysis: TextAnalysis = { tokenAnalyses: [mkTokenAnalysis('orphan')], - tokenAnalysisLinks: [mkTokenLink('linked', 'GEN 1:1')], + tokenAnalysisLinks: [makeTokenLink('linked', 'GEN 1:1')], segmentAnalyses: [], segmentAnalysisLinks: [], phraseAnalyses: [], @@ -175,7 +163,7 @@ describe('splitAnalysisByBook', () => { it('partitions a payload no link references under the bookless key', () => { const analysis: TextAnalysis = { tokenAnalyses: [mkTokenAnalysis('orphan')], - tokenAnalysisLinks: [mkTokenLink('linked', 'GEN 1:1')], + tokenAnalysisLinks: [makeTokenLink('linked', 'GEN 1:1')], segmentAnalyses: [], segmentAnalysisLinks: [], phraseAnalyses: [], @@ -206,7 +194,7 @@ describe('splitAnalysisByBook', () => { it('does not file a payload as bookless when another book links it', () => { const analysis: TextAnalysis = { tokenAnalyses: [mkTokenAnalysis('shared')], - tokenAnalysisLinks: [mkTokenLink('shared', 'GEN 1:1')], + tokenAnalysisLinks: [makeTokenLink('shared', 'GEN 1:1')], segmentAnalyses: [], segmentAnalysisLinks: [], phraseAnalyses: [], @@ -251,7 +239,10 @@ describe('splitAnalysisByBook', () => { describe('mergeAnalyses', () => { const genesis: TextAnalysis = { tokenAnalyses: [mkTokenAnalysis('ta-gen'), mkTokenAnalysis('shared')], - tokenAnalysisLinks: [mkTokenLink('ta-gen', 'GEN 1:1:0'), mkTokenLink('shared', 'GEN 1:1:3')], + tokenAnalysisLinks: [ + makeTokenLink('ta-gen', 'GEN 1:1:0'), + makeTokenLink('shared', 'GEN 1:1:3'), + ], segmentAnalyses: [mkSegmentAnalysis('sa-gen')], segmentAnalysisLinks: [mkSegmentLink('sa-gen', 'GEN 1:1')], phraseAnalyses: [{ ...FIXTURE_STAMPS, id: 'pa-gen', surfaceText: 'in the' }], @@ -259,7 +250,7 @@ describe('mergeAnalyses', () => { }; const exodus: TextAnalysis = { tokenAnalyses: [mkTokenAnalysis('shared')], - tokenAnalysisLinks: [mkTokenLink('shared', 'EXO 1:1:0')], + tokenAnalysisLinks: [makeTokenLink('shared', 'EXO 1:1:0')], segmentAnalyses: [mkSegmentAnalysis('sa-exo')], segmentAnalysisLinks: [mkSegmentLink('sa-exo', 'EXO 1:1')], phraseAnalyses: [], @@ -321,10 +312,10 @@ describe('removeBookFromAnalysis', () => { mkTokenAnalysis('tok-orphan'), ], tokenAnalysisLinks: [ - mkTokenLink('tok-gen', 'GEN 1:1:0'), - mkTokenLink('tok-exo', 'EXO 2:2:0'), + makeTokenLink('tok-gen', 'GEN 1:1:0'), + makeTokenLink('tok-exo', 'EXO 2:2:0'), // Only link referencing tok-orphan is a GEN link → removing GEN orphans the payload. - mkTokenLink('tok-orphan', 'GEN 3:3:0'), + makeTokenLink('tok-orphan', 'GEN 3:3:0'), ], segmentAnalyses: [mkSegmentAnalysis('seg-gen'), mkSegmentAnalysis('seg-exo')], segmentAnalysisLinks: [ diff --git a/src/__tests__/utils/analysis-violations.test.ts b/src/__tests__/utils/analysis-violations.test.ts index b960fa11c..5b8d4c403 100644 --- a/src/__tests__/utils/analysis-violations.test.ts +++ b/src/__tests__/utils/analysis-violations.test.ts @@ -3,17 +3,7 @@ import type { TextAnalysis, TokenAnalysisLink } from 'interlinearizer'; import { emptyAnalysis } from '../../types/empty-factories'; import { reportAnalysisViolations } from '../../utils/analysis-violations'; -import { FIXTURE_STAMPS, silentLogger } from '../test-helpers'; - -function linkTo(analysisId: string, tokenRef: string, id = `tl-${tokenRef}`): TokenAnalysisLink { - return { - id, - analysisId, - ...FIXTURE_STAMPS, - status: 'approved', - token: { tokenRef, surfaceText: 'word' }, - }; -} +import { FIXTURE_STAMPS, makeTokenLink, silentLogger } from '../test-helpers'; function analysisWithLinks(tokenAnalysisLinks: TokenAnalysisLink[]): TextAnalysis { return { @@ -56,7 +46,7 @@ describe('reportAnalysisViolations', () => { it('reports nothing for an analysis whose collections agree', () => { const logger = silentLogger(); - reportAnalysisViolations(logger, analysisWithLinks([linkTo('ta-1', 'GEN 1:1:0')]), 'x'); + reportAnalysisViolations(logger, analysisWithLinks([makeTokenLink('ta-1', 'GEN 1:1:0')]), 'x'); expect(logger.warn).not.toHaveBeenCalled(); }); @@ -64,10 +54,10 @@ describe('reportAnalysisViolations', () => { it('reports each violation kind once, with its count and a sample', () => { const logger = silentLogger(); const analysis = analysisWithLinks([ - linkTo('ta-missing', 'GEN 1:1:0'), - linkTo('ta-missing', 'GEN 1:1:4'), - linkTo('ta-1', 'GEN 1:1:8', 'tl-same'), - linkTo('ta-1', 'GEN 1:1:12', 'tl-same'), + makeTokenLink('ta-missing', 'GEN 1:1:0'), + makeTokenLink('ta-missing', 'GEN 1:1:4'), + makeTokenLink('ta-1', 'GEN 1:1:8', { id: 'tl-same' }), + makeTokenLink('ta-1', 'GEN 1:1:12', { id: 'tl-same' }), ]); reportAnalysisViolations(logger, analysis, 'project abc on save'); diff --git a/src/__tests__/utils/project-analysis-summary.test.ts b/src/__tests__/utils/project-analysis-summary.test.ts index ba9b4422f..aa971b251 100644 --- a/src/__tests__/utils/project-analysis-summary.test.ts +++ b/src/__tests__/utils/project-analysis-summary.test.ts @@ -1,25 +1,14 @@ /// -import type { TextAnalysis, TokenAnalysis, TokenAnalysisLink } from 'interlinearizer'; +import type { TextAnalysis, TokenAnalysis } from 'interlinearizer'; import { summarizeAnalysis } from '../../utils/project-analysis-summary'; -import { FIXTURE_STAMPS, makePhraseLink } from '../test-helpers'; +import { FIXTURE_STAMPS, makePhraseLink, makeTokenLink } from '../test-helpers'; /** Creates a minimal {@link TokenAnalysis} payload record fixture. */ function mkTokenAnalysis(id: string): TokenAnalysis { return { ...FIXTURE_STAMPS, id, surfaceText: id }; } -/** Creates a {@link TokenAnalysisLink} joining a token ref to an analysis id. */ -function mkTokenLink(analysisId: string, tokenRef: string): TokenAnalysisLink { - return { - id: `${tokenRef}:${analysisId}`, - ...FIXTURE_STAMPS, - analysisId, - status: 'approved', - token: { tokenRef, surfaceText: tokenRef }, - }; -} - /** Builds a {@link TextAnalysis} with the given token analyses and links, other layers empty. */ function mkAnalysis(overrides: Partial = {}): TextAnalysis { return { @@ -37,7 +26,7 @@ describe('summarizeAnalysis', () => { it('counts token analyses and collects the books their links reference', () => { const analysis = mkAnalysis({ tokenAnalyses: [mkTokenAnalysis('a1'), mkTokenAnalysis('a2')], - tokenAnalysisLinks: [mkTokenLink('a1', 'GEN 1:1:0'), mkTokenLink('a2', 'EXO 2:3:4')], + tokenAnalysisLinks: [makeTokenLink('a1', 'GEN 1:1:0'), makeTokenLink('a2', 'EXO 2:3:4')], }); expect(summarizeAnalysis(analysis)).toEqual({ @@ -49,7 +38,7 @@ describe('summarizeAnalysis', () => { it('ignores segment and phrase analyses, which the row does not describe', () => { const analysis = mkAnalysis({ tokenAnalyses: [mkTokenAnalysis('a1')], - tokenAnalysisLinks: [mkTokenLink('a1', 'GEN 1:1:0')], + tokenAnalysisLinks: [makeTokenLink('a1', 'GEN 1:1:0')], segmentAnalyses: [{ ...FIXTURE_STAMPS, id: 's1', surfaceText: 's1' }], segmentAnalysisLinks: [ { @@ -71,9 +60,9 @@ describe('summarizeAnalysis', () => { const analysis = mkAnalysis({ tokenAnalyses: [mkTokenAnalysis('a1'), mkTokenAnalysis('a2'), mkTokenAnalysis('a3')], tokenAnalysisLinks: [ - mkTokenLink('a1', 'MRK 1:1:0'), - mkTokenLink('a2', 'GEN 1:1:0'), - mkTokenLink('a3', 'GEN 5:2:0'), + makeTokenLink('a1', 'MRK 1:1:0'), + makeTokenLink('a2', 'GEN 1:1:0'), + makeTokenLink('a3', 'GEN 5:2:0'), ], }); @@ -85,9 +74,9 @@ describe('summarizeAnalysis', () => { const analysis = mkAnalysis({ tokenAnalyses: [mkTokenAnalysis('a1'), mkTokenAnalysis('a2'), mkTokenAnalysis('a3')], tokenAnalysisLinks: [ - mkTokenLink('a1', 'LEV 1:1:0'), - mkTokenLink('a2', 'GEN 1:1:0'), - mkTokenLink('a3', 'EXO 1:1:0'), + makeTokenLink('a1', 'LEV 1:1:0'), + makeTokenLink('a2', 'GEN 1:1:0'), + makeTokenLink('a3', 'EXO 1:1:0'), ], }); @@ -98,7 +87,7 @@ describe('summarizeAnalysis', () => { // A corrupt ref stays visible rather than vanishing from a row whose count still includes it. const analysis = mkAnalysis({ tokenAnalyses: [mkTokenAnalysis('a1'), mkTokenAnalysis('a2')], - tokenAnalysisLinks: [mkTokenLink('a1', 'GEN 1:1:0'), mkTokenLink('a2', 'ZZZ 1:1:0')], + tokenAnalysisLinks: [makeTokenLink('a1', 'GEN 1:1:0'), makeTokenLink('a2', 'ZZZ 1:1:0')], }); expect(summarizeAnalysis(analysis).books).toEqual(['ZZZ', 'GEN']); @@ -111,7 +100,7 @@ describe('summarizeAnalysis', () => { it('counts an analysis payload no link references, so the count matches what is stored', () => { const analysis = mkAnalysis({ tokenAnalyses: [mkTokenAnalysis('a1'), mkTokenAnalysis('orphan')], - tokenAnalysisLinks: [mkTokenLink('a1', 'GEN 1:1:0')], + tokenAnalysisLinks: [makeTokenLink('a1', 'GEN 1:1:0')], }); expect(summarizeAnalysis(analysis)).toEqual({ books: ['GEN'], tokenAnalysisCount: 2 }); diff --git a/src/__tests__/utils/reanchor-analysis.test.ts b/src/__tests__/utils/reanchor-analysis.test.ts index 28cd5d4d9..08aee5cac 100644 --- a/src/__tests__/utils/reanchor-analysis.test.ts +++ b/src/__tests__/utils/reanchor-analysis.test.ts @@ -11,7 +11,7 @@ import { reanchorAnalysisToBook } from '../../utils/reanchor-analysis'; import { resegmentBook } from '../../parsers/papi/resegmentBook'; import { mergeSegments, reanchorSegmentation } from '../../utils/segmentation'; import { emptyAnalysis } from '../../types/empty-factories'; -import { makeVerseBook, makePhraseLink, FIXTURE_STAMPS } from '../test-helpers'; +import { makeVerseBook, makePhraseLink, makeTokenLink, FIXTURE_STAMPS } from '../test-helpers'; /** * The stamp a re-anchor writes in these tests. Distinct from `FIXTURE_STAMPS` so an assertion on @@ -27,21 +27,6 @@ function reanchor( return reanchorAnalysisToBook(analysis, book, REANCHOR_STAMP, storedSplits); } -/** Builds an approved token link naming `tokenRef` with the surface text it was written against. */ -function makeTokenLink( - tokenRef: string, - surfaceText: string, - analysisId = 'ta-1', -): TokenAnalysisLink { - return { - id: `${tokenRef}:${analysisId}`, - analysisId, - ...FIXTURE_STAMPS, - status: 'approved', - token: { tokenRef, surfaceText }, - }; -} - /** Seeds a `TextAnalysis` carrying just the given token links. */ function analysisWithTokenLinks(links: TokenAnalysisLink[]): TextAnalysis { return { ...emptyAnalysis(), tokenAnalysisLinks: links }; @@ -106,7 +91,9 @@ function splitTwins(editedText: string): { describe('reanchorAnalysisToBook', () => { it('shifts a link onto the token that kept its surface text when a word is inserted before it', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'it was and unbelievable' }]); - const analysis = analysisWithTokenLinks([makeTokenLink('GEN 1:1:7', 'unbelievable')]); + const analysis = analysisWithTokenLinks([ + makeTokenLink('ta-1', 'GEN 1:1:7', { surfaceText: 'unbelievable' }), + ]); const result = reanchor(analysis, book); @@ -117,7 +104,9 @@ describe('reanchorAnalysisToBook', () => { it('leaves a link alone when its token ref still names the same surface text', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'it was unbelievable' }]); - const analysis = analysisWithTokenLinks([makeTokenLink('GEN 1:1:7', 'unbelievable')]); + const analysis = analysisWithTokenLinks([ + makeTokenLink('ta-1', 'GEN 1:1:7', { surfaceText: 'unbelievable' }), + ]); const result = reanchor(analysis, book); @@ -130,8 +119,8 @@ describe('reanchorAnalysisToBook', () => { // Written against the pre-edit text "the light and the dark", where the two "the"s sat at 0 // and 16. Glossing both is what gives the alignment the context to tell them apart. const analysis = analysisWithTokenLinks([ - makeTokenLink('GEN 1:1:0', 'the'), - makeTokenLink('GEN 1:1:16', 'the', 'ta-2'), + makeTokenLink('ta-1', 'GEN 1:1:0', { surfaceText: 'the' }), + makeTokenLink('ta-2', 'GEN 1:1:16', { surfaceText: 'the' }), ]); const result = reanchor(analysis, book); @@ -146,8 +135,8 @@ describe('reanchorAnalysisToBook', () => { // Written against "bank bank", where the two senses sat at 0 and 5. Which one the edit removed // is unknowable from the text, so neither gloss may claim the survivor. const analysis = analysisWithTokenLinks([ - makeTokenLink('GEN 1:1:0', 'bank'), - makeTokenLink('GEN 1:1:5', 'bank', 'ta-2'), + makeTokenLink('ta-1', 'GEN 1:1:0', { surfaceText: 'bank' }), + makeTokenLink('ta-2', 'GEN 1:1:5', { surfaceText: 'bank' }), ]); const result = reanchor(analysis, book); @@ -158,7 +147,9 @@ describe('reanchorAnalysisToBook', () => { it('leaves a lone repeated form where it was written rather than picking an occurrence', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'the light and the dark' }]); // Only the second "the" is glossed, and its own ref still names it, so it stays put. - const analysis = analysisWithTokenLinks([makeTokenLink('GEN 1:1:14', 'the')]); + const analysis = analysisWithTokenLinks([ + makeTokenLink('ta-1', 'GEN 1:1:14', { surfaceText: 'the' }), + ]); const result = reanchor(analysis, book); @@ -167,7 +158,9 @@ describe('reanchorAnalysisToBook', () => { it('leaves a lone repeated form approved when its own ref still names it', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'the light and the dark' }]); - const analysis = analysisWithTokenLinks([makeTokenLink('GEN 1:1:14', 'the')]); + const analysis = analysisWithTokenLinks([ + makeTokenLink('ta-1', 'GEN 1:1:14', { surfaceText: 'the' }), + ]); const result = reanchor(analysis, book); @@ -178,7 +171,9 @@ describe('reanchorAnalysisToBook', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'dog and the bright the cat' }]); // Written against a verse whose "the" sat at 0. That ref now names "dog", and two "the"s // survive, so no evidence says which one the gloss meant. - const analysis = analysisWithTokenLinks([makeTokenLink('GEN 1:1:0', 'the')]); + const analysis = analysisWithTokenLinks([ + makeTokenLink('ta-1', 'GEN 1:1:0', { surfaceText: 'the' }), + ]); const result = reanchor(analysis, book); @@ -189,8 +184,8 @@ describe('reanchorAnalysisToBook', () => { // Written against "beta x beta y beta" with the first two "beta"s glossed, at 0 and 7. const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'beta x and beta y beta' }]); const analysis = analysisWithTokenLinks([ - makeTokenLink('GEN 1:1:0', 'beta'), - makeTokenLink('GEN 1:1:7', 'beta', 'ta-2'), + makeTokenLink('ta-1', 'GEN 1:1:0', { surfaceText: 'beta' }), + makeTokenLink('ta-2', 'GEN 1:1:7', { surfaceText: 'beta' }), ]); const result = reanchor(analysis, book); @@ -204,8 +199,8 @@ describe('reanchorAnalysisToBook', () => { it('stales a shifted twin whose remaining counterparts are ambiguous', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'beta x and beta y beta' }]); const analysis = analysisWithTokenLinks([ - makeTokenLink('GEN 1:1:0', 'beta'), - makeTokenLink('GEN 1:1:7', 'beta', 'ta-2'), + makeTokenLink('ta-1', 'GEN 1:1:0', { surfaceText: 'beta' }), + makeTokenLink('ta-2', 'GEN 1:1:7', { surfaceText: 'beta' }), ]); const result = reanchor(analysis, book); @@ -217,8 +212,8 @@ describe('reanchorAnalysisToBook', () => { // Written against "alpha beta beta", whose "beta"s sat at 6 and 11. const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'alpha and beta beta' }]); const analysis = analysisWithTokenLinks([ - makeTokenLink('GEN 1:1:6', 'beta'), - makeTokenLink('GEN 1:1:11', 'beta', 'ta-2'), + makeTokenLink('ta-1', 'GEN 1:1:6', { surfaceText: 'beta' }), + makeTokenLink('ta-2', 'GEN 1:1:11', { surfaceText: 'beta' }), ]); const result = reanchor(analysis, book); @@ -233,9 +228,9 @@ describe('reanchorAnalysisToBook', () => { // Written against "light and dark", at 0, 6 and 10. const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'and dark light' }]); const analysis = analysisWithTokenLinks([ - makeTokenLink('GEN 1:1:0', 'light'), - makeTokenLink('GEN 1:1:6', 'and', 'ta-2'), - makeTokenLink('GEN 1:1:10', 'dark', 'ta-3'), + makeTokenLink('ta-1', 'GEN 1:1:0', { surfaceText: 'light' }), + makeTokenLink('ta-2', 'GEN 1:1:6', { surfaceText: 'and' }), + makeTokenLink('ta-3', 'GEN 1:1:10', { surfaceText: 'dark' }), ]); const result = reanchor(analysis, book); @@ -251,7 +246,9 @@ describe('reanchorAnalysisToBook', () => { // Written against "bank x bank" with the second "bank" glossed at 7. The insertion moves the // first "bank" onto that ref, which a snapshot cannot tell apart from the gloss staying put. const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'and so bank x bank' }]); - const analysis = analysisWithTokenLinks([makeTokenLink('GEN 1:1:7', 'bank')]); + const analysis = analysisWithTokenLinks([ + makeTokenLink('ta-1', 'GEN 1:1:7', { surfaceText: 'bank' }), + ]); const result = reanchor(analysis, book); @@ -265,7 +262,7 @@ describe('reanchorAnalysisToBook', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'in the beginning' }]); const analysis: TextAnalysis = { ...emptyAnalysis(), - tokenAnalysisLinks: [makeTokenLink('GEN 1:1:0', 'in')], + tokenAnalysisLinks: [makeTokenLink('ta-1', 'GEN 1:1:0', { surfaceText: 'in' })], phraseAnalysisLinks: [ makePhraseLink('pa-1', ['GEN 1:1:0', 'GEN 1:1:3', 'GEN 1:1:7'], ['in', 'the', 'beginning']), ], @@ -280,7 +277,7 @@ describe('reanchorAnalysisToBook', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'and in the beginning' }]); const analysis: TextAnalysis = { ...emptyAnalysis(), - tokenAnalysisLinks: [makeTokenLink('GEN 1:1:0', 'in')], + tokenAnalysisLinks: [makeTokenLink('ta-1', 'GEN 1:1:0', { surfaceText: 'in' })], phraseAnalysisLinks: [ makePhraseLink('pa-1', ['GEN 1:1:0', 'GEN 1:1:3', 'GEN 1:1:7'], ['in', 'the', 'beginning']), ], @@ -304,7 +301,9 @@ describe('reanchorAnalysisToBook', () => { addedStarts: [], }); // Written against "gamma delta", before "inserted " shifted the verse's offsets. - const analysis = analysisWithTokenLinks([makeTokenLink('GEN 1:2:0', 'gamma')]); + const analysis = analysisWithTokenLinks([ + makeTokenLink('ta-1', 'GEN 1:2:0', { surfaceText: 'gamma' }), + ]); const result = reanchor(analysis, merged); @@ -325,8 +324,8 @@ describe('reanchorAnalysisToBook', () => { // "alpha" occurs once per verse but twice in the merged segment, so only a per-verse alignment // can place either gloss. const analysis = analysisWithTokenLinks([ - makeTokenLink('GEN 1:1:0', 'alpha'), - makeTokenLink('GEN 1:2:0', 'alpha', 'ta-2'), + makeTokenLink('ta-1', 'GEN 1:1:0', { surfaceText: 'alpha' }), + makeTokenLink('ta-2', 'GEN 1:2:0', { surfaceText: 'alpha' }), ]); const result = reanchor(analysis, merged); @@ -337,7 +336,9 @@ describe('reanchorAnalysisToBook', () => { it('flips a link to stale when its token no longer exists in the segment', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'it was fine' }]); - const analysis = analysisWithTokenLinks([makeTokenLink('GEN 1:1:7', 'unbelievable')]); + const analysis = analysisWithTokenLinks([ + makeTokenLink('ta-1', 'GEN 1:1:7', { surfaceText: 'unbelievable' }), + ]); const result = reanchor(analysis, book); @@ -346,7 +347,9 @@ describe('reanchorAnalysisToBook', () => { it('leaves a stale link pointing at the ref it was written against', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'it was fine' }]); - const analysis = analysisWithTokenLinks([makeTokenLink('GEN 1:1:7', 'unbelievable')]); + const analysis = analysisWithTokenLinks([ + makeTokenLink('ta-1', 'GEN 1:1:7', { surfaceText: 'unbelievable' }), + ]); const result = reanchor(analysis, book); @@ -370,7 +373,9 @@ describe('reanchorAnalysisToBook', () => { it('leaves links in books other than the one being re-anchored untouched', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'it was fine' }]); - const analysis = analysisWithTokenLinks([makeTokenLink('EXO 1:1:7', 'unbelievable')]); + const analysis = analysisWithTokenLinks([ + makeTokenLink('ta-1', 'EXO 1:1:7', { surfaceText: 'unbelievable' }), + ]); const result = reanchor(analysis, book); @@ -428,7 +433,7 @@ describe('reanchorAnalysisToBook', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'a big cat sat' }]); const analysis: TextAnalysis = { ...emptyAnalysis(), - tokenAnalysisLinks: [makeTokenLink('GEN 1:1:4', 'cat')], + tokenAnalysisLinks: [makeTokenLink('ta-1', 'GEN 1:1:4', { surfaceText: 'cat' })], phraseAnalysisLinks: [makePhraseLink('pa-1', ['GEN 1:1:0', 'GEN 1:1:4'], ['the', 'cat'])], }; @@ -512,7 +517,7 @@ describe('reanchorAnalysisToBook', () => { const analysis: TextAnalysis = { ...emptyAnalysis(), phraseAnalysisLinks: [makePhraseLink('pa-1', ['GEN 1:1:0', 'GEN 1:1:3'], ['in', 'the'])], - tokenAnalysisLinks: [makeTokenLink('GEN 1:2:4', 'the')], + tokenAnalysisLinks: [makeTokenLink('ta-1', 'GEN 1:2:4', { surfaceText: 'the' })], }; const result = reanchor(analysis, book); @@ -522,7 +527,9 @@ describe('reanchorAnalysisToBook', () => { it('stales a link whose verse the loaded book no longer holds', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'it was unbelievable' }]); - const analysis = analysisWithTokenLinks([makeTokenLink('GEN 1:2:0', 'elsewhere')]); + const analysis = analysisWithTokenLinks([ + makeTokenLink('ta-1', 'GEN 1:2:0', { surfaceText: 'elsewhere' }), + ]); const result = reanchor(analysis, book); @@ -534,7 +541,9 @@ describe('reanchorAnalysisToBook', () => { { sid: 'GEN 1:1', text: 'it was unbelievable' }, { sid: 'GEN 1:2', text: '' }, ]); - const analysis = analysisWithTokenLinks([makeTokenLink('GEN 1:2:0', 'emptied')]); + const analysis = analysisWithTokenLinks([ + makeTokenLink('ta-1', 'GEN 1:2:0', { surfaceText: 'emptied' }), + ]); const result = reanchor(analysis, book); @@ -546,7 +555,9 @@ describe('reanchorAnalysisToBook', () => { { sid: 'GEN 1:1', text: 'it was unbelievable' }, { sid: 'GEN 1:2-3', text: '' }, ]); - const analysis = analysisWithTokenLinks([makeTokenLink('GEN 1:2-3:0', 'emptied')]); + const analysis = analysisWithTokenLinks([ + makeTokenLink('ta-1', 'GEN 1:2-3:0', { surfaceText: 'emptied' }), + ]); const result = reanchor(analysis, book); @@ -558,8 +569,8 @@ describe('reanchorAnalysisToBook', () => { // A stale link's snapshot keeps the form it was written against, so letting it speak for the // ref would re-stale the approval that replaced it. const analysis = analysisWithTokenLinks([ - { ...makeTokenLink('GEN 1:1:0', 'cat'), status: 'stale' }, - makeTokenLink('GEN 1:1:0', 'dog', 'ta-2'), + makeTokenLink('ta-1', 'GEN 1:1:0', { surfaceText: 'cat', status: 'stale' }), + makeTokenLink('ta-2', 'GEN 1:1:0', { surfaceText: 'dog' }), ]); const result = reanchor(analysis, book); @@ -570,8 +581,8 @@ describe('reanchorAnalysisToBook', () => { it('leaves an approval on its word when a stale link of the same form names another ref', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'dog cat' }]); const analysis = analysisWithTokenLinks([ - { ...makeTokenLink('GEN 1:1:0', 'cat'), status: 'stale' }, - makeTokenLink('GEN 1:1:4', 'cat', 'ta-2'), + makeTokenLink('ta-1', 'GEN 1:1:0', { surfaceText: 'cat', status: 'stale' }), + makeTokenLink('ta-2', 'GEN 1:1:4', { surfaceText: 'cat' }), ]); const result = reanchor(analysis, book); @@ -582,8 +593,8 @@ describe('reanchorAnalysisToBook', () => { it('leaves an approval on its word when a rejected link of the same form names another ref', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'dog cat' }]); const analysis = analysisWithTokenLinks([ - { ...makeTokenLink('GEN 1:1:0', 'cat'), status: 'rejected' }, - makeTokenLink('GEN 1:1:4', 'cat', 'ta-2'), + makeTokenLink('ta-1', 'GEN 1:1:0', { surfaceText: 'cat', status: 'rejected' }), + makeTokenLink('ta-2', 'GEN 1:1:4', { surfaceText: 'cat' }), ]); const result = reanchor(analysis, book); @@ -594,8 +605,8 @@ describe('reanchorAnalysisToBook', () => { it('keeps an approval on its word rather than trading it to an earlier stale twin', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'dog cat cat' }]); const analysis = analysisWithTokenLinks([ - { ...makeTokenLink('GEN 1:1:0', 'cat'), status: 'stale' }, - makeTokenLink('GEN 1:1:4', 'cat', 'ta-2'), + makeTokenLink('ta-1', 'GEN 1:1:0', { surfaceText: 'cat', status: 'stale' }), + makeTokenLink('ta-2', 'GEN 1:1:4', { surfaceText: 'cat' }), ]); const result = reanchor(analysis, book); @@ -606,8 +617,8 @@ describe('reanchorAnalysisToBook', () => { it('stales an ambiguous approval rather than letting a rejected twin choose its occurrence', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'x dog cat cat' }]); const analysis = analysisWithTokenLinks([ - { ...makeTokenLink('GEN 1:1:0', 'cat'), status: 'rejected' }, - makeTokenLink('GEN 1:1:4', 'cat', 'ta-2'), + makeTokenLink('ta-1', 'GEN 1:1:0', { surfaceText: 'cat', status: 'rejected' }), + makeTokenLink('ta-2', 'GEN 1:1:4', { surfaceText: 'cat' }), ]); const result = reanchor(analysis, book); @@ -622,8 +633,8 @@ describe('reanchorAnalysisToBook', () => { // Written against "cat dog cat", at 0 and 8. const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'x cat dog cat' }]); const analysis = analysisWithTokenLinks([ - { ...makeTokenLink('GEN 1:1:0', 'cat'), status: 'rejected' }, - makeTokenLink('GEN 1:1:8', 'cat', 'ta-2'), + makeTokenLink('ta-1', 'GEN 1:1:0', { surfaceText: 'cat', status: 'rejected' }), + makeTokenLink('ta-2', 'GEN 1:1:8', { surfaceText: 'cat' }), ]); const once = reanchor(analysis, book); @@ -634,8 +645,8 @@ describe('reanchorAnalysisToBook', () => { // Written against "cat dog cat", at 0 and 8. const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'x cat dog cat' }]); const analysis = analysisWithTokenLinks([ - { ...makeTokenLink('GEN 1:1:0', 'cat'), status: 'stale' }, - makeTokenLink('GEN 1:1:8', 'cat', 'ta-2'), + makeTokenLink('ta-1', 'GEN 1:1:0', { surfaceText: 'cat', status: 'stale' }), + makeTokenLink('ta-2', 'GEN 1:1:8', { surfaceText: 'cat' }), ]); const result = reanchor(analysis, book); @@ -650,8 +661,8 @@ describe('reanchorAnalysisToBook', () => { // Written against "bank bank", at 0 and 5; one of the two was replaced. const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'bank shore' }]); const analysis = analysisWithTokenLinks([ - makeTokenLink('GEN 1:1:0', 'bank'), - { ...makeTokenLink('GEN 1:1:5', 'bank', 'ta-2'), status: 'candidate' }, + makeTokenLink('ta-1', 'GEN 1:1:0', { surfaceText: 'bank' }), + makeTokenLink('ta-2', 'GEN 1:1:5', { surfaceText: 'bank', status: 'candidate' }), ]); const once = reanchor(analysis, book); @@ -661,7 +672,7 @@ describe('reanchorAnalysisToBook', () => { it('returns a stale link to approved when its word comes back at a shifted ref', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'it and was unbelievable' }]); const analysis = analysisWithTokenLinks([ - { ...makeTokenLink('GEN 1:1:7', 'unbelievable'), status: 'stale' }, + makeTokenLink('ta-1', 'GEN 1:1:7', { surfaceText: 'unbelievable', status: 'stale' }), ]); const result = reanchor(analysis, book); @@ -674,7 +685,7 @@ describe('reanchorAnalysisToBook', () => { it('returns a stale link to approved when its word comes back at the same ref', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'it was unbelievable' }]); const analysis = analysisWithTokenLinks([ - { ...makeTokenLink('GEN 1:1:7', 'unbelievable'), status: 'stale' }, + makeTokenLink('ta-1', 'GEN 1:1:7', { surfaceText: 'unbelievable', status: 'stale' }), ]); const result = reanchor(analysis, book); @@ -686,8 +697,8 @@ describe('reanchorAnalysisToBook', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'it was unbelievable' }]); // A user who re-glossed the word while the old link was stale already owns the approval. const analysis = analysisWithTokenLinks([ - { ...makeTokenLink('GEN 1:1:7', 'unbelievable'), status: 'stale' }, - makeTokenLink('GEN 1:1:7', 'unbelievable', 'ta-2'), + makeTokenLink('ta-1', 'GEN 1:1:7', { surfaceText: 'unbelievable', status: 'stale' }), + makeTokenLink('ta-2', 'GEN 1:1:7', { surfaceText: 'unbelievable' }), ]); const result = reanchor(analysis, book); @@ -698,8 +709,8 @@ describe('reanchorAnalysisToBook', () => { it('revives only the earlier of two stale links that place on one token', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'it was unbelievable' }]); const analysis = analysisWithTokenLinks([ - { ...makeTokenLink('GEN 1:1:7', 'unbelievable'), status: 'stale' }, - { ...makeTokenLink('GEN 1:1:7', 'unbelievable', 'ta-2'), status: 'stale' }, + makeTokenLink('ta-1', 'GEN 1:1:7', { surfaceText: 'unbelievable', status: 'stale' }), + makeTokenLink('ta-2', 'GEN 1:1:7', { surfaceText: 'unbelievable', status: 'stale' }), ]); const result = reanchor(analysis, book); @@ -710,8 +721,8 @@ describe('reanchorAnalysisToBook', () => { it('leaves a stale link stale when the approval sharing its token shifts along with it', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'it was and unbelievable' }]); const analysis = analysisWithTokenLinks([ - { ...makeTokenLink('GEN 1:1:7', 'unbelievable'), status: 'stale' }, - makeTokenLink('GEN 1:1:7', 'unbelievable', 'ta-2'), + makeTokenLink('ta-1', 'GEN 1:1:7', { surfaceText: 'unbelievable', status: 'stale' }), + makeTokenLink('ta-2', 'GEN 1:1:7', { surfaceText: 'unbelievable' }), ]); const result = reanchor(analysis, book); @@ -722,8 +733,8 @@ describe('reanchorAnalysisToBook', () => { it('revives a stale link when the approval that held its token is itself staled', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'it was unbelievable' }]); const analysis = analysisWithTokenLinks([ - { ...makeTokenLink('GEN 1:1:7', 'unbelievable'), status: 'stale' }, - makeTokenLink('GEN 1:1:99', 'vanished', 'ta-2'), + makeTokenLink('ta-1', 'GEN 1:1:7', { surfaceText: 'unbelievable', status: 'stale' }), + makeTokenLink('ta-2', 'GEN 1:1:99', { surfaceText: 'vanished' }), ]); const result = reanchor(analysis, book); @@ -734,7 +745,7 @@ describe('reanchorAnalysisToBook', () => { it('leaves a placed link that was never stale at the status it had', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'it was unbelievable' }]); const analysis = analysisWithTokenLinks([ - { ...makeTokenLink('GEN 1:1:7', 'unbelievable'), status: 'suggested' }, + makeTokenLink('ta-1', 'GEN 1:1:7', { surfaceText: 'unbelievable', status: 'suggested' }), ]); const result = reanchor(analysis, book); @@ -747,8 +758,8 @@ describe('reanchorAnalysisToBook', () => { // History left a stale 'cat' and its replacement approval 'dog' on one ref. Neither matches the // live form there, so only each link's own stored text says which word it meant. const analysis = analysisWithTokenLinks([ - { ...makeTokenLink('GEN 1:1:0', 'cat'), status: 'stale' }, - makeTokenLink('GEN 1:1:0', 'dog', 'ta-2'), + makeTokenLink('ta-1', 'GEN 1:1:0', { surfaceText: 'cat', status: 'stale' }), + makeTokenLink('ta-2', 'GEN 1:1:0', { surfaceText: 'dog' }), ]); const result = reanchor(analysis, book); @@ -763,8 +774,8 @@ describe('reanchorAnalysisToBook', () => { // Written against "alpha the beta the", where the glossed pair sat at 6 and 15. A third "the" // appearing means a pairing was chosen rather than forced, so neither gloss may claim one. const analysis = analysisWithTokenLinks([ - makeTokenLink('GEN 1:1:6', 'the'), - makeTokenLink('GEN 1:1:15', 'the', 'ta-2'), + makeTokenLink('ta-1', 'GEN 1:1:6', { surfaceText: 'the' }), + makeTokenLink('ta-2', 'GEN 1:1:15', { surfaceText: 'the' }), ]); const result = reanchor(analysis, book); @@ -775,8 +786,8 @@ describe('reanchorAnalysisToBook', () => { it('places a repeated form when the verse holds exactly the occurrences it stored', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'and the light and the dark' }]); const analysis = analysisWithTokenLinks([ - makeTokenLink('GEN 1:1:0', 'the'), - makeTokenLink('GEN 1:1:16', 'the', 'ta-2'), + makeTokenLink('ta-1', 'GEN 1:1:0', { surfaceText: 'the' }), + makeTokenLink('ta-2', 'GEN 1:1:16', { surfaceText: 'the' }), ]); const result = reanchor(analysis, book); @@ -787,7 +798,7 @@ describe('reanchorAnalysisToBook', () => { it('leaves an orphaned rejected link rejected rather than staling its verdict', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'it was' }]); const analysis = analysisWithTokenLinks([ - { ...makeTokenLink('GEN 1:1:7', 'unbelievable'), status: 'rejected' }, + makeTokenLink('ta-1', 'GEN 1:1:7', { surfaceText: 'unbelievable', status: 'rejected' }), ]); const result = reanchor(analysis, book); @@ -798,7 +809,7 @@ describe('reanchorAnalysisToBook', () => { it('leaves a rejected link rejected when the word it names comes back', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'it was unbelievable' }]); const analysis = analysisWithTokenLinks([ - { ...makeTokenLink('GEN 1:1:7', 'unbelievable'), status: 'rejected' }, + makeTokenLink('ta-1', 'GEN 1:1:7', { surfaceText: 'unbelievable', status: 'rejected' }), ]); const result = reanchor(analysis, book); @@ -841,8 +852,8 @@ describe('reanchorAnalysisToBook', () => { // PT9's merger demotes a second approval to 'candidate' to keep one approval per token, so a // candidate still occupies the token its stale neighbor would otherwise revive onto. const analysis = analysisWithTokenLinks([ - { ...makeTokenLink('GEN 1:1:7', 'unbelievable'), status: 'stale' }, - { ...makeTokenLink('GEN 1:1:7', 'unbelievable', 'ta-2'), status: 'candidate' }, + makeTokenLink('ta-1', 'GEN 1:1:7', { surfaceText: 'unbelievable', status: 'stale' }), + makeTokenLink('ta-2', 'GEN 1:1:7', { surfaceText: 'unbelievable', status: 'candidate' }), ]); const result = reanchor(analysis, book); @@ -905,9 +916,9 @@ describe('reanchorAnalysisToBook', () => { const analysis: TextAnalysis = { ...emptyAnalysis(), tokenAnalysisLinks: [ - makeTokenLink('GEN 1:1:2', 'amen'), - { ...makeTokenLink('GEN 1:1:7', 'amen', 'ta-2'), status: 'candidate' }, - { ...makeTokenLink('GEN 1:1:12', 'amen', 'ta-3'), status: 'candidate' }, + makeTokenLink('ta-1', 'GEN 1:1:2', { surfaceText: 'amen' }), + makeTokenLink('ta-2', 'GEN 1:1:7', { surfaceText: 'amen', status: 'candidate' }), + makeTokenLink('ta-3', 'GEN 1:1:12', { surfaceText: 'amen', status: 'candidate' }), ], phraseAnalysisLinks: [ { @@ -942,14 +953,18 @@ describe('reanchorAnalysisToBook', () => { it('returns the original analysis reference when nothing moved', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'it was unbelievable' }]); - const analysis = analysisWithTokenLinks([makeTokenLink('GEN 1:1:7', 'unbelievable')]); + const analysis = analysisWithTokenLinks([ + makeTokenLink('ta-1', 'GEN 1:1:7', { surfaceText: 'unbelievable' }), + ]); expect(reanchor(analysis, book)).toBe(analysis); }); it('stamps a link it moves with the time of the re-anchor', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'it was and unbelievable' }]); - const analysis = analysisWithTokenLinks([makeTokenLink('GEN 1:1:7', 'unbelievable')]); + const analysis = analysisWithTokenLinks([ + makeTokenLink('ta-1', 'GEN 1:1:7', { surfaceText: 'unbelievable' }), + ]); const result = reanchor(analysis, book); @@ -958,7 +973,9 @@ describe('reanchorAnalysisToBook', () => { it('stamps a link it stales with the time of the re-anchor', () => { const book = makeVerseBook([{ sid: 'GEN 1:1', text: 'it was fine' }]); - const analysis = analysisWithTokenLinks([makeTokenLink('GEN 1:1:7', 'unbelievable')]); + const analysis = analysisWithTokenLinks([ + makeTokenLink('ta-1', 'GEN 1:1:7', { surfaceText: 'unbelievable' }), + ]); const result = reanchor(analysis, book);