Skip to content

Commit 9f30b10

Browse files
committed
Only load draft build when needed & simplify tests
1 parent 2d146ce commit 9f30b10

File tree

2 files changed

+65
-86
lines changed

2 files changed

+65
-86
lines changed

src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/tabs/editor-tab-menu.service.spec.ts

Lines changed: 37 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { TestBed } from '@angular/core/testing';
22
import { invert } from 'lodash-es';
33
import { isParatextRole, SFProjectRole } from 'realtime-server/lib/esm/scriptureforge/models/sf-project-role';
44
import { createTestProjectProfile } from 'realtime-server/lib/esm/scriptureforge/models/sf-project-test-data';
5-
import { lastValueFrom, of, take } from 'rxjs';
5+
import { firstValueFrom, of } from 'rxjs';
66
import { anything, mock, when } from 'ts-mockito';
77
import { v4 as uuid } from 'uuid';
88
import { ActivatedProjectService } from 'xforge-common/activated-project.service';
@@ -51,14 +51,11 @@ describe('EditorTabMenuService', () => {
5151
service['canShowResource'] = () => true;
5252
service['canShowBiblicalTerms'] = () => false;
5353

54-
const items = await lastValueFrom(service.getMenuItems().pipe(take(1)));
55-
expect(items.length).toBe(3);
56-
expect(items[0].type).toBe('history');
57-
expect(items[1].type).toBe('draft');
58-
expect(items[2].type).toBe('project-resource');
54+
const items = await firstValueFrom(service.getMenuItems());
55+
expect(items.map(i => i.type)).toEqual(['history', 'draft', 'project-resource']);
5956
});
6057

61-
it('should get "history", "project-resource", and not "draft" (tab already exists) menu items', done => {
58+
it('should get "history", "project-resource", and not "draft" (tab already exists) menu items', async () => {
6259
const env = new TestEnvironment();
6360
env.setExistingTabs([
6461
{ id: uuid(), type: 'history', headerText$: of('History'), closeable: true, movable: true },
@@ -69,26 +66,19 @@ describe('EditorTabMenuService', () => {
6966
service['canShowResource'] = () => true;
7067
service['canShowBiblicalTerms'] = () => false;
7168

72-
service.getMenuItems().subscribe(items => {
73-
expect(items.length).toBe(2);
74-
expect(items[0].type).toBe('history');
75-
expect(items[1].type).toBe('project-resource');
76-
done();
77-
});
69+
const items = await firstValueFrom(service.getMenuItems());
70+
expect(items.map(i => i.type)).toEqual(['history', 'project-resource']);
7871
});
7972

80-
it('should get "history" (enabled), not "draft" (no draft build), and not "project-resource" menu items', done => {
73+
it('should get "history" (enabled), not "draft" (no draft build), and not "project-resource" menu items', async () => {
8174
const env = new TestEnvironment(TestEnvironment.projectDocNoDraft, { hasCompletedDraftBuild: false });
8275
env.setExistingTabs([{ id: uuid(), type: 'history', headerText$: of('History'), closeable: true, movable: true }]);
8376
service['canShowHistory'] = () => true;
8477
service['canShowResource'] = () => false;
8578
service['canShowBiblicalTerms'] = () => false;
8679

87-
service.getMenuItems().subscribe(items => {
88-
expect(items.length).toBe(1);
89-
expect(items[0].type).toBe('history');
90-
done();
91-
});
80+
const items = await firstValueFrom(service.getMenuItems());
81+
expect(items.map(i => i.type)).toEqual(['history']);
9282
});
9383

9484
it('should get "draft", "project-resource", and not "history" menu items', async () => {
@@ -98,13 +88,11 @@ describe('EditorTabMenuService', () => {
9888
service['canShowResource'] = () => true;
9989
service['canShowBiblicalTerms'] = () => false;
10090

101-
const items = await lastValueFrom(service.getMenuItems().pipe(take(1)));
102-
expect(items.length).toBe(2);
103-
expect(items[0].type).toBe('draft');
104-
expect(items[1].type).toBe('project-resource');
91+
const items = await firstValueFrom(service.getMenuItems());
92+
expect(items.map(i => i.type)).toEqual(['draft', 'project-resource']);
10593
});
10694

107-
it('should get "project-resources" and "history", and not "draft" on resource projects', done => {
95+
it('should get "project-resources" and "history", and not "draft" on resource projects', async () => {
10896
const projectDoc = {
10997
id: 'resource01',
11098
data: createTestProjectProfile({ paratextId: 'resource16char01', userRoles: TestEnvironment.rolesByUser })
@@ -114,15 +102,11 @@ describe('EditorTabMenuService', () => {
114102
service['canShowHistory'] = () => true;
115103
service['canShowResource'] = () => true;
116104

117-
service.getMenuItems().subscribe(items => {
118-
expect(items.length).toBe(2);
119-
expect(items[0].type).toBe('history');
120-
expect(items[1].type).toBe('project-resource');
121-
done();
122-
});
105+
const items = await firstValueFrom(service.getMenuItems());
106+
expect(items.map(i => i.type)).toEqual(['history', 'project-resource']);
123107
});
124108

125-
it('should get "project-resources" and "history", and not "draft" when draft does not exist', done => {
109+
it('should get "project-resources" and "history", and not "draft" when draft does not exist', async () => {
126110
const projectDoc = {
127111
id: 'project-no-draft',
128112
data: createTestProjectProfile({ translateConfig: { preTranslate: false } })
@@ -132,42 +116,34 @@ describe('EditorTabMenuService', () => {
132116
service['canShowHistory'] = () => true;
133117
service['canShowResource'] = () => true;
134118

135-
service.getMenuItems().subscribe(items => {
136-
expect(items.length).toBe(2);
137-
expect(items[0].type).toBe('history');
138-
expect(items[1].type).toBe('project-resource');
139-
done();
140-
});
119+
const items = await firstValueFrom(service.getMenuItems());
120+
expect(items.map(i => i.type)).toEqual(['history', 'project-resource']);
141121
});
142122

143-
it('should not get "draft" if the user cannot view drafts', done => {
123+
it('should not get "draft" if the user cannot view drafts', async () => {
144124
const env = new TestEnvironment();
145125
when(mockPermissionsService.canAccessDrafts(anything(), anything())).thenReturn(false);
146126
env.setExistingTabs([]);
147127
service['canShowHistory'] = () => true;
148128

149-
service.getMenuItems().subscribe(items => {
150-
expect(items.length).toBe(1);
151-
expect(items[0].type).toBe('history');
152-
done();
153-
});
129+
const items = await firstValueFrom(service.getMenuItems());
130+
expect(items.map(i => i.type)).toEqual(['history']);
154131
});
155132

156-
it('should get "biblical terms" menu item', done => {
133+
it('should get "biblical terms" menu item', async () => {
157134
const env = new TestEnvironment();
158135
when(mockPermissionsService.canAccessBiblicalTerms(anything())).thenReturn(true);
159136
env.setExistingTabs([]);
160137
service['canShowHistory'] = () => false;
161138
service['canShowResource'] = () => false;
162139

163-
service.getMenuItems().subscribe(items => {
164-
expect(items.length).toBeGreaterThanOrEqual(1);
165-
expect(items.some(i => i.type === 'biblical-terms')).toBe(true);
166-
if (items.length > 1) {
167-
expect(items.some(i => i.type === 'draft')).toBe(true);
168-
}
169-
done();
170-
});
140+
const items = await firstValueFrom(service.getMenuItems());
141+
142+
expect(items.length).toBeGreaterThanOrEqual(1);
143+
expect(items.some(i => i.type === 'biblical-terms')).toBe(true);
144+
if (items.length > 1) {
145+
expect(items.some(i => i.type === 'draft')).toBe(true);
146+
}
171147
});
172148

173149
it('should get no menu items', async () => {
@@ -177,7 +153,7 @@ describe('EditorTabMenuService', () => {
177153
service['canShowResource'] = () => false;
178154
service['canShowBiblicalTerms'] = () => false;
179155

180-
const items = await lastValueFrom(service.getMenuItems().pipe(take(1)));
156+
const items = await firstValueFrom(service.getMenuItems());
181157
expect(items.length).toBe(0);
182158
});
183159

@@ -190,16 +166,12 @@ describe('EditorTabMenuService', () => {
190166

191167
// Wait for observables to settle
192168
env.onlineStatus.setIsOnline(false);
193-
const offlineItems = await lastValueFrom(service.getMenuItems().pipe(take(1)));
194-
expect(offlineItems.length).toBe(1);
195-
expect(offlineItems[0].type).toBe('history');
169+
const offlineItems = await firstValueFrom(service.getMenuItems());
170+
expect(offlineItems.map(i => i.type)).toEqual(['history']);
196171

197172
env.onlineStatus.setIsOnline(true);
198-
const onlineItems = await lastValueFrom(service.getMenuItems().pipe(take(1)));
199-
expect(onlineItems.length).toBe(3);
200-
expect(onlineItems[0].type).toBe('history');
201-
expect(onlineItems[1].type).toBe('draft');
202-
expect(onlineItems[2].type).toBe('project-resource');
173+
const onlineItems = await firstValueFrom(service.getMenuItems());
174+
expect(onlineItems.map(i => i.type)).toEqual(['history', 'draft', 'project-resource']);
203175
});
204176

205177
describe('canShowHistory', () => {
@@ -249,10 +221,8 @@ describe('EditorTabMenuService', () => {
249221
service['canShowResource'] = () => true;
250222
service['canShowBiblicalTerms'] = () => false;
251223

252-
const items = await lastValueFrom(service.getMenuItems().pipe(take(1)));
253-
expect(items.length).toBe(2);
254-
expect(items[0].type).toBe('history');
255-
expect(items[1].type).toBe('project-resource');
224+
const items = await firstValueFrom(service.getMenuItems());
225+
expect(items.map(i => i.type)).toEqual(['history', 'project-resource']);
256226
});
257227
});
258228

@@ -299,8 +269,9 @@ class TestEnvironment {
299269
) {
300270
const projectDoc: SFProjectProfileDoc = explicitProjectDoc ?? this.projectDoc;
301271
const hasCompletedDraftBuild = options?.hasCompletedDraftBuild ?? true;
302-
when(mockActivatedProject.projectDoc$).thenReturn(of(projectDoc));
272+
when(mockActivatedProject.changes$).thenReturn(of(projectDoc));
303273
when(mockActivatedProject.projectId$).thenReturn(of(projectDoc.id));
274+
when(mockActivatedProject.projectId).thenReturn(projectDoc.id);
304275
when(mockTabState.tabs$).thenReturn(of([] as EditorTabInfo[]));
305276
when(mockDraftGenerationService.getLastCompletedBuild(anything())).thenReturn(
306277
of(hasCompletedDraftBuild ? ({} as any) : undefined)

src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/tabs/editor-tab-menu.service.ts

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
} from 'realtime-server/lib/esm/scriptureforge/models/editor-tab';
77
import { isParatextRole } from 'realtime-server/lib/esm/scriptureforge/models/sf-project-role';
88
import { combineLatest, map, Observable, of } from 'rxjs';
9-
import { shareReplay, switchMap } from 'rxjs/operators';
9+
import { distinctUntilChanged, shareReplay, switchMap } from 'rxjs/operators';
1010
import { ActivatedProjectService } from 'xforge-common/activated-project.service';
1111
import { I18nService } from 'xforge-common/i18n.service';
1212
import { OnlineStatusService } from 'xforge-common/online-status.service';
@@ -16,19 +16,34 @@ import { SFProjectProfileDoc } from '../../../core/models/sf-project-profile-doc
1616
import { ParatextService } from '../../../core/paratext.service';
1717
import { PermissionsService } from '../../../core/permissions.service';
1818
import { SFProjectService } from '../../../core/sf-project.service';
19-
import { BuildDto } from '../../../machine-api/build-dto';
2019
import { TabMenuItem, TabMenuService, TabStateService } from '../../../shared/sf-tab-group';
2120
import { DraftGenerationService } from '../../draft-generation/draft-generation.service';
2221
import { DraftOptionsService } from '../../draft-generation/draft-options.service';
2322
import { EditorTabInfo } from './editor-tabs.types';
2423

2524
@Injectable()
2625
export class EditorTabMenuService implements TabMenuService<EditorTabGroupType> {
27-
// TODO: Detect when a new draft build is available so we can update the latest build
28-
private readonly latestDraftBuild$: Observable<BuildDto | undefined> = this.activatedProject.projectId$.pipe(
29-
switchMap(projectId =>
30-
projectId == null ? of(undefined) : this.draftGenerationService.getLastCompletedBuild(projectId)
31-
)
26+
private readonly hasDraftAndPermission$: Observable<boolean> = this.activatedProject.changes$.pipe(
27+
map(
28+
projectDoc =>
29+
projectDoc?.data != null &&
30+
SFProjectService.hasDraft(projectDoc.data) &&
31+
this.permissionsService.canAccessDrafts(projectDoc, this.userService.currentUserId)
32+
),
33+
distinctUntilChanged()
34+
);
35+
36+
private readonly showDraftTab$: Observable<boolean> = combineLatest([
37+
this.hasDraftAndPermission$,
38+
this.onlineStatus.onlineStatus$
39+
]).pipe(
40+
switchMap(([hasDraftAndPermission, isOnline]) => {
41+
return isOnline && hasDraftAndPermission && this.activatedProject.projectId != null
42+
? this.draftGenerationService
43+
.getLastCompletedBuild(this.activatedProject.projectId)
44+
.pipe(map(build => !this.draftOptionsService.areFormattingOptionsAvailableButUnselected(build)))
45+
: of(false);
46+
})
3247
);
3348

3449
private readonly menuItems$: Observable<TabMenuItem[]> = this.initMenuItems();
@@ -52,22 +67,15 @@ export class EditorTabMenuService implements TabMenuService<EditorTabGroupType>
5267

5368
private initMenuItems(): Observable<TabMenuItem[]> {
5469
return combineLatest([
55-
this.activatedProject.projectDoc$.pipe(filterNullish()),
70+
this.activatedProject.changes$.pipe(filterNullish()),
5671
this.onlineStatus.onlineStatus$,
57-
this.latestDraftBuild$
72+
this.showDraftTab$
5873
]).pipe(
5974
quietTakeUntilDestroyed(this.destroyRef),
60-
switchMap(([projectDoc, isOnline, latestDraftBuild]) => {
61-
return combineLatest([of(projectDoc), of(isOnline), this.tabState.tabs$, of(latestDraftBuild)]);
75+
switchMap(([projectDoc, isOnline, showDraftTab]) => {
76+
return combineLatest([of(projectDoc), of(isOnline), of(showDraftTab), this.tabState.tabs$]);
6277
}),
63-
switchMap(([projectDoc, isOnline, existingTabs, latestDraftBuild]) => {
64-
const showDraft =
65-
isOnline &&
66-
projectDoc.data != null &&
67-
SFProjectService.hasDraft(projectDoc.data) &&
68-
this.permissionsService.canAccessDrafts(projectDoc, this.userService.currentUserId) &&
69-
latestDraftBuild != null &&
70-
!this.draftOptionsService.areFormattingOptionsAvailableButUnselected(latestDraftBuild);
78+
switchMap(([projectDoc, isOnline, showDraftTab, existingTabs]) => {
7179
const items: Observable<TabMenuItem>[] = [];
7280

7381
for (const tabType of editorTabTypes) {
@@ -83,7 +91,7 @@ export class EditorTabMenuService implements TabMenuService<EditorTabGroupType>
8391
}
8492
break;
8593
case 'draft':
86-
if (!showDraft) {
94+
if (!showDraftTab) {
8795
continue;
8896
}
8997
break;

0 commit comments

Comments
 (0)