44
55import type { SerializedVerseRef } from '@sillsdev/scripture' ;
66import { render , screen } from '@testing-library/react' ;
7- import userEvent from '@testing-library/user-event' ;
8- import type { Book } from 'interlinearizer' ;
7+ import type { Book , Segment } from 'interlinearizer' ;
98import Interlinearizer from '../../components/Interlinearizer' ;
109
11- // Store captured props so tests can simulate callbacks
10+ // Store captured props so tests can inspect what Interlinearizer passes down
1211let capturedContinuousViewProps : Record < string , unknown > = { } ;
1312
13+ type CapturedSegmentViewProps = {
14+ segment : Segment ;
15+ displayMode ?: string ;
16+ isActive ?: boolean ;
17+ onClick ?: ( ref : { book : string ; chapter : number ; verse : number } ) => void ;
18+ } ;
19+ let capturedSegmentViewPropsList : CapturedSegmentViewProps [ ] = [ ] ;
20+
1421jest . mock ( '../../components/ContinuousView' , ( ) => ( {
1522 __esModule : true ,
1623 default : ( props : Record < string , unknown > ) => {
@@ -19,6 +26,18 @@ jest.mock('../../components/ContinuousView', () => ({
1926 } ,
2027} ) ) ;
2128
29+ jest . mock ( '../../components/SegmentView' , ( ) => ( {
30+ __esModule : true ,
31+ SegmentView : ( { segment, ...rest } : CapturedSegmentViewProps ) => {
32+ capturedSegmentViewPropsList . push ( { segment, ...rest } ) ;
33+ return < div data-testid = "segment-view" data-segment-id = { segment . id } /> ;
34+ } ,
35+ default : ( { segment, ...rest } : CapturedSegmentViewProps ) => {
36+ capturedSegmentViewPropsList . push ( { segment, ...rest } ) ;
37+ return < div data-testid = "segment-view" data-segment-id = { segment . id } /> ;
38+ } ,
39+ } ) ) ;
40+
2241const defaultScrRef : SerializedVerseRef = { book : 'GEN' , chapterNum : 1 , verseNum : 1 } ;
2342
2443/** Pre-built Book with one GEN 1:1 segment. */
@@ -90,31 +109,13 @@ const GEN_1_MULTI_BOOK: Book = {
90109 ] ,
91110} ;
92111
93- /** Book with a non-word (punctuation) token — exercises the non-word chip branch. */
94- const GEN_1_1_PUNCTUATION_BOOK : Book = {
95- id : 'GEN' ,
96- bookRef : 'GEN' ,
97- textVersion : 'v1' ,
98- segments : [
99- {
100- id : 'GEN 1:1' ,
101- startRef : { book : 'GEN' , chapter : 1 , verse : 1 } ,
102- endRef : { book : 'GEN' , chapter : 1 , verse : 1 } ,
103- baselineText : '.' ,
104- tokens : [
105- {
106- id : 'GEN 1:1:0' ,
107- surfaceText : '.' ,
108- writingSystem : 'en' ,
109- type : 'punctuation' ,
110- charStart : 0 ,
111- charEnd : 1 ,
112- } ,
113- ] ,
114- } ,
115- ] ,
116- } ;
117-
112+ /**
113+ * Renders an Interlinearizer component with sensible defaults, allowing individual props to be
114+ * overridden per test.
115+ *
116+ * @param options - Partial props to merge over the defaults.
117+ * @returns The render result from @testing-library/react.
118+ */
118119function renderInterlinearizer ( {
119120 book = GEN_1_1_BOOK ,
120121 bookSegments = GEN_1_1_BOOK . segments ,
@@ -142,12 +143,13 @@ function renderInterlinearizer({
142143describe ( 'Interlinearizer' , ( ) => {
143144 beforeEach ( ( ) => {
144145 capturedContinuousViewProps = { } ;
146+ capturedSegmentViewPropsList = [ ] ;
145147 } ) ;
146148
147- it ( 'renders token chips when the tokenized book has a segment for the current reference' , ( ) => {
149+ it ( 'renders a SegmentView when the tokenized book has a segment for the current reference' , ( ) => {
148150 renderInterlinearizer ( ) ;
149151
150- expect ( screen . getByText ( 'In ') ) . toBeInTheDocument ( ) ;
152+ expect ( screen . getAllByTestId ( 'segment-view ') ) . toHaveLength ( 1 ) ;
151153 } ) ;
152154
153155 it ( 'shows a no-verse message when the tokenized book has no segments at all' , ( ) => {
@@ -156,56 +158,48 @@ describe('Interlinearizer', () => {
156158 expect ( screen . getByText ( / n o v e r s e d a t a f o r g e n 1 \. / i) ) . toBeInTheDocument ( ) ;
157159 } ) ;
158160
159- it ( 'renders all segments in the current chapter' , ( ) => {
161+ it ( 'renders a SegmentView for every segment in the current chapter' , ( ) => {
160162 renderInterlinearizer ( { bookSegments : GEN_1_MULTI_BOOK . segments } ) ;
161163
162- expect ( screen . getByText ( 'In' ) ) . toBeInTheDocument ( ) ;
163- expect ( screen . getByText ( 'And' ) ) . toBeInTheDocument ( ) ;
164+ expect ( screen . getAllByTestId ( 'segment-view' ) ) . toHaveLength ( 2 ) ;
165+ expect ( capturedSegmentViewPropsList [ 0 ] . segment . id ) . toBe ( 'GEN 1:1' ) ;
166+ expect ( capturedSegmentViewPropsList [ 1 ] . segment . id ) . toBe ( 'GEN 1:2' ) ;
164167 } ) ;
165168
166- it ( 'highlights only the segment matching the current verse' , ( ) => {
167- const { container } = renderInterlinearizer ( { bookSegments : GEN_1_MULTI_BOOK . segments } ) ;
169+ it ( 'passes isActive=true only to the segment matching the current verse' , ( ) => {
170+ renderInterlinearizer ( { bookSegments : GEN_1_MULTI_BOOK . segments } ) ;
168171
169- // defaultScrRef is GEN 1:1, so verse 1 is active
170- const activeSegments = container . querySelectorAll ( 'button[aria-current="true"]' ) ;
171- expect ( activeSegments ) . toHaveLength ( 1 ) ;
172+ // defaultScrRef is GEN 1:1
173+ expect ( capturedSegmentViewPropsList [ 0 ] . isActive ) . toBe ( true ) ;
174+ expect ( capturedSegmentViewPropsList [ 1 ] . isActive ) . toBeFalsy ( ) ;
172175 } ) ;
173176
174- it ( 'shows all chapter segments when navigating to a title reference (verse 0)' , ( ) => {
177+ it ( 'renders all segments when navigating to a title reference (verse 0)' , ( ) => {
175178 const titleRef : SerializedVerseRef = { book : 'GEN' , chapterNum : 1 , verseNum : 0 } ;
176179 renderInterlinearizer ( { bookSegments : GEN_1_MULTI_BOOK . segments , scrRef : titleRef } ) ;
177180
178- expect ( screen . getByText ( 'In' ) ) . toBeInTheDocument ( ) ;
179- expect ( screen . getByText ( 'And' ) ) . toBeInTheDocument ( ) ;
180- } ) ;
181-
182- it ( 'renders non-word tokens as muted chips' , ( ) => {
183- renderInterlinearizer ( { bookSegments : GEN_1_1_PUNCTUATION_BOOK . segments } ) ;
184-
185- expect ( screen . getByText ( '.' ) ) . toBeInTheDocument ( ) ;
181+ expect ( screen . getAllByTestId ( 'segment-view' ) ) . toHaveLength ( 2 ) ;
186182 } ) ;
187183
188- it ( 'calls setScrRef with the segment ref when a verse box is clicked' , async ( ) => {
184+ it ( 'calls setScrRef with the segment ref when a verse box is clicked' , ( ) => {
189185 const mockSetScrRef = jest . fn ( ) ;
190186 renderInterlinearizer ( { bookSegments : GEN_1_MULTI_BOOK . segments , setScrRef : mockSetScrRef } ) ;
191187
192- await userEvent . click ( screen . getByText ( 'And' ) ) ;
188+ capturedSegmentViewPropsList [ 1 ] . onClick ?. ( { book : 'GEN' , chapter : 1 , verse : 2 } ) ;
193189
194190 expect ( mockSetScrRef ) . toHaveBeenCalledWith ( { book : 'GEN' , chapterNum : 1 , verseNum : 2 } ) ;
195191 } ) ;
196192
197- it ( 'renders segments in baseline-text mode when continuousScroll is true' , ( ) => {
193+ it ( 'passes displayMode=" baseline-text" to SegmentView when continuousScroll is true' , ( ) => {
198194 renderInterlinearizer ( { continuousScroll : true } ) ;
199195
200- expect ( screen . getByText ( 'In the beginning.' ) ) . toBeInTheDocument ( ) ;
201- expect ( screen . queryByText ( 'In' ) ) . not . toBeInTheDocument ( ) ;
196+ expect ( capturedSegmentViewPropsList [ 0 ] . displayMode ) . toBe ( 'baseline-text' ) ;
202197 } ) ;
203198
204- it ( 'renders all chapter segments in baseline-text mode when continuousScroll is true' , ( ) => {
199+ it ( 'passes displayMode=" baseline-text" to all SegmentViews when continuousScroll is true' , ( ) => {
205200 renderInterlinearizer ( { bookSegments : GEN_1_MULTI_BOOK . segments , continuousScroll : true } ) ;
206201
207- expect ( screen . getByText ( 'In the beginning.' ) ) . toBeInTheDocument ( ) ;
208- expect ( screen . getByText ( 'And the earth.' ) ) . toBeInTheDocument ( ) ;
202+ capturedSegmentViewPropsList . forEach ( ( p ) => expect ( p . displayMode ) . toBe ( 'baseline-text' ) ) ;
209203 } ) ;
210204
211205 it ( 'renders ContinuousView when continuousScroll is true' , ( ) => {
@@ -228,7 +222,7 @@ describe('Interlinearizer', () => {
228222
229223 const continuousView = screen . getByTestId ( 'continuous-view' ) ;
230224 const allElements = Array . from (
231- container . querySelectorAll ( '[data-testid="continuous-view"], button[aria-current ]' ) ,
225+ container . querySelectorAll ( '[data-testid="continuous-view"], [data-testid="segment-view" ]' ) ,
232226 ) ;
233227 expect ( allElements [ 0 ] ) . toBe ( continuousView ) ;
234228 } ) ;
@@ -239,9 +233,9 @@ describe('Interlinearizer', () => {
239233
240234 expect ( screen . getByTestId ( 'continuous-view' ) ) . toBeInTheDocument ( ) ;
241235
242- // eslint-disable-next-line @typescript-eslint/no-explicit-any, no-type-assertion/no-type-assertion
243- const onVerseChange = capturedContinuousViewProps . onVerseChange as any ;
244- expect ( onVerseChange ) . toBeDefined ( ) ;
236+ const { onVerseChange } = capturedContinuousViewProps ;
237+ if ( typeof onVerseChange !== 'function' )
238+ throw new Error ( 'Expected onVerseChange to be a function' ) ;
245239
246240 onVerseChange ( { book : 'GEN' , chapter : 2 , verse : 3 } ) ;
247241
0 commit comments