11import { TestBed } from '@angular/core/testing' ;
22import { LynxInsightFilter } from 'realtime-server/lib/esm/scriptureforge/models/lynx-insight' ;
3+ import { TextInfo } from 'realtime-server/lib/esm/scriptureforge/models/text-info' ;
4+ import { anything , mock , verify , when } from 'ts-mockito' ;
35import { RouteBookChapter } from 'xforge-common/activated-book-chapter.service' ;
46import { configureTestingModule } from 'xforge-common/test-utils' ;
57import { TextDocId } from '../../../../core/models/text-doc' ;
8+ import { TextDocService } from '../../../../core/text-doc.service' ;
69import { LynxInsight } from './lynx-insight' ;
710import { LynxInsightFilterService } from './lynx-insight-filter.service' ;
811
912describe ( 'LynxInsightFilterService' , ( ) => {
1013 let service : LynxInsightFilterService ;
14+ const mockTextDocService = mock ( TextDocService ) ;
1115
1216 function createMockInsight (
1317 type : 'info' | 'warning' | 'error' = 'warning' ,
@@ -25,11 +29,24 @@ describe('LynxInsightFilterService', () => {
2529 } ;
2630 }
2731
32+ function createMockTextInfo ( bookNum : number = 40 ) : TextInfo {
33+ return {
34+ bookNum,
35+ hasSource : false ,
36+ chapters : [ ] ,
37+ permissions : { }
38+ } ;
39+ }
40+
2841 configureTestingModule ( ( ) => ( {
29- providers : [ LynxInsightFilterService ]
42+ providers : [ LynxInsightFilterService , { provide : TextDocService , useMock : mockTextDocService } ]
3043 } ) ) ;
3144
3245 beforeEach ( ( ) => {
46+ // Set up default mocks
47+ when ( mockTextDocService . hasChapterEditPermissionForText ( anything ( ) , anything ( ) ) ) . thenReturn ( true ) ;
48+ when ( mockTextDocService . isUsfmValidForText ( anything ( ) , anything ( ) ) ) . thenReturn ( true ) ;
49+
3350 service = TestBed . inject ( LynxInsightFilterService ) ;
3451 } ) ;
3552
@@ -182,5 +199,184 @@ describe('LynxInsightFilterService', () => {
182199
183200 expect ( result ) . toBe ( 'project' ) ;
184201 } ) ;
202+
203+ it ( 'should return false when permissions check fails' , ( ) => {
204+ const insight = createMockInsight ( ) ;
205+ const filter : LynxInsightFilter = {
206+ types : [ 'warning' ] ,
207+ scope : 'project' ,
208+ includeDismissed : true
209+ } ;
210+ const bookChapter : RouteBookChapter = { bookId : 'MAT' , chapter : 1 } ;
211+ const dismissedIds : string [ ] = [ ] ;
212+ const projectTexts : TextInfo [ ] = [ createMockTextInfo ( ) ] ;
213+
214+ // Mock permission check to return false
215+ when ( mockTextDocService . hasChapterEditPermissionForText ( anything ( ) , anything ( ) ) ) . thenReturn ( false ) ;
216+
217+ const result = service . matchesFilter ( insight , filter , bookChapter , dismissedIds , projectTexts ) ;
218+
219+ expect ( result ) . toBeFalse ( ) ;
220+ } ) ;
221+
222+ it ( 'should return false when USFM validity check fails' , ( ) => {
223+ const insight = createMockInsight ( ) ;
224+ const filter : LynxInsightFilter = {
225+ types : [ 'warning' ] ,
226+ scope : 'project' ,
227+ includeDismissed : true
228+ } ;
229+ const bookChapter : RouteBookChapter = { bookId : 'MAT' , chapter : 1 } ;
230+ const dismissedIds : string [ ] = [ ] ;
231+ const projectTexts : TextInfo [ ] = [ createMockTextInfo ( ) ] ;
232+
233+ // Mock USFM validity check to return false
234+ when ( mockTextDocService . isUsfmValidForText ( anything ( ) , anything ( ) ) ) . thenReturn ( false ) ;
235+
236+ const result = service . matchesFilter ( insight , filter , bookChapter , dismissedIds , projectTexts ) ;
237+
238+ expect ( result ) . toBeFalse ( ) ;
239+ } ) ;
240+
241+ it ( 'should return false when book not found in project texts' , ( ) => {
242+ const insight = createMockInsight ( 'warning' , 42 ) ; // Luke
243+ const filter : LynxInsightFilter = {
244+ types : [ 'warning' ] ,
245+ scope : 'project' ,
246+ includeDismissed : true
247+ } ;
248+ const bookChapter : RouteBookChapter = { bookId : 'MAT' , chapter : 1 } ;
249+ const dismissedIds : string [ ] = [ ] ;
250+ const projectTexts : TextInfo [ ] = [ createMockTextInfo ( 40 ) ] ; // Only Matthew
251+
252+ const result = service . matchesFilter ( insight , filter , bookChapter , dismissedIds , projectTexts ) ;
253+
254+ expect ( result ) . toBeFalse ( ) ;
255+ } ) ;
256+
257+ it ( 'should return true when permissions check passes' , ( ) => {
258+ const insight = createMockInsight ( ) ;
259+ const filter : LynxInsightFilter = {
260+ types : [ 'warning' ] ,
261+ scope : 'project' ,
262+ includeDismissed : true
263+ } ;
264+ const bookChapter : RouteBookChapter = { bookId : 'MAT' , chapter : 1 } ;
265+ const dismissedIds : string [ ] = [ ] ;
266+ const projectTexts : TextInfo [ ] = [ createMockTextInfo ( ) ] ;
267+
268+ // Mock permission checks to return true (default setup)
269+ when ( mockTextDocService . hasChapterEditPermissionForText ( anything ( ) , anything ( ) ) ) . thenReturn ( true ) ;
270+ when ( mockTextDocService . isUsfmValidForText ( anything ( ) , anything ( ) ) ) . thenReturn ( true ) ;
271+
272+ const result = service . matchesFilter ( insight , filter , bookChapter , dismissedIds , projectTexts ) ;
273+
274+ expect ( result ) . toBeTrue ( ) ;
275+ } ) ;
276+
277+ it ( 'should return true when projectTexts is undefined (no permission filtering)' , ( ) => {
278+ const insight = createMockInsight ( ) ;
279+ const filter : LynxInsightFilter = {
280+ types : [ 'warning' ] ,
281+ scope : 'project' ,
282+ includeDismissed : true
283+ } ;
284+ const bookChapter : RouteBookChapter = { bookId : 'MAT' , chapter : 1 } ;
285+ const dismissedIds : string [ ] = [ ] ;
286+
287+ const result = service . matchesFilter ( insight , filter , bookChapter , dismissedIds ) ;
288+
289+ expect ( result ) . toBeTrue ( ) ;
290+ } ) ;
291+
292+ it ( 'should call permission methods with correct parameters when projectTexts provided' , ( ) => {
293+ const insight = createMockInsight ( 'warning' , 40 , 1 ) ;
294+ const filter : LynxInsightFilter = {
295+ types : [ 'warning' ] ,
296+ scope : 'project' ,
297+ includeDismissed : true
298+ } ;
299+ const bookChapter : RouteBookChapter = { bookId : 'MAT' , chapter : 1 } ;
300+ const dismissedIds : string [ ] = [ ] ;
301+ const projectTexts : TextInfo [ ] = [ createMockTextInfo ( 40 ) ] ;
302+
303+ when ( mockTextDocService . hasChapterEditPermissionForText ( anything ( ) , anything ( ) ) ) . thenReturn ( true ) ;
304+ when ( mockTextDocService . isUsfmValidForText ( anything ( ) , anything ( ) ) ) . thenReturn ( true ) ;
305+
306+ const result = service . matchesFilter ( insight , filter , bookChapter , dismissedIds , projectTexts ) ;
307+
308+ verify ( mockTextDocService . hasChapterEditPermissionForText ( anything ( ) , 1 ) ) . once ( ) ;
309+ verify ( mockTextDocService . isUsfmValidForText ( anything ( ) , 1 ) ) . once ( ) ;
310+ expect ( result ) . toBeTrue ( ) ;
311+ } ) ;
312+ } ) ;
313+
314+ describe ( 'hasDisplayPermission' , ( ) => {
315+ it ( 'should return false when text not found' , ( ) => {
316+ const insight = createMockInsight ( 'warning' , 42 ) ; // Luke
317+ const projectTexts : TextInfo [ ] = [ createMockTextInfo ( 40 ) ] ; // Only Matthew
318+
319+ const result = service . hasDisplayPermission ( insight , projectTexts ) ;
320+
321+ expect ( result ) . toBeFalse ( ) ;
322+ } ) ;
323+
324+ it ( 'should return false when edit permission is false' , ( ) => {
325+ const insight = createMockInsight ( ) ;
326+ const projectTexts : TextInfo [ ] = [ createMockTextInfo ( ) ] ;
327+
328+ when ( mockTextDocService . hasChapterEditPermissionForText ( anything ( ) , anything ( ) ) ) . thenReturn ( false ) ;
329+
330+ const result = service . hasDisplayPermission ( insight , projectTexts ) ;
331+
332+ expect ( result ) . toBeFalse ( ) ;
333+ } ) ;
334+
335+ it ( 'should return false when USFM is invalid' , ( ) => {
336+ const insight = createMockInsight ( ) ;
337+ const projectTexts : TextInfo [ ] = [ createMockTextInfo ( ) ] ;
338+
339+ when ( mockTextDocService . isUsfmValidForText ( anything ( ) , anything ( ) ) ) . thenReturn ( false ) ;
340+
341+ const result = service . hasDisplayPermission ( insight , projectTexts ) ;
342+
343+ expect ( result ) . toBeFalse ( ) ;
344+ } ) ;
345+
346+ it ( 'should return true when all checks pass' , ( ) => {
347+ const insight = createMockInsight ( ) ;
348+ const projectTexts : TextInfo [ ] = [ createMockTextInfo ( ) ] ;
349+
350+ when ( mockTextDocService . hasChapterEditPermissionForText ( anything ( ) , anything ( ) ) ) . thenReturn ( true ) ;
351+ when ( mockTextDocService . isUsfmValidForText ( anything ( ) , anything ( ) ) ) . thenReturn ( true ) ;
352+
353+ const result = service . hasDisplayPermission ( insight , projectTexts ) ;
354+
355+ verify ( mockTextDocService . hasChapterEditPermissionForText ( anything ( ) , 1 ) ) . once ( ) ;
356+ verify ( mockTextDocService . isUsfmValidForText ( anything ( ) , 1 ) ) . once ( ) ;
357+ expect ( result ) . toBeTrue ( ) ;
358+ } ) ;
359+
360+ it ( 'should call methods with correct TextInfo and chapter number' , ( ) => {
361+ const insight = createMockInsight ( 'warning' , 42 , 3 ) ; // Luke 3
362+ const matthewText = createMockTextInfo ( 40 ) ;
363+ const lukeText = createMockTextInfo ( 42 ) ;
364+ const projectTexts : TextInfo [ ] = [ matthewText , lukeText ] ;
365+
366+ when ( mockTextDocService . hasChapterEditPermissionForText ( anything ( ) , anything ( ) ) ) . thenReturn ( true ) ;
367+ when ( mockTextDocService . isUsfmValidForText ( anything ( ) , anything ( ) ) ) . thenReturn ( true ) ;
368+
369+ service . hasDisplayPermission ( insight , projectTexts ) ;
370+
371+ // Should call with Luke 3
372+ verify ( mockTextDocService . hasChapterEditPermissionForText ( lukeText , 3 ) ) . once ( ) ;
373+ verify ( mockTextDocService . isUsfmValidForText ( lukeText , 3 ) ) . once ( ) ;
374+
375+ // Should not call with Matthew
376+ verify ( mockTextDocService . hasChapterEditPermissionForText ( matthewText , anything ( ) ) ) . never ( ) ;
377+ verify ( mockTextDocService . isUsfmValidForText ( matthewText , anything ( ) ) ) . never ( ) ;
378+
379+ expect ( 1 ) . toBe ( 1 ) ;
380+ } ) ;
185381 } ) ;
186382} ) ;
0 commit comments