44import { useLocalizedStrings } from '@papi/frontend/react' ;
55import { fireEvent , render , screen } from '@testing-library/react' ;
66import userEvent from '@testing-library/user-event' ;
7+ import type { MorphemeAnalysis } from 'interlinearizer' ;
78import type { ComponentProps } from 'react' ;
89import { MorphemeBreakdownPopover } from '../../components/MorphemeEditor' ;
910
@@ -17,9 +18,17 @@ const LOCALIZED = {
1718 '%interlinearizer_morphemeEditor_emptyHint%' : 'Enter morpheme forms separated by spaces' ,
1819 '%interlinearizer_morphemeEditor_confirmResetPrompt%' : 'Discard this breakdown and its glosses?' ,
1920 '%interlinearizer_morphemeEditor_confirmResetAction%' : 'Reset' ,
21+ '%interlinearizer_morphemeEditor_confirmResplitPrompt%' :
22+ 'This breakdown drops {forms}, discarding the glosses on it. Save anyway?' ,
23+ '%interlinearizer_morphemeEditor_confirmResplitAction%' : 'Save and discard' ,
2024 '%interlinearizer_morphemeGloss_label%' : 'Gloss for morpheme {form}' ,
2125} ;
2226
27+ /** A morpheme carrying a gloss, so dropping it is the loss a re-split confirms over. */
28+ function glossed ( id : string , form : string ) : MorphemeAnalysis {
29+ return { id, form, writingSystem : 'und' , gloss : { und : form } } ;
30+ }
31+
2332beforeEach ( ( ) => {
2433 jest . mocked ( useLocalizedStrings ) . mockReturnValue ( [ LOCALIZED , false ] ) ;
2534} ) ;
@@ -450,6 +459,110 @@ describe('MorphemeBreakdownPopover', () => {
450459 } ) ;
451460 } ) ;
452461
462+ describe ( 're-split confirmation' , ( ) => {
463+ /**
464+ * Renders the popover over a glossed breakdown of "unbelievable" that this token solely owns,
465+ * so a re-split dropping any of its forms destroys that form's gloss outright.
466+ */
467+ function renderResplitting (
468+ props : Partial < ComponentProps < typeof MorphemeBreakdownPopover > > = { } ,
469+ ) {
470+ return renderPopover ( {
471+ initialValue : 'un- believ -able' ,
472+ morphemes : [ glossed ( 'm-1' , 'un-' ) , glossed ( 'm-2' , 'believ' ) , glossed ( 'm-3' , '-able' ) ] ,
473+ onReset : jest . fn ( ) ,
474+ surfaceText : 'unbelievable' ,
475+ ...props ,
476+ } ) ;
477+ }
478+
479+ /** Replaces the draft with `value` and commits it. */
480+ async function commit ( value : string ) {
481+ await userEvent . clear ( screen . getByRole ( 'textbox' ) ) ;
482+ await userEvent . type ( screen . getByRole ( 'textbox' ) , value ) ;
483+ await userEvent . keyboard ( '{Enter}' ) ;
484+ }
485+
486+ it ( 'asks before a re-split that strands a glossed form' , async ( ) => {
487+ const onSave = jest . fn ( ) ;
488+ const onClose = jest . fn ( ) ;
489+ renderResplitting ( { onSave, onClose } ) ;
490+ await commit ( 'un- believe' ) ;
491+ expect ( screen . getByTestId ( 'morpheme-split-confirm' ) ) . toBeInTheDocument ( ) ;
492+ expect ( onSave ) . not . toHaveBeenCalled ( ) ;
493+ expect ( onClose ) . not . toHaveBeenCalled ( ) ;
494+ } ) ;
495+
496+ it ( 'names the stranded forms in the prompt' , async ( ) => {
497+ renderResplitting ( ) ;
498+ await commit ( 'un- believe' ) ;
499+ expect ( screen . getByTestId ( 'morpheme-split-confirm' ) ) . toHaveTextContent (
500+ 'This breakdown drops believ, -able, discarding the glosses on it. Save anyway?' ,
501+ ) ;
502+ } ) ;
503+
504+ it ( 'saves and closes when the confirmation is accepted' , async ( ) => {
505+ const onSave = jest . fn ( ) ;
506+ const onClose = jest . fn ( ) ;
507+ renderResplitting ( { onSave, onClose } ) ;
508+ await commit ( 'un- believe' ) ;
509+ await userEvent . click ( screen . getByTestId ( 'morpheme-split-confirm-action' ) ) ;
510+ expect ( onSave ) . toHaveBeenCalledWith ( 'un- believe' ) ;
511+ expect ( onClose ) . toHaveBeenCalledTimes ( 1 ) ;
512+ } ) ;
513+
514+ it ( 'returns to the draft when the confirmation is canceled' , async ( ) => {
515+ const onSave = jest . fn ( ) ;
516+ renderResplitting ( { onSave } ) ;
517+ await commit ( 'un- believe' ) ;
518+ await userEvent . click ( screen . getByRole ( 'button' , { name : 'Cancel' } ) ) ;
519+ expect ( screen . queryByTestId ( 'morpheme-split-confirm' ) ) . not . toBeInTheDocument ( ) ;
520+ expect ( screen . getByRole ( 'textbox' ) ) . toHaveValue ( 'un- believe' ) ;
521+ expect ( onSave ) . not . toHaveBeenCalled ( ) ;
522+ } ) ;
523+
524+ it ( 'leaves a pending re-split unwritten when the user presses outside the panel' , async ( ) => {
525+ // The same reasoning as the reset confirmation: the loss is irreversible, so a stray click
526+ // must not answer the prompt, even though an outside press on an edited draft normally saves.
527+ const onSave = jest . fn ( ) ;
528+ const onClose = jest . fn ( ) ;
529+ renderResplitting ( { onSave, onClose } ) ;
530+ await commit ( 'un- believe' ) ;
531+ await userEvent . click ( screen . getByTestId ( 'popover-outside' ) ) ;
532+ expect ( onSave ) . not . toHaveBeenCalled ( ) ;
533+ expect ( onClose ) . toHaveBeenCalledTimes ( 1 ) ;
534+ } ) ;
535+
536+ it ( 'saves without asking when the re-split keeps every glossed form' , async ( ) => {
537+ const onSave = jest . fn ( ) ;
538+ renderResplitting ( { onSave } ) ;
539+ await commit ( 'un- believ -able -ness' ) ;
540+ expect ( screen . queryByTestId ( 'morpheme-split-confirm' ) ) . not . toBeInTheDocument ( ) ;
541+ expect ( onSave ) . toHaveBeenCalledWith ( 'un- believ -able -ness' ) ;
542+ } ) ;
543+
544+ it ( 'saves without asking when the stranded form carried no gloss' , async ( ) => {
545+ const onSave = jest . fn ( ) ;
546+ renderResplitting ( {
547+ morphemes : [ glossed ( 'm-1' , 'un-' ) , { id : 'm-2' , form : 'believ' , writingSystem : 'und' } ] ,
548+ onSave,
549+ } ) ;
550+ await commit ( 'un- believe' ) ;
551+ expect ( screen . queryByTestId ( 'morpheme-split-confirm' ) ) . not . toBeInTheDocument ( ) ;
552+ expect ( onSave ) . toHaveBeenCalledWith ( 'un- believe' ) ;
553+ } ) ;
554+
555+ it ( 'saves without asking when the payload is shared, its morphemes withheld' , async ( ) => {
556+ // A shared payload is forked rather than re-segmented in place, so the co-linked tokens keep
557+ // the glosses this token drops and there is nothing to confirm.
558+ const onSave = jest . fn ( ) ;
559+ renderResplitting ( { morphemes : undefined , onSave } ) ;
560+ await commit ( 'un- believe' ) ;
561+ expect ( screen . queryByTestId ( 'morpheme-split-confirm' ) ) . not . toBeInTheDocument ( ) ;
562+ expect ( onSave ) . toHaveBeenCalledWith ( 'un- believe' ) ;
563+ } ) ;
564+ } ) ;
565+
453566 it ( 'falls back to the token gloss input on close when the chip has no morpheme gloss field' , async ( ) => {
454567 render (
455568 < label >
0 commit comments