Skip to content

Commit 1c9bf0f

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

File tree

3 files changed

+62
-7
lines changed

3 files changed

+62
-7
lines changed

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

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import Quill, { Delta, EmitterSource, Range } from 'quill';
3131
import { User } from 'realtime-server/lib/esm/common/models/user';
3232
import { createTestUser } from 'realtime-server/lib/esm/common/models/user-test-data';
3333
import { WritingSystem } from 'realtime-server/lib/esm/common/models/writing-system';
34+
import { Json0OpBuilder } from 'realtime-server/lib/esm/common/utils/json0-op-builder';
3435
import { obj } from 'realtime-server/lib/esm/common/utils/obj-path';
3536
import { RecursivePartial } from 'realtime-server/lib/esm/common/utils/type-utils';
3637
import { BiblicalTerm } from 'realtime-server/lib/esm/scriptureforge/models/biblical-term';
@@ -70,6 +71,7 @@ import { CONSOLE } from 'xforge-common/browser-globals';
7071
import { BugsnagService } from 'xforge-common/bugsnag.service';
7172
import { createTestFeatureFlag, FeatureFlagService } from 'xforge-common/feature-flags/feature-flag.service';
7273
import { GenericDialogComponent, GenericDialogOptions } from 'xforge-common/generic-dialog/generic-dialog.component';
74+
import { MemoryRealtimeDocAdapter } from 'xforge-common/memory-realtime-remote-store';
7375
import { UserDoc } from 'xforge-common/models/user-doc';
7476
import { NoticeService } from 'xforge-common/notice.service';
7577
import { OnlineStatusService } from 'xforge-common/online-status.service';
@@ -3373,7 +3375,7 @@ describe('EditorComponent', () => {
33733375
env.dispose();
33743376
}));
33753377

3376-
it('should remove resolved notes after a remote update', fakeAsync(() => {
3378+
it('should remove resolved notes after a remote update', fakeAsync(async () => {
33773379
const env = new TestEnvironment();
33783380
env.setProjectUserConfig();
33793381
env.wait();
@@ -3382,7 +3384,7 @@ describe('EditorComponent', () => {
33823384
let noteThreadEmbedCount = env.countNoteThreadEmbeds(contents.ops!);
33833385
expect(noteThreadEmbedCount).toEqual(5);
33843386

3385-
env.resolveNote('project01', 'dataid01');
3387+
await env.resolveNote('project01', 'dataid01', 'remote');
33863388
contents = env.targetEditor.getContents();
33873389
noteThreadEmbedCount = env.countNoteThreadEmbeds(contents.ops!);
33883390
expect(noteThreadEmbedCount).toEqual(4);
@@ -5594,10 +5596,19 @@ class TestEnvironment {
55945596
return noteEmbedCount;
55955597
}
55965598

5597-
resolveNote(projectId: string, threadId: string): void {
5599+
async resolveNote(projectId: string, threadId: string, origin: 'local' | 'remote'): Promise<void> {
55985600
const noteDoc: NoteThreadDoc = this.getNoteThreadDoc(projectId, threadId);
5599-
noteDoc.submitJson0Op(op => op.set(n => n.status, NoteStatus.Resolved));
5600-
this.realtimeService.updateQueryAdaptersRemote();
5601+
const ops = op => op.set(n => n.status, NoteStatus.Resolved);
5602+
5603+
if (origin === 'remote') {
5604+
const docAdapter: MemoryRealtimeDocAdapter = noteDoc.adapter as MemoryRealtimeDocAdapter;
5605+
const builder = new Json0OpBuilder(noteDoc.data);
5606+
ops(builder);
5607+
const operations = builder.op;
5608+
this.realtimeService.simulateRemoteChange(docAdapter, operations);
5609+
} else {
5610+
await noteDoc.submitJson0Op(ops);
5611+
}
56015612
this.wait();
56025613
}
56035614

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: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import { Injectable } from '@angular/core';
22
import { merge } from 'lodash-es';
33
import * as OTJson0 from 'ot-json0';
44
import { MemoryOfflineStore } from './memory-offline-store';
5-
import { MemoryRealtimeQueryAdapter, MemoryRealtimeRemoteStore } from './memory-realtime-remote-store';
5+
import {
6+
MemoryRealtimeDocAdapter,
7+
MemoryRealtimeQueryAdapter,
8+
MemoryRealtimeRemoteStore
9+
} from './memory-realtime-remote-store';
610
import { FileOfflineData, FileType } from './models/file-offline-data';
711
import { Snapshot } from './models/snapshot';
812
import { RealtimeService } from './realtime.service';
@@ -33,6 +37,8 @@ export class TestRealtimeService extends RealtimeService {
3337
}
3438
}
3539

40+
/** Write a realtime doc into the store, for a given collection. An existing realtime doc with the same id is
41+
* overwritten. */
3642
addSnapshot<T>(collection: string, snapshot: Partial<Snapshot<T>>, addToOfflineStore: boolean = false): void {
3743
const completeSnapshot = addSnapshotDefaults(snapshot);
3844
(this.remoteStore as MemoryRealtimeRemoteStore).addSnapshot(collection, completeSnapshot);
@@ -57,6 +63,37 @@ export class TestRealtimeService extends RealtimeService {
5763
}
5864
}
5965

66+
/** Intended to do the same thing as `updateQueryAdaptersRemote` but without remoteChanges$ emitting, which can be
67+
* done in follow up. */
68+
updateQueryAdaptersRemoteQuietly(): MemoryRealtimeQueryAdapter[] {
69+
const adaptersToEmit: MemoryRealtimeQueryAdapter[] = [];
70+
for (const collectionQueries of this.subscribeQueries.values()) {
71+
for (const query of collectionQueries) {
72+
const adapter = query.adapter as MemoryRealtimeQueryAdapter;
73+
if ((adapter as any).performQuery()) {
74+
adaptersToEmit.push(adapter);
75+
}
76+
}
77+
}
78+
return adaptersToEmit;
79+
}
80+
81+
/** Simulate a change happening externally. The MemoryRealtimeDocAdapter data and MemoryRealtimeQueryAdapter results
82+
* are updated before changes are announced, so when changes begin to be announced, the docs and queries are all
83+
* up-to-date. The order of emits, and presence or absence of RealtimeQuery.remoteDocChanges$, may be different than
84+
* when running the app. */
85+
async simulateRemoteChange(docAdapter: MemoryRealtimeDocAdapter, ops: any): Promise<void> {
86+
// Submitting ops to the realtime doc adapter to simulate writing data on a remote server may seem backwards but
87+
// is getting the job done.
88+
docAdapter.submitOpWithoutEmitting(ops);
89+
const queryAdaptersToEmit: MemoryRealtimeQueryAdapter[] = this.updateQueryAdaptersRemoteQuietly();
90+
docAdapter.emitChange(ops);
91+
docAdapter.emitRemoteChange(ops);
92+
for (const adapter of queryAdaptersToEmit) {
93+
adapter.remoteChanges$.next();
94+
}
95+
}
96+
6097
async updateQueriesLocal(): Promise<void> {
6198
for (const collectionQueries of this.subscribeQueries.values()) {
6299
for (const query of collectionQueries) {

0 commit comments

Comments
 (0)