Skip to content

Commit df0bf6b

Browse files
committed
test: more accurately simulate a remote note change
1 parent 1c26d26 commit df0bf6b

File tree

3 files changed

+64
-7
lines changed

3 files changed

+64
-7
lines changed

src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/editor.component.spec.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ import { CONSOLE } from 'xforge-common/browser-globals';
7070
import { BugsnagService } from 'xforge-common/bugsnag.service';
7171
import { createTestFeatureFlag, FeatureFlagService } from 'xforge-common/feature-flags/feature-flag.service';
7272
import { GenericDialogComponent, GenericDialogOptions } from 'xforge-common/generic-dialog/generic-dialog.component';
73+
import { MemoryRealtimeDocAdapter } from 'xforge-common/memory-realtime-remote-store';
7374
import { UserDoc } from 'xforge-common/models/user-doc';
7475
import { NoticeService } from 'xforge-common/notice.service';
7576
import { OnlineStatusService } from 'xforge-common/online-status.service';
@@ -3373,7 +3374,7 @@ describe('EditorComponent', () => {
33733374
env.dispose();
33743375
}));
33753376

3376-
it('should remove resolved notes after a remote update', fakeAsync(() => {
3377+
it('should remove resolved notes after a remote update', fakeAsync(async () => {
33773378
const env = new TestEnvironment();
33783379
env.setProjectUserConfig();
33793380
env.wait();
@@ -3382,7 +3383,7 @@ describe('EditorComponent', () => {
33823383
let noteThreadEmbedCount = env.countNoteThreadEmbeds(contents.ops!);
33833384
expect(noteThreadEmbedCount).toEqual(5);
33843385

3385-
env.resolveNote('project01', 'dataid01');
3386+
await env.resolveNote('project01', 'dataid01');
33863387
contents = env.targetEditor.getContents();
33873388
noteThreadEmbedCount = env.countNoteThreadEmbeds(contents.ops!);
33883389
expect(noteThreadEmbedCount).toEqual(4);
@@ -5594,10 +5595,9 @@ class TestEnvironment {
55945595
return noteEmbedCount;
55955596
}
55965597

5597-
resolveNote(projectId: string, threadId: string): void {
5598+
async resolveNote(projectId: string, threadId: string): Promise<void> {
55985599
const noteDoc: NoteThreadDoc = this.getNoteThreadDoc(projectId, threadId);
5599-
noteDoc.submitJson0Op(op => op.set(n => n.status, NoteStatus.Resolved));
5600-
this.realtimeService.updateQueryAdaptersRemote();
5600+
await this.realtimeService.simulateRemoteChangeJson0Op(noteDoc, op => op.set(n => n.status, NoteStatus.Resolved));
56015601
this.wait();
56025602
}
56035603

src/SIL.XForge.Scripture/ClientApp/src/xforge-common/memory-realtime-remote-store.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ export class MemoryRealtimeRemoteStore extends RealtimeRemoteStore {
1717
// getAccessToken is not used in this memory implementation
1818
}
1919

20+
/** Write a realtime doc into the store, for a given collection. An existing realtime doc with the same id is
21+
* overwritten. */
2022
addSnapshot<T>(collection: string, snapshot: Snapshot<T>): void {
2123
let collectionSnapshots = this.snapshots.get(collection);
2224
if (collectionSnapshots == null) {
@@ -132,7 +134,7 @@ export class MemoryRealtimeDocAdapter implements RealtimeDocAdapter {
132134
return Promise.resolve();
133135
}
134136

135-
submitOp(op: any, source?: any): Promise<void> {
137+
submitOpWithoutEmitting(op: any): void {
136138
if (this.type == null) {
137139
throw new Error('The doc has not been loaded.');
138140
}
@@ -143,6 +145,11 @@ export class MemoryRealtimeDocAdapter implements RealtimeDocAdapter {
143145
}
144146
this.data = this.type.apply(this.data, op);
145147
this.version++;
148+
}
149+
150+
/** Multiple operations are received for the `op` argument. */
151+
submitOp(op: any, source?: any): Promise<void> {
152+
this.submitOpWithoutEmitting(op);
146153
this.emitChange(op);
147154
if (!source) {
148155
this.emitRemoteChange(op);

src/SIL.XForge.Scripture/ClientApp/src/xforge-common/test-realtime.service.ts

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
import { Injectable } from '@angular/core';
22
import { merge } from 'lodash-es';
33
import * as OTJson0 from 'ot-json0';
4+
import { Json0OpBuilder } from 'realtime-server/lib/esm/common/utils/json0-op-builder';
45
import { MemoryOfflineStore } from './memory-offline-store';
5-
import { MemoryRealtimeQueryAdapter, MemoryRealtimeRemoteStore } from './memory-realtime-remote-store';
6+
import {
7+
MemoryRealtimeDocAdapter,
8+
MemoryRealtimeQueryAdapter,
9+
MemoryRealtimeRemoteStore
10+
} from './memory-realtime-remote-store';
611
import { FileOfflineData, FileType } from './models/file-offline-data';
12+
import { RealtimeDoc } from './models/realtime-doc';
713
import { Snapshot } from './models/snapshot';
814
import { RealtimeService } from './realtime.service';
915
import { objectId } from './utils';
@@ -33,6 +39,8 @@ export class TestRealtimeService extends RealtimeService {
3339
}
3440
}
3541

42+
/** Write a realtime doc into the store, for a given collection. An existing realtime doc with the same id is
43+
* overwritten. */
3644
addSnapshot<T>(collection: string, snapshot: Partial<Snapshot<T>>, addToOfflineStore: boolean = false): void {
3745
const completeSnapshot = addSnapshotDefaults(snapshot);
3846
(this.remoteStore as MemoryRealtimeRemoteStore).addSnapshot(collection, completeSnapshot);
@@ -57,6 +65,48 @@ export class TestRealtimeService extends RealtimeService {
5765
}
5866
}
5967

68+
/** Intended to do the same thing as `updateQueryAdaptersRemote` but without remoteChanges$ emitting, which can be
69+
* done in follow up. */
70+
updateQueryAdaptersRemoteQuietly(): MemoryRealtimeQueryAdapter[] {
71+
const adaptersToEmit: MemoryRealtimeQueryAdapter[] = [];
72+
for (const collectionQueries of this.subscribeQueries.values()) {
73+
for (const query of collectionQueries) {
74+
const adapter = query.adapter as MemoryRealtimeQueryAdapter;
75+
if ((adapter as any).performQuery()) {
76+
adaptersToEmit.push(adapter);
77+
}
78+
}
79+
}
80+
return adaptersToEmit;
81+
}
82+
83+
/** Simulate a change happening externally. The MemoryRealtimeDocAdapter data and MemoryRealtimeQueryAdapter results
84+
* are updated before changes are announced, so when changes begin to be announced, the docs and queries are all
85+
* up-to-date. The order of emits, and presence or absence of RealtimeQuery.remoteDocChanges$, may be different than
86+
* when running the app. */
87+
async simulateRemoteChange(doc: RealtimeDoc, ops: any): Promise<void> {
88+
const docAdapter: MemoryRealtimeDocAdapter = doc.adapter as MemoryRealtimeDocAdapter;
89+
// Submitting ops to the realtime doc adapter to simulate writing data on a remote server may seem backwards but
90+
// is getting the job done.
91+
docAdapter.submitOpWithoutEmitting(ops);
92+
const queryAdaptersToEmit: MemoryRealtimeQueryAdapter[] = this.updateQueryAdaptersRemoteQuietly();
93+
docAdapter.emitChange(ops);
94+
docAdapter.emitRemoteChange(ops);
95+
for (const adapter of queryAdaptersToEmit) {
96+
adapter.remoteChanges$.next();
97+
}
98+
}
99+
100+
async simulateRemoteChangeJson0Op<T>(
101+
doc: RealtimeDoc<T>,
102+
build: (builder: Json0OpBuilder<T>) => void
103+
): Promise<void> {
104+
if (doc.data == null) throw new Error('Cannot simulate remote change on document with null data');
105+
const builder = new Json0OpBuilder(doc.data) as Json0OpBuilder<T>;
106+
build(builder);
107+
if (builder.op.length > 0) await this.simulateRemoteChange(doc, builder.op);
108+
}
109+
60110
async updateQueriesLocal(): Promise<void> {
61111
for (const collectionQueries of this.subscribeQueries.values()) {
62112
for (const query of collectionQueries) {

0 commit comments

Comments
 (0)