Skip to content

Commit d22d415

Browse files
Support waitForPendingWrites in secondary tabs
1 parent 7bf7379 commit d22d415

File tree

4 files changed

+173
-2
lines changed

4 files changed

+173
-2
lines changed

packages/firestore/src/core/sync_engine.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1185,6 +1185,7 @@ export async function applyBatchState(
11851185
// NOTE: Both these methods are no-ops for batches that originated from
11861186
// other clients.
11871187
processUserCallback(syncEngineImpl, batchId, error ? error : null);
1188+
triggerPendingWritesCallbacks(syncEngineImpl, batchId);
11881189
removeCachedMutationBatchMetadata(syncEngineImpl.localStore, batchId);
11891190
} else {
11901191
fail(`Unknown batchState: ${batchState}`);

packages/firestore/test/unit/specs/spec_builder.ts

+15
Original file line numberDiff line numberDiff line change
@@ -793,6 +793,14 @@ export class SpecBuilder {
793793
return this;
794794
}
795795

796+
waitForPendingWrites() {
797+
this.nextStep();
798+
this.currentStep = {
799+
waitForPendingWrites: true
800+
};
801+
return this;
802+
}
803+
796804
expectUserCallbacks(docs: {
797805
acknowledged?: string[];
798806
rejected?: string[];
@@ -944,6 +952,13 @@ export class SpecBuilder {
944952
return this;
945953
}
946954

955+
expectWaitForPendingWritesEvent(count = 1): this {
956+
this.assertStep('Expectations require previous step');
957+
const currentStep = this.currentStep!;
958+
currentStep.expectedWaitForPendingWritesEvents = count;
959+
return this;
960+
}
961+
947962
private static queryToSpec(query: Query): SpecQuery {
948963
// TODO(dimond): full query support
949964
const spec: SpecQuery = { path: query.path.canonicalString() };

packages/firestore/test/unit/specs/spec_test_runner.ts

+43-1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import { SnapshotVersion } from '../../../src/core/snapshot_version';
4444
import {
4545
activeLimboDocumentResolutions,
4646
enqueuedLimboDocumentResolutions,
47+
registerPendingWritesCallback,
4748
SyncEngine,
4849
syncEngineListen,
4950
syncEngineUnlisten,
@@ -151,6 +152,8 @@ import {
151152
SharedFakeWebStorage,
152153
testWindow
153154
} from '../../util/test_platform';
155+
import { waitForPendingWrites } from '../../../exp/src/api/database';
156+
import { getSyncEngine } from '../../../exp/src/api/components';
154157

155158
const ARBITRARY_SEQUENCE_NUMBER = 2;
156159

@@ -195,6 +198,7 @@ abstract class TestRunner {
195198
private eventList: QueryEvent[] = [];
196199
private acknowledgedDocs: string[];
197200
private rejectedDocs: string[];
201+
private waitForPendingWritesEvents = 0;
198202
private snapshotsInSyncListeners: Array<Observer<void>>;
199203
private snapshotsInSyncEvents = 0;
200204

@@ -342,6 +346,9 @@ abstract class TestRunner {
342346
this.validateExpectedSnapshotEvents(step.expectedSnapshotEvents!);
343347
await this.validateExpectedState(step.expectedState!);
344348
this.validateSnapshotsInSyncEvents(step.expectedSnapshotsInSyncEvents);
349+
this.validateWaitForPendingWritesEvents(
350+
step.expectedWaitForPendingWritesEvents
351+
);
345352
this.eventList = [];
346353
this.rejectedDocs = [];
347354
this.acknowledgedDocs = [];
@@ -382,6 +389,8 @@ abstract class TestRunner {
382389
return this.doWriteAck(step.writeAck!);
383390
} else if ('failWrite' in step) {
384391
return this.doFailWrite(step.failWrite!);
392+
} else if ('waitForPendingWrites' in step) {
393+
return this.doWaitForPendingWrites();
385394
} else if ('runTimer' in step) {
386395
return this.doRunTimer(step.runTimer!);
387396
} else if ('drainQueue' in step) {
@@ -716,6 +725,18 @@ abstract class TestRunner {
716725
});
717726
}
718727

728+
private async doWaitForPendingWrites(): Promise<void> {
729+
const deferred = new Deferred();
730+
deferred.promise.then(() => {
731+
++this.waitForPendingWritesEvents;
732+
console.log(this.waitForPendingWritesEvents);
733+
});
734+
735+
return this.queue.enqueue(() =>
736+
registerPendingWritesCallback(this.syncEngine, deferred)
737+
);
738+
}
739+
719740
private async doRunTimer(timer: string): Promise<void> {
720741
// We assume the timer string is a valid TimerID enum value, but if it's
721742
// not, then there won't be a matching item on the queue and
@@ -912,10 +933,23 @@ abstract class TestRunner {
912933
}
913934
}
914935

936+
private validateWaitForPendingWritesEvents(
937+
expectedCount: number | undefined
938+
): void {
939+
expect(this.waitForPendingWritesEvents).to.eq(
940+
expectedCount || 0,
941+
'for waitForPendingWritesEvents'
942+
);
943+
this.waitForPendingWritesEvents = 0;
944+
}
945+
915946
private validateSnapshotsInSyncEvents(
916947
expectedCount: number | undefined
917948
): void {
918-
expect(this.snapshotsInSyncEvents).to.eq(expectedCount || 0);
949+
expect(this.snapshotsInSyncEvents).to.eq(
950+
expectedCount || 0,
951+
'for snapshotsInSyncEvents'
952+
);
919953
this.snapshotsInSyncEvents = 0;
920954
}
921955

@@ -1334,6 +1368,8 @@ export interface SpecStep {
13341368
writeAck?: SpecWriteAck;
13351369
/** Fail a write */
13361370
failWrite?: SpecWriteFailure;
1371+
/** Add a new `waitForPendingWrites` listener. */
1372+
waitForPendingWrites?: true;
13371373

13381374
/** Fails the listed database actions. */
13391375
failDatabase?: false | PersistenceAction[];
@@ -1387,6 +1423,12 @@ export interface SpecStep {
13871423
* If not provided, the test will fail if the step causes events to be raised.
13881424
*/
13891425
expectedSnapshotsInSyncEvents?: number;
1426+
1427+
/**
1428+
* Optional expected number of waitForPendingWrite callbacks to be called.
1429+
* If not provided, the test will fail if the step causes events to be raised.
1430+
*/
1431+
expectedWaitForPendingWritesEvents?: number;
13901432
}
13911433

13921434
/** [<target-id>, <query-path>] */

packages/firestore/test/unit/specs/write_spec.test.ts

+114-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
import { Document } from '../../../src/model/document';
1919
import { TimerId } from '../../../src/util/async_queue';
20-
import { Code } from '../../../src/util/error';
20+
import { Code, FirestoreError } from '../../../src/util/error';
2121
import { doc, query } from '../../util/helpers';
2222

2323
import { describeSpec, specTest } from './describe_spec';
@@ -1490,4 +1490,117 @@ describeSpec('Writes:', [], () => {
14901490
.expectEvents(query1, { added: [docA, docB], fromCache: true });
14911491
}
14921492
);
1493+
1494+
specTest(
1495+
'Wait for pending writes resolves after write acknowledgment',
1496+
[],
1497+
() => {
1498+
return spec()
1499+
.userSets('collection/a', { k: 'a' })
1500+
.userSets('collection/b', { k: 'b' })
1501+
.waitForPendingWrites()
1502+
.writeAcks('collection/a', 1001)
1503+
.failWrite(
1504+
'collection/b',
1505+
new RpcError(Code.FAILED_PRECONDITION, 'Write error')
1506+
)
1507+
.expectWaitForPendingWritesEvent();
1508+
}
1509+
);
1510+
1511+
specTest('Wait for pending writes resolves with no writes', [], () => {
1512+
return spec().waitForPendingWrites().expectWaitForPendingWritesEvent();
1513+
});
1514+
1515+
specTest('Wait for pending writes resolves multiple times', [], () => {
1516+
return spec()
1517+
.userSets('collection/a', { k: 'a' })
1518+
.waitForPendingWrites()
1519+
.waitForPendingWrites()
1520+
.writeAcks('collection/a', 1001)
1521+
.expectWaitForPendingWritesEvent(2);
1522+
});
1523+
1524+
specTest(
1525+
'Wait for pending writes resolves if another write is issued',
1526+
[],
1527+
() => {
1528+
return spec()
1529+
.userSets('collection/a', { k: 'a' })
1530+
.waitForPendingWrites()
1531+
.userSets('collection/b', { k: 'b' })
1532+
.writeAcks('collection/a', 1001)
1533+
.expectWaitForPendingWritesEvent()
1534+
.writeAcks('collection/b', 1002);
1535+
}
1536+
);
1537+
1538+
specTest(
1539+
'Wait for pending writes waits after restart',
1540+
['durable-persistence'],
1541+
() => {
1542+
return spec()
1543+
.userSets('collection/a', { k: 'a' })
1544+
.restart()
1545+
.waitForPendingWrites()
1546+
.writeAcks('collection/a', 1001, { expectUserCallback: false })
1547+
.expectWaitForPendingWritesEvent();
1548+
}
1549+
);
1550+
1551+
specTest(
1552+
'Wait for pending writes resolves for write in secondary tab',
1553+
['multi-client'],
1554+
() => {
1555+
return client(0)
1556+
.expectPrimaryState(true)
1557+
.client(1)
1558+
.userSets('collection/a', { k: 'a' })
1559+
.waitForPendingWrites()
1560+
.client(0)
1561+
.writeAcks('collection/a', 1001, { expectUserCallback: false })
1562+
.client(1)
1563+
.expectUserCallbacks({ acknowledged: ['collection/a'] })
1564+
.expectWaitForPendingWritesEvent();
1565+
}
1566+
);
1567+
1568+
specTest(
1569+
'Wait for pending writes resolve independently for different tabs',
1570+
['multi-client'],
1571+
() => {
1572+
return client(0)
1573+
.userSets('collection/a', { k: 'a' })
1574+
.waitForPendingWrites()
1575+
.client(1)
1576+
.userSets('collection/b', { k: 'b' })
1577+
.waitForPendingWrites()
1578+
.client(2)
1579+
.userSets('collection/c', { k: 'c' })
1580+
.waitForPendingWrites()
1581+
.client(0)
1582+
.writeAcks('collection/a', 1001)
1583+
.expectWaitForPendingWritesEvent(/* count= */ 1)
1584+
.client(1)
1585+
.expectWaitForPendingWritesEvent(/* count= */ 0)
1586+
.client(2)
1587+
.expectWaitForPendingWritesEvent(/* count= */ 0)
1588+
.client(0)
1589+
.writeAcks('collection/b', 1002, { expectUserCallback: false })
1590+
.expectWaitForPendingWritesEvent(/* count= */ 0)
1591+
.client(1)
1592+
.expectUserCallbacks({ acknowledged: ['collection/b'] })
1593+
.expectWaitForPendingWritesEvent(/* count= */ 1)
1594+
.client(2)
1595+
.expectWaitForPendingWritesEvent(/* count= */ 0)
1596+
.client(0)
1597+
.writeAcks('collection/c', 1003, { expectUserCallback: false })
1598+
.expectWaitForPendingWritesEvent(/* count= */ 0)
1599+
.client(1)
1600+
.expectWaitForPendingWritesEvent(/* count= */ 0)
1601+
.client(2)
1602+
.expectUserCallbacks({ acknowledged: ['collection/c'] })
1603+
.expectWaitForPendingWritesEvent(/* count= */ 1);
1604+
}
1605+
);
14931606
});

0 commit comments

Comments
 (0)