@@ -20,12 +20,14 @@ describe('extractBookFromUsj', () => {
2020 expect ( extractBookFromUsj ( usj , 'kmr' ) . writingSystem ) . toBe ( 'kmr' ) ;
2121 } ) ;
2222
23- it ( 'produces a stable contentHash independent of writingSystem ' , ( ) => {
23+ it ( 'produces the same contentHash for identical content with different writingSystems ' , ( ) => {
2424 const a : UsjDocument = { content : [ { type : 'book' , code : 'GEN' , content : [ ] } ] } ;
2525 const b : UsjDocument = { content : [ ...a . content ] } ;
26- // Same content, different writingSystem — hash must be identical (it hashes content only).
2726 expect ( extractBookFromUsj ( a , 'en' ) . contentHash ) . toBe ( extractBookFromUsj ( b , 'es' ) . contentHash ) ;
28- // Different content must yield a different hash.
27+ } ) ;
28+
29+ it ( 'produces a different contentHash for different content' , ( ) => {
30+ const a : UsjDocument = { content : [ { type : 'book' , code : 'GEN' , content : [ ] } ] } ;
2931 const c : UsjDocument = { content : [ { type : 'book' , code : 'MAT' , content : [ ] } ] } ;
3032 expect ( extractBookFromUsj ( a , WS ) . contentHash ) . not . toBe ( extractBookFromUsj ( c , WS ) . contentHash ) ;
3133 } ) ;
@@ -263,6 +265,63 @@ describe('extractBookFromUsj', () => {
263265 expect ( verses [ 0 ] . text ) . toBe ( 'I have gone astray' ) ;
264266 } ) ;
265267
268+ it ( 'skips text inside a heading para marker that appears mid-verse (before the verse is closed)' , ( ) => {
269+ // An s1 heading node that arrives while a verse is still open must not contribute its text.
270+ const usj : UsjDocument = {
271+ content : [
272+ { type : 'book' , code : 'PSA' , content : [ ] } ,
273+ {
274+ type : 'para' ,
275+ marker : 'p' ,
276+ content : [ { type : 'verse' , sid : 'PSA 1:1' } , 'Blessed is the man' ] ,
277+ } ,
278+ // s1 heading arrives while PSA 1:1 is still the currentVerse
279+ { type : 'para' , marker : 's1' , content : [ 'Interlude' ] } ,
280+ {
281+ type : 'para' ,
282+ marker : 'p' ,
283+ content : [ 'who walks not in the counsel of the wicked.' ] ,
284+ } ,
285+ ] ,
286+ } ;
287+ const { verses } = extractBookFromUsj ( usj , WS ) ;
288+ expect ( verses ) . toHaveLength ( 1 ) ;
289+ expect ( verses [ 0 ] . text ) . toBe ( 'Blessed is the man who walks not in the counsel of the wicked.' ) ;
290+ } ) ;
291+
292+ it ( 'includes text nested inside multiple levels of inline char nodes' , ( ) => {
293+ // The traverse fallback recurses into any unknown node that has content, so deeply
294+ // nested char nodes must still contribute their text.
295+ const usj : UsjDocument = {
296+ content : [
297+ { type : 'book' , code : 'JHN' , content : [ ] } ,
298+ {
299+ type : 'para' ,
300+ marker : 'p' ,
301+ content : [
302+ { type : 'verse' , sid : 'JHN 1:14' } ,
303+ 'And the ' ,
304+ {
305+ type : 'char' ,
306+ marker : 'em' ,
307+ content : [
308+ {
309+ type : 'char' ,
310+ marker : 'nd' ,
311+ content : [ 'Word' ] ,
312+ } ,
313+ ] ,
314+ } ,
315+ ' became flesh.' ,
316+ ] ,
317+ } ,
318+ ] ,
319+ } ;
320+ const { verses } = extractBookFromUsj ( usj , WS ) ;
321+ expect ( verses ) . toHaveLength ( 1 ) ;
322+ expect ( verses [ 0 ] . text ) . toBe ( 'And the Word became flesh.' ) ;
323+ } ) ;
324+
266325 it ( 'produces a stable contentHash when a node has an optional property explicitly set to undefined' , ( ) => {
267326 const withUndefined : UsjDocument = {
268327 content : [ { type : 'book' , code : 'GEN' , marker : undefined , content : [ ] } ] ,
0 commit comments