diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/checking/checking/checking.component.spec.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/checking/checking/checking.component.spec.ts index 63cede9b2be..61ae7681383 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/checking/checking/checking.component.spec.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/checking/checking/checking.component.spec.ts @@ -38,7 +38,18 @@ import { fromVerseRef, VerseRefData } from 'realtime-server/lib/esm/scripturefor import * as RichText from 'rich-text'; import { BehaviorSubject, firstValueFrom, of, Subject } from 'rxjs'; import { first } from 'rxjs/operators'; -import { anyString, anything, instance, mock, reset, resetCalls, spy, verify, when } from 'ts-mockito'; +import { + anyString, + anything, + instance, + mock, + objectContaining, + reset, + resetCalls, + spy, + verify, + when +} from 'ts-mockito'; import { DialogService } from 'xforge-common/dialog.service'; import { FileService } from 'xforge-common/file.service'; import { createStorageFileData, FileOfflineData, FileType } from 'xforge-common/models/file-offline-data'; @@ -347,6 +358,63 @@ describe('CheckingComponent', () => { discardPeriodicTasks(); })); + it('should specify the current verse when opening question dialog', fakeAsync(() => { + const env = new TestEnvironment({ user: ADMIN_USER }); + env.component.scripturePanel!.textComponent.segmentRef = 'verse_1_3'; + env.fixture.detectChanges(); + tick(); + + env.clickButton(env.addQuestionButton); + verify( + mockedQuestionDialogService.questionDialog( + objectContaining({ + defaultVerse: new VerseRef(43, 1, 3) + }) + ) + ).once(); + expect().nothing(); + flush(); + discardPeriodicTasks(); + })); + + it('should specify the nearest verse below a heading when opening question dialog', fakeAsync(() => { + const env = new TestEnvironment({ user: ADMIN_USER }); + env.component.scripturePanel!.textComponent.segmentRef = 's1_1'; + env.fixture.detectChanges(); + tick(); + + env.clickButton(env.addQuestionButton); + verify( + mockedQuestionDialogService.questionDialog( + objectContaining({ + defaultVerse: new VerseRef(43, 1, 5) + }) + ) + ).once(); + expect().nothing(); + flush(); + discardPeriodicTasks(); + })); + + it('should specify the nearest verse above a heading at the bottom when opening question dialog', fakeAsync(() => { + const env = new TestEnvironment({ user: ADMIN_USER }); + env.component.scripturePanel!.textComponent.segmentRef = 's1_2'; + env.fixture.detectChanges(); + tick(); + + env.clickButton(env.addQuestionButton); + verify( + mockedQuestionDialogService.questionDialog( + objectContaining({ + defaultVerse: new VerseRef(43, 1, 6) + }) + ) + ).once(); + expect().nothing(); + flush(); + discardPeriodicTasks(); + })); + it('hides add question button for community checker', fakeAsync(() => { const env = new TestEnvironment({ user: CHECKER_USER }); expect(env.addQuestionButton).toBeNull(); @@ -3883,6 +3951,8 @@ class TestEnvironment { delta.insert({ verse: { number: '4', style: 'v' } }); delta.insert(`target: chapter ${chapter}, verse 4.`, { segment: `verse_${chapter}_4` }); delta.insert('\n', { para: { style: 'p' } }); + delta.insert('Verse 2 heading', { segment: 's1_1' }); + delta.insert('\n', { para: { style: 's1' } }); delta.insert({ blank: true }, { segment: `verse_${chapter}_4/p_1` }); delta.insert({ verse: { number: '5', style: 'v' } }); delta.insert(`target: chapter ${chapter}, `, { segment: `verse_${chapter}_5` }); @@ -3890,6 +3960,8 @@ class TestEnvironment { delta.insert({ verse: { number: '6', style: 'v' } }); delta.insert(`ישע`, { segment: `verse_${chapter}_6` }); delta.insert('\n', { para: { style: 'p' } }); + delta.insert('End of chapter heading', { segment: 's1_2' }); + delta.insert('\n', { para: { style: 's1' } }); return delta; } diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/checking/checking/checking.component.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/checking/checking/checking.component.ts index d155ccada3f..75624adcf95 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/checking/checking/checking.component.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/checking/checking/checking.component.ts @@ -1078,9 +1078,28 @@ export class CheckingComponent extends DataLoadingComponent implements OnInit, A // Get the segment to set the scripture reference let verseRef: VerseRef | undefined = undefined; - const segmentRef: string | undefined = this.scripturePanel?.textComponent.segmentRef; - if (segmentRef != null && this.book != null) { - verseRef = getVerseRefFromSegmentRef(this.book, segmentRef); + if (this.scripturePanel != null) { + let segmentRef: string | undefined = this.scripturePanel.textComponent.segmentRef; + if (segmentRef != null && this.book != null) { + verseRef = getVerseRefFromSegmentRef(this.book, segmentRef); + + // If this segment is not a verse segment, keep looking down the chapter until we get a verse segment + while (verseRef == null) { + segmentRef = this.scripturePanel.textComponent.getNextSegmentRef(segmentRef); + if (segmentRef == null) break; + verseRef = getVerseRefFromSegmentRef(this.book, segmentRef); + } + + // And then if we still do not have a verse segment, look back up the chapter until we get a verse segment + if (segmentRef == null) { + segmentRef = this.scripturePanel.textComponent.segmentRef; + while (verseRef == null) { + segmentRef = this.scripturePanel.textComponent.getPrevSegmentRef(segmentRef); + if (segmentRef == null) break; + verseRef = getVerseRefFromSegmentRef(this.book, segmentRef); + } + } + } } verseRef ??= new VerseRef(this.book ?? 0, this.chapter ?? 1, 1); diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/shared/text/text.component.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/shared/text/text.component.ts index 800d32b6e97..c9de8acdf74 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/shared/text/text.component.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/shared/text/text.component.ts @@ -717,6 +717,14 @@ export class TextComponent implements AfterViewInit, OnDestroy { return false; } + getNextSegmentRef(ref: string): string | undefined { + return this.viewModel.getNextSegmentRef(ref); + } + + getPrevSegmentRef(ref: string): string | undefined { + return this.viewModel.getPrevSegmentRef(ref); + } + getSegmentRange(ref: string): Range | undefined { return this.viewModel.getSegmentRange(ref); } @@ -1512,7 +1520,7 @@ export class TextComponent implements AfterViewInit, OnDestroy { if (this._segment == null) { return; } - const nextRef = this.viewModel.getNextSegmentRef(this._segment.ref); + const nextRef = this.getNextSegmentRef(this._segment.ref); if (nextRef != null) { this.setSegment(nextRef, undefined, true, end); } @@ -1522,7 +1530,7 @@ export class TextComponent implements AfterViewInit, OnDestroy { if (this._segment == null) { return; } - const prevRef = this.viewModel.getPrevSegmentRef(this._segment.ref); + const prevRef = this.getPrevSegmentRef(this._segment.ref); if (prevRef != null) { this.setSegment(prevRef, undefined, true, end); }