@@ -2,7 +2,7 @@ import { Component, DestroyRef, NO_ERRORS_SCHEMA, ViewChild } from '@angular/cor
22import { ComponentFixture , fakeAsync , flush , TestBed , tick } from '@angular/core/testing' ;
33import { Delta } from 'quill' ;
44import { LynxInsightFilter , LynxInsightType } from 'realtime-server/lib/esm/scriptureforge/models/lynx-insight' ;
5- import { BehaviorSubject } from 'rxjs' ;
5+ import { BehaviorSubject , Subject } from 'rxjs' ;
66import { anything , instance , mock , verify , when } from 'ts-mockito' ;
77import { ActivatedBookChapterService , RouteBookChapter } from 'xforge-common/activated-book-chapter.service' ;
88import { configureTestingModule } from 'xforge-common/test-utils' ;
@@ -350,6 +350,76 @@ describe('LynxInsightEditorObjectsComponent', () => {
350350 verify ( mockInsightRenderService . removeAllInsightFormatting ( anything ( ) ) ) . atLeast ( 1 ) ;
351351 } ) ) ;
352352 } ) ;
353+
354+ describe ( 'embed position changes' , ( ) => {
355+ it ( 'should re-render insights when embed positions change' , fakeAsync ( ( ) => {
356+ // Start with editor not ready
357+ const env = new TestEnvironment ( { initialEditorReady : false } ) ;
358+ const testInsight = env . createTestInsight ( ) ;
359+
360+ env . setFilteredInsights ( [ testInsight ] ) ;
361+ tick ( ) ;
362+ flush ( ) ;
363+
364+ // No render calls, as editor not ready yet
365+ verify ( mockInsightRenderService . renderActionOverlay ( anything ( ) , anything ( ) , anything ( ) , anything ( ) ) ) . never ( ) ;
366+ verify ( mockInsightRenderService . render ( anything ( ) , anything ( ) ) ) . never ( ) ;
367+
368+ // Editor ready should trigger first render of insights and action overlay
369+ env . setEditorReady ( true ) ;
370+ tick ( ) ;
371+ flush ( ) ;
372+ verify ( mockInsightRenderService . render ( anything ( ) , anything ( ) ) ) . once ( ) ;
373+ verify ( mockInsightRenderService . renderActionOverlay ( anything ( ) , anything ( ) , anything ( ) , anything ( ) ) ) . once ( ) ;
374+
375+ // Embed position change should trigger second insights render, but not action overlay render
376+ env . triggerEmbedPositionChange ( ) ;
377+ tick ( env . component . embedPositionsChangedDebounceTime ) ;
378+ flush ( ) ;
379+ verify ( mockInsightRenderService . render ( anything ( ) , anything ( ) ) ) . twice ( ) ;
380+ verify ( mockInsightRenderService . renderActionOverlay ( anything ( ) , anything ( ) , anything ( ) , anything ( ) ) ) . once ( ) ;
381+ } ) ) ;
382+
383+ it ( 'should not reset overlay state when embed positions change' , fakeAsync ( ( ) => {
384+ // Start with editor not ready
385+ const env = new TestEnvironment ( { initialEditorReady : false } ) ;
386+ const testInsight = env . createTestInsight ( ) ;
387+
388+ env . setFilteredInsights ( [ testInsight ] ) ;
389+ tick ( ) ;
390+ flush ( ) ;
391+
392+ // No render calls, as editor not ready yet
393+ verify ( mockInsightRenderService . renderActionOverlay ( anything ( ) , anything ( ) , anything ( ) , anything ( ) ) ) . never ( ) ;
394+ verify ( mockInsightRenderService . render ( anything ( ) , anything ( ) ) ) . never ( ) ;
395+
396+ // Editor ready should trigger first render of insights and action overlay
397+ env . setEditorReady ( true ) ;
398+ tick ( ) ;
399+ flush ( ) ;
400+ verify ( mockInsightRenderService . render ( anything ( ) , anything ( ) ) ) . once ( ) ;
401+ verify ( mockInsightRenderService . renderActionOverlay ( anything ( ) , anything ( ) , anything ( ) , anything ( ) ) ) . once ( ) ;
402+
403+ // Set display state to trigger action overlay
404+ env . setDisplayState ( {
405+ activeInsightIds : [ testInsight . id ] ,
406+ actionOverlayActive : true ,
407+ promptActive : true ,
408+ cursorActiveInsightIds : [ ]
409+ } ) ;
410+ tick ( ) ;
411+ flush ( ) ;
412+ verify ( mockInsightRenderService . render ( anything ( ) , anything ( ) ) ) . once ( ) ; // No new insight render
413+ verify ( mockInsightRenderService . renderActionOverlay ( anything ( ) , anything ( ) , anything ( ) , anything ( ) ) ) . twice ( ) ;
414+
415+ // Embed position change should trigger second insights render, but not action overlay render
416+ env . triggerEmbedPositionChange ( ) ;
417+ tick ( env . component . embedPositionsChangedDebounceTime ) ;
418+ flush ( ) ;
419+ verify ( mockInsightRenderService . render ( anything ( ) , anything ( ) ) ) . twice ( ) ;
420+ verify ( mockInsightRenderService . renderActionOverlay ( anything ( ) , anything ( ) , anything ( ) , anything ( ) ) ) . twice ( ) ;
421+ } ) ) ;
422+ } ) ;
353423} ) ;
354424
355425@Component ( {
@@ -390,6 +460,7 @@ class TestEnvironment {
390460 private filterSubject : BehaviorSubject < LynxInsightFilter > ;
391461 private filteredInsightCountsByTypeSubject : BehaviorSubject < Record < LynxInsightType , number > > ;
392462 private taskRunningStatusSubject : BehaviorSubject < boolean > ;
463+ private embedPositionsChangedSubject : Subject < void > ;
393464
394465 constructor ( args : TestEnvArgs = { } ) {
395466 const textModelConverter = instance ( mockTextModelConverter ) ;
@@ -415,6 +486,8 @@ class TestEnvironment {
415486 this . filteredInsightCountsByTypeSubject = new BehaviorSubject < any > ( { info : 0 , warning : 0 , error : 0 } ) ;
416487 this . taskRunningStatusSubject = new BehaviorSubject < boolean > ( false ) ;
417488
489+ this . embedPositionsChangedSubject = new Subject < void > ( ) ;
490+
418491 // Create mock editor
419492 const mockRoot = document . createElement ( 'div' ) ;
420493 const actualEditor = {
@@ -460,6 +533,7 @@ class TestEnvironment {
460533 when ( mockOverlayService . isOpen ) . thenReturn ( false ) ;
461534 when ( mockLynxWorkspaceService . getOnTypeEdits ( anything ( ) , anything ( ) ) ) . thenResolve ( [ ] ) ;
462535 when ( mockTextModelConverter . dataDeltaToEditorDelta ( anything ( ) ) ) . thenCall ( ( delta : Delta ) => delta ) ;
536+ when ( mockTextModelConverter . embedPositionsChanged$ ) . thenReturn ( this . embedPositionsChangedSubject ) ;
463537
464538 // Setup text model converter to return ranges as-is (prevents null range issues)
465539 when ( mockTextModelConverter . dataRangeToEditorRange ( anything ( ) ) ) . thenCall ( ( range : LynxInsightRange ) => range ) ;
@@ -518,6 +592,10 @@ class TestEnvironment {
518592 }
519593 }
520594
595+ triggerEmbedPositionChange ( ) : void {
596+ this . embedPositionsChangedSubject . next ( ) ;
597+ }
598+
521599 createTestInsight ( props : Partial < LynxInsight > = { } ) : LynxInsight {
522600 return {
523601 id : props . id ?? 'test-insight-1' ,
0 commit comments