Skip to content

Commit b5084fb

Browse files
Add explicit tests for Compat (#5870)
1 parent dd764b0 commit b5084fb

26 files changed

+8934
-20
lines changed

packages/firestore-compat/.idea/runConfigurations/Integration_Tests__Emulator_.xml

+18
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* @license
3+
* Copyright 2022 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
require('@babel/register')({ extensions: ['.js', '.ts'] });
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"presets": [
3+
"@babel/preset-typescript",
4+
["@babel/preset-env", {"targets": {"node": "10"}, "modules": "cjs"}]
5+
],
6+
"plugins": ["babel-plugin-transform-import-meta"]
7+
}

packages/firestore-compat/karma.conf.js

+3-17
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ module.exports = function (config) {
2424
files: getTestFiles(argv),
2525

2626
preprocessors: {
27-
'test/**/*.ts': ['webpack', 'sourcemap']
27+
'test/*.ts': ['webpack', 'sourcemap']
2828
},
2929

3030
// frameworks to use
@@ -44,22 +44,8 @@ module.exports = function (config) {
4444
* --unit and --integration command-line arguments.
4545
*/
4646
function getTestFiles(argv) {
47-
const unitTests = 'test/unit/bootstrap.ts';
48-
const legcayIntegrationTests = 'test/integration/bootstrap.ts';
49-
const liteIntegrationTests = 'test/lite/bootstrap.ts';
50-
if (argv.unit) {
51-
return [unitTests];
52-
} else if (argv.integration) {
53-
return [legcayIntegrationTests];
54-
} else if (argv.lite) {
55-
process.env.TEST_PLATFORM = 'browser_lite';
56-
return [liteIntegrationTests];
57-
} else {
58-
// Note that we cannot include both the firestore-exp and the legacy SDK
59-
// as the test runners modify the global namespace cannot be both included
60-
// in the same bundle.
61-
return [unitTests, legcayIntegrationTests];
62-
}
47+
const integrationTests = 'test/bootstrap.ts';
48+
return [integrationTests];
6349
}
6450

6551
/**

packages/firestore-compat/package.json

+7-3
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,16 @@
2626
"scripts": {
2727
"lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'",
2828
"lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'",
29-
"prettier": "prettier --write '*.js' '*.ts' '@(src|test)/**/*.ts'",
29+
"prettier": "prettier --write '*.js' '@(src|test)/**/*.ts'",
3030
"build": "rollup -c ./rollup.config.js",
3131
"build:console": "node tools/console.build.js",
3232
"build:deps": "lerna run --scope @firebase/firestore-compat --include-dependencies build",
3333
"build:release": "yarn build && yarn add-compat-overloads",
34-
"test": "echo 'tested as part of firestore'",
34+
"test": "run-s lint test:all",
35+
"test:ci": "node ../../scripts/run_tests_in_ci.js -s test:all",
36+
"test:all": "run-p test:browser test:node",
37+
"test:browser": "karma start --single-run",
38+
"test:node": "mocha --require babel-register.js --require src/index.node.ts --timeout 5000 'test/*.test.ts'",
3539
"add-compat-overloads": "ts-node-script ../../scripts/build/create-overloads.ts -i ../firestore/dist/index.d.ts -o dist/src/index.d.ts -a -r Firestore:types.FirebaseFirestore -r CollectionReference:types.CollectionReference -r DocumentReference:types.DocumentReference -r Query:types.Query -r FirebaseApp:FirebaseAppCompat --moduleToEnhance @firebase/firestore"
3640
},
3741
"peerDependencies": {
@@ -65,4 +69,4 @@
6569
"bugs": {
6670
"url": "https://github.com/firebase/firebase-js-sdk/issues"
6771
}
68-
}
72+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
/**
2+
* @license
3+
* Copyright 2022 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import * as firestore from '@firebase/firestore-types';
19+
import { expect } from 'chai';
20+
21+
import { addEqualityMatcher } from './util/equality_matcher';
22+
import { EventsAccumulator } from './util/events_accumulator';
23+
import * as firebaseExport from './util/firebase_export';
24+
import { apiDescribe, withTestDb, withTestDoc } from './util/helpers';
25+
26+
addEqualityMatcher();
27+
28+
const FieldValue = firebaseExport.FieldValue;
29+
30+
/**
31+
* Note: Transforms are tested pretty thoroughly in server_timestamp.test.ts
32+
* (via set, update, transactions, nested in documents, multiple transforms
33+
* together, etc.) and so these tests mostly focus on the array transform
34+
* semantics.
35+
*/
36+
apiDescribe('Array Transforms:', (persistence: boolean) => {
37+
// A document reference to read and write to.
38+
let docRef: firestore.DocumentReference;
39+
40+
// Accumulator used to capture events during the test.
41+
let accumulator: EventsAccumulator<firestore.DocumentSnapshot>;
42+
43+
// Listener registration for a listener maintained during the course of the
44+
// test.
45+
let unsubscribe: () => void;
46+
47+
/** Writes some initialData and consumes the events generated. */
48+
async function writeInitialData(
49+
initialData: firestore.DocumentData
50+
): Promise<void> {
51+
await docRef.set(initialData);
52+
await accumulator.awaitLocalEvent();
53+
const snapshot = await accumulator.awaitRemoteEvent();
54+
expect(snapshot.data()).to.deep.equal(initialData);
55+
}
56+
57+
async function expectLocalAndRemoteEvent(
58+
expected: firestore.DocumentData
59+
): Promise<void> {
60+
const localSnap = await accumulator.awaitLocalEvent();
61+
expect(localSnap.data()).to.deep.equal(expected);
62+
const remoteSnap = await accumulator.awaitRemoteEvent();
63+
expect(remoteSnap.data()).to.deep.equal(expected);
64+
}
65+
66+
/**
67+
* Wraps a test, getting a docRef and event accumulator, and cleaning them
68+
* up when done.
69+
*/
70+
async function withTestSetup<T>(test: () => Promise<T>): Promise<void> {
71+
await withTestDoc(persistence, async doc => {
72+
docRef = doc;
73+
accumulator = new EventsAccumulator<firestore.DocumentSnapshot>();
74+
unsubscribe = docRef.onSnapshot(
75+
{ includeMetadataChanges: true },
76+
accumulator.storeEvent
77+
);
78+
79+
// wait for initial null snapshot to avoid potential races.
80+
const snapshot = await accumulator.awaitRemoteEvent();
81+
expect(snapshot.exists).to.be.false;
82+
await test();
83+
unsubscribe();
84+
});
85+
}
86+
87+
it('create document with arrayUnion()', async () => {
88+
await withTestSetup(async () => {
89+
await docRef.set({ array: FieldValue.arrayUnion(1, 2) });
90+
await expectLocalAndRemoteEvent({ array: [1, 2] });
91+
});
92+
});
93+
94+
it('append to array via update()', async () => {
95+
await withTestSetup(async () => {
96+
await writeInitialData({ array: [1, 3] });
97+
await docRef.update({ array: FieldValue.arrayUnion(2, 1, 4) });
98+
await expectLocalAndRemoteEvent({ array: [1, 3, 2, 4] });
99+
});
100+
});
101+
102+
it('append to array via set(..., {merge: true})', async () => {
103+
await withTestSetup(async () => {
104+
await writeInitialData({ array: [1, 3] });
105+
await docRef.set(
106+
{ array: FieldValue.arrayUnion(2, 1, 4) },
107+
{ merge: true }
108+
);
109+
await expectLocalAndRemoteEvent({ array: [1, 3, 2, 4] });
110+
});
111+
});
112+
113+
it('append object to array via update()', async () => {
114+
await withTestSetup(async () => {
115+
await writeInitialData({ array: [{ a: 'hi' }] });
116+
await docRef.update({
117+
array: FieldValue.arrayUnion({ a: 'hi' }, { a: 'bye' })
118+
});
119+
await expectLocalAndRemoteEvent({ array: [{ a: 'hi' }, { a: 'bye' }] });
120+
});
121+
});
122+
123+
it('remove from array via update()', async () => {
124+
await withTestSetup(async () => {
125+
await writeInitialData({ array: [1, 3, 1, 3] });
126+
await docRef.update({ array: FieldValue.arrayRemove(1, 4) });
127+
await expectLocalAndRemoteEvent({ array: [3, 3] });
128+
});
129+
});
130+
131+
it('remove from array via set(..., {merge: true})', async () => {
132+
await withTestSetup(async () => {
133+
await writeInitialData({ array: [1, 3, 1, 3] });
134+
await docRef.set(
135+
{ array: FieldValue.arrayRemove(1, 4) },
136+
{ merge: true }
137+
);
138+
await expectLocalAndRemoteEvent({ array: [3, 3] });
139+
});
140+
});
141+
142+
it('remove object from array via update()', async () => {
143+
await withTestSetup(async () => {
144+
await writeInitialData({ array: [{ a: 'hi' }, { a: 'bye' }] });
145+
await docRef.update({ array: FieldValue.arrayRemove({ a: 'hi' }) });
146+
await expectLocalAndRemoteEvent({ array: [{ a: 'bye' }] });
147+
});
148+
});
149+
150+
it('arrayUnion() supports DocumentReference', async () => {
151+
await withTestSetup(async () => {
152+
await docRef.set({ array: FieldValue.arrayUnion(docRef) });
153+
await expectLocalAndRemoteEvent({ array: [docRef] });
154+
});
155+
});
156+
157+
/**
158+
* Unlike the withTestSetup() tests above, these tests intentionally avoid
159+
* having any ongoing listeners so that we can test what gets stored in the
160+
* offline cache based purely on the write acknowledgement (without receiving
161+
* an updated document via watch). As such they also rely on persistence
162+
* being enabled so documents remain in the cache after the write.
163+
*/
164+
// eslint-disable-next-line no-restricted-properties
165+
(persistence ? describe : describe.skip)('Server Application: ', () => {
166+
it('set() with no cached base doc', async () => {
167+
await withTestDoc(persistence, async docRef => {
168+
await docRef.set({ array: FieldValue.arrayUnion(1, 2) });
169+
const snapshot = await docRef.get({ source: 'cache' });
170+
expect(snapshot.data()).to.deep.equal({ array: [1, 2] });
171+
});
172+
});
173+
174+
it('update() with no cached base doc', async () => {
175+
let path: string | null = null;
176+
// Write an initial document in an isolated Firestore instance so it's not
177+
// stored in our cache
178+
await withTestDoc(persistence, async docRef => {
179+
path = docRef.path;
180+
await docRef.set({ array: [42] });
181+
});
182+
183+
await withTestDb(persistence, async db => {
184+
const docRef = db.doc(path!);
185+
await docRef.update({ array: FieldValue.arrayUnion(1, 2) });
186+
187+
// Nothing should be cached since it was an update and we had no base
188+
// doc.
189+
let errCaught = false;
190+
try {
191+
await docRef.get({ source: 'cache' });
192+
} catch (err) {
193+
expect(err.code).to.equal('unavailable');
194+
errCaught = true;
195+
}
196+
expect(errCaught).to.be.true;
197+
});
198+
});
199+
200+
it('set(..., {merge}) with no cached based doc', async () => {
201+
let path: string | null = null;
202+
// Write an initial document in an isolated Firestore instance so it's not
203+
// stored in our cache
204+
await withTestDoc(persistence, async docRef => {
205+
path = docRef.path;
206+
await docRef.set({ array: [42] });
207+
});
208+
209+
await withTestDb(persistence, async db => {
210+
const docRef = db.doc(path!);
211+
await docRef.set(
212+
{ array: FieldValue.arrayUnion(1, 2) },
213+
{ merge: true }
214+
);
215+
216+
// Document will be cached but we'll be missing 42.
217+
const snapshot = await docRef.get({ source: 'cache' });
218+
expect(snapshot.data()).to.deep.equal({ array: [1, 2] });
219+
});
220+
});
221+
222+
it('update() with cached base doc using arrayUnion()', async () => {
223+
await withTestDoc(persistence, async docRef => {
224+
await docRef.set({ array: [42] });
225+
await docRef.update({ array: FieldValue.arrayUnion(1, 2) });
226+
const snapshot = await docRef.get({ source: 'cache' });
227+
expect(snapshot.data()).to.deep.equal({ array: [42, 1, 2] });
228+
});
229+
});
230+
231+
it('update() with cached base doc using arrayRemove()', async () => {
232+
await withTestDoc(persistence, async docRef => {
233+
await docRef.set({ array: [42, 1, 2] });
234+
await docRef.update({ array: FieldValue.arrayRemove(1, 2) });
235+
const snapshot = await docRef.get({ source: 'cache' });
236+
expect(snapshot.data()).to.deep.equal({ array: [42] });
237+
});
238+
});
239+
});
240+
});

0 commit comments

Comments
 (0)