Skip to content

Commit 338010d

Browse files
Update/add tests
1 parent 2833e40 commit 338010d

22 files changed

Lines changed: 2558 additions & 5 deletions

src/__tests__/components/AnalysisStore.test.tsx

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import {
1313
usePhraseLinkForToken,
1414
usePhraseLinkMap,
1515
usePhraseDispatch,
16+
usePhraseGloss,
17+
usePhraseGlossDispatch,
1618
} from '../../components/AnalysisStore';
1719

1820
// ---------------------------------------------------------------------------
@@ -591,3 +593,133 @@ describe('usePhraseDispatch', () => {
591593
);
592594
});
593595
});
596+
597+
// ---------------------------------------------------------------------------
598+
// usePhraseGloss
599+
// ---------------------------------------------------------------------------
600+
601+
/**
602+
* Renders the phrase gloss for a given phraseId, used to assert on `usePhraseGloss`.
603+
*
604+
* @param props - Component props.
605+
* @param props.phraseId - Phrase id to look up.
606+
* @returns JSX element.
607+
*/
608+
function PhraseGlossReader({ phraseId }: Readonly<{ phraseId: string }>) {
609+
const gloss = usePhraseGloss(phraseId);
610+
return <span data-testid="phrase-gloss">{gloss}</span>;
611+
}
612+
613+
/**
614+
* Renders a component that calls `usePhraseGloss` without a provider, to assert it throws.
615+
*
616+
* @returns Nothing — only mounted to trigger the throw.
617+
*/
618+
function PhraseGlossUser() {
619+
usePhraseGloss('p1');
620+
return undefined;
621+
}
622+
623+
/** A `TextAnalysis` with a phrase that has a gloss in the `'und'` language. */
624+
const PHRASE_ANALYSIS_WITH_GLOSS: TextAnalysis = {
625+
segmentAnalyses: [],
626+
segmentAnalysisLinks: [],
627+
tokenAnalyses: [],
628+
tokenAnalysisLinks: [],
629+
phraseAnalyses: [
630+
{ id: 'phrase-1', surfaceText: 'Hello World', gloss: { und: 'world beginning' } },
631+
],
632+
phraseAnalysisLinks: [
633+
{
634+
analysisId: 'phrase-1',
635+
status: 'approved',
636+
tokens: [{ tokenRef: 'tok-a', surfaceText: 'Hello' }],
637+
},
638+
],
639+
};
640+
641+
describe('usePhraseGloss', () => {
642+
it('returns empty string when phraseId is not found', () => {
643+
render(
644+
<AnalysisStoreProvider analysisLanguage="und">
645+
<PhraseGlossReader phraseId="missing" />
646+
</AnalysisStoreProvider>,
647+
);
648+
expect(screen.getByTestId('phrase-gloss')).toHaveTextContent('');
649+
});
650+
651+
it('returns the gloss for the active analysis language', () => {
652+
render(
653+
<AnalysisStoreProvider initialAnalysis={PHRASE_ANALYSIS_WITH_GLOSS} analysisLanguage="und">
654+
<PhraseGlossReader phraseId="phrase-1" />
655+
</AnalysisStoreProvider>,
656+
);
657+
expect(screen.getByTestId('phrase-gloss')).toHaveTextContent('world beginning');
658+
});
659+
660+
it('throws when called outside an AnalysisStoreProvider', () => {
661+
jest.spyOn(console, 'error').mockImplementation(() => {});
662+
expect(() => render(<PhraseGlossUser />)).toThrow(
663+
'usePhraseGloss must be used inside an AnalysisStoreProvider',
664+
);
665+
});
666+
});
667+
668+
// ---------------------------------------------------------------------------
669+
// usePhraseGlossDispatch
670+
// ---------------------------------------------------------------------------
671+
672+
/**
673+
* Renders a button that writes a phrase gloss via `usePhraseGlossDispatch`.
674+
*
675+
* @param props - Component props.
676+
* @param props.phraseId - Phrase id to write.
677+
* @param props.value - Gloss value to write.
678+
* @returns JSX element.
679+
*/
680+
function PhraseGlossWriter({ phraseId, value }: Readonly<{ phraseId: string; value: string }>) {
681+
const dispatch = usePhraseGlossDispatch();
682+
return (
683+
<button onClick={() => dispatch(phraseId, value)} type="button">
684+
write
685+
</button>
686+
);
687+
}
688+
689+
/**
690+
* Renders a component that calls `usePhraseGlossDispatch` without a provider, to assert it throws.
691+
*
692+
* @returns Nothing — only mounted to trigger the throw.
693+
*/
694+
function PhraseGlossDispatchUser() {
695+
usePhraseGlossDispatch();
696+
return undefined;
697+
}
698+
699+
describe('usePhraseGlossDispatch', () => {
700+
it('writes the phrase gloss and triggers onSave', async () => {
701+
const onSave = jest.fn();
702+
render(
703+
<AnalysisStoreProvider
704+
initialAnalysis={PHRASE_ANALYSIS}
705+
analysisLanguage="und"
706+
onSave={onSave}
707+
>
708+
<PhraseGlossWriter phraseId="phrase-1" value="beginning" />
709+
</AnalysisStoreProvider>,
710+
);
711+
712+
await userEvent.click(screen.getByRole('button', { name: 'write' }));
713+
714+
expect(onSave).toHaveBeenCalledTimes(1);
715+
const saved: TextAnalysis = onSave.mock.calls[0][0];
716+
expect(saved.phraseAnalyses[0].gloss).toStrictEqual({ und: 'beginning' });
717+
});
718+
719+
it('throws when called outside an AnalysisStoreProvider', () => {
720+
jest.spyOn(console, 'error').mockImplementation(() => {});
721+
expect(() => render(<PhraseGlossDispatchUser />)).toThrow(
722+
'usePhraseGlossDispatch must be used inside an AnalysisStoreProvider',
723+
);
724+
});
725+
});

0 commit comments

Comments
 (0)