Skip to content

Commit b481d7a

Browse files
authored
v1.0.0 beta.3 (#108)
* fix(callFirestore): handle a case where data function is undefined in firestore get * feat(tests): add testing against emulators (#107)
1 parent f527ebb commit b481d7a

11 files changed

+3986
-246
lines changed

.github/workflows/publish.yml

+25
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,37 @@ jobs:
4444
CI: true
4545
run: yarn lint
4646

47+
- name: Expose Test Environment Variables
48+
env:
49+
SERVICE_ACCOUNT: ${{ secrets.SERVICE_ACCOUNT }}
50+
GITHUB_HEAD_REF: ${{ github.head_ref }}
51+
GITHUB_REF: ${{ github.ref }}
52+
# Generate Service Account file required to prevent credential error (jq used to format)
53+
run: |
54+
echo "Generating Service Account File..."
55+
echo "$(echo $SERVICE_ACCOUNT | jq .)" > $HOME/serviceAccount.json
56+
echo "::set-env name=GOOGLE_APPLICATION_CREDENTIALS::$HOME/serviceAccount.json"
57+
4758
- name: Run Unit Tests + Coverage
4859
env:
4960
CI: true
5061
CODE_CLIMATE: ${{ secrets.CODE_CLIMATE }}
5162
run: yarn test:cov && yarn codecov
5263

64+
- name: Run Build
65+
run: yarn build
66+
67+
# Skipped since yarn isn't supported
68+
# - name: Size Check
69+
# uses: andresz1/[email protected]
70+
# env:
71+
# CI_JOB_NUMBER: 1
72+
# with:
73+
# github_token: ${{ secrets.GITHUB_TOKEN }}
74+
75+
- name: Size Check
76+
run: yarn size
77+
5378
- name: Publish
5479
env:
5580
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

.github/workflows/verify.yml

+25-5
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,38 @@ jobs:
3333
CI: true
3434
run: yarn install --frozen-lockfile
3535

36-
- name: Run Build
37-
run: yarn build
38-
3936
- name: Check For Lint
40-
env:
41-
CI: true
4237
run: yarn lint
4338

39+
- name: Expose Test Environment Variables
40+
env:
41+
SERVICE_ACCOUNT: ${{ secrets.SERVICE_ACCOUNT }}
42+
GITHUB_HEAD_REF: ${{ github.head_ref }}
43+
GITHUB_REF: ${{ github.ref }}
44+
# Generate Service Account file required to prevent credential error (jq used to format)
45+
run: |
46+
echo "Generating Service Account File..."
47+
echo "$(echo $SERVICE_ACCOUNT | jq .)" > $HOME/serviceAccount.json
48+
echo "::set-env name=GOOGLE_APPLICATION_CREDENTIALS::$HOME/serviceAccount.json"
49+
4450
- name: Run Unit Tests + Coverage
4551
if: success()
4652
run: yarn test:cov
4753

54+
- name: Run Build
55+
run: yarn build
56+
57+
# Skipped since yarn isn't supported
58+
# - name: Size Check
59+
# uses: andresz1/[email protected]
60+
# env:
61+
# CI_JOB_NUMBER: 1
62+
# with:
63+
# github_token: ${{ secrets.GITHUB_TOKEN }}
64+
65+
- name: Size Check
66+
run: yarn size
67+
4868
- name: Upload Coverage
4969
if: success()
5070
env:

index.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ declare module "tasks" {
284284
* @param data - Data to pass to action
285285
* @returns Promsie which resolves with results of calling RTDB
286286
*/
287-
export function callRtdb(adminInstance: any, action: RTDBAction, actionPath: string, options?: RTDBCommandOptions, data?: FixtureData): Promise<any>;
287+
export function callRtdb(adminInstance: any, action: RTDBAction, actionPath: string, options?: RTDBCommandOptions, data?: FixtureData | string | boolean): Promise<any>;
288288
/**
289289
* Options for building Firestore commands
290290
*/

package.json

+27-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "cypress-firebase",
3-
"version": "1.0.0-beta.2",
3+
"version": "1.0.0-beta.3",
44
"description": "Utilities to help testing Firebase projects with Cypress.",
55
"main": "lib/index.js",
66
"module": "lib/index.js",
@@ -16,15 +16,22 @@
1616
"watch:es": "yarn build:esm --watch",
1717
"lint": "eslint src/**.ts --ext .ts",
1818
"lint:fix": "yarn lint --fix",
19-
"test": "mocha ./test/unit/**/*.spec.ts",
20-
"test:cov": "nyc --reporter=lcov --reporter=html yarn test",
19+
"test:base": "mocha ./test/unit/**/*.spec.ts --exit",
20+
"test:cov:base": "nyc --reporter=lcov --reporter=html yarn test:base",
21+
"test:watch": "yarn test:base --watch",
22+
"emulators": "firebase emulators:start --only firestore,database",
23+
"test": "firebase emulators:exec --only firestore,database \"yarn test:base\"",
24+
"test:cov": "firebase emulators:exec --only firestore,database \"yarn test:cov:base\"",
25+
"size": "yarn build && size-limit",
2126
"prepare": "yarn clean && yarn build"
2227
},
2328
"peerDependencies": {
2429
"firebase-admin": "^8"
2530
},
2631
"devDependencies": {
32+
"@firebase/testing": "^0.17.1",
2733
"@istanbuljs/nyc-config-typescript": "^1.0.1",
34+
"@size-limit/preset-small-lib": "^4.4.1",
2835
"@types/chai": "^4.2.10",
2936
"@types/mocha": "^7.0.2",
3037
"@types/node": "^13.9.3",
@@ -42,7 +49,7 @@
4249
"eslint-plugin-jsx-a11y": "^6.2.3",
4350
"eslint-plugin-prettier": "^3.1.2",
4451
"firebase-admin": "^8.10.0",
45-
"firebase-tools": "^7.15.0",
52+
"firebase-tools": "^7.16.0",
4653
"husky": "^4.2.3",
4754
"lint-staged": "^10.0.8",
4855
"mocha": "^7.1.0",
@@ -51,6 +58,7 @@
5158
"rimraf": "^3.0.0",
5259
"sinon": "^9.0.0",
5360
"sinon-chai": "^3.5.0",
61+
"size-limit": "^4.4.1",
5462
"source-map-support": "^0.5.16",
5563
"ts-node": "^8.8.1",
5664
"typescript": "^3.8.3"
@@ -93,5 +101,19 @@
93101
"*.{js,ts}": [
94102
"eslint --fix"
95103
]
96-
}
104+
},
105+
"size-limit": [
106+
{
107+
"name": "CommonJS",
108+
"path": "lib/*.js",
109+
"limit": "9kb",
110+
"webpack": false
111+
},
112+
{
113+
"name": "ESM",
114+
"path": "lib-esm/*.js",
115+
"limit": "8.5kb",
116+
"webpack": false
117+
}
118+
]
97119
}

src/plugin.ts

+1-10
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,6 @@ export default function pluginWithTasks(
4545
// Attach tasks to Cypress using on function
4646
cypressOnFunc('task', tasksWithFirebase);
4747

48-
// Enable cypress-firebase tasks support in custom commands
49-
const modifiedConfig = {
50-
...cypressConfig,
51-
env: {
52-
...(cypressConfig.env || {}),
53-
useCypressFirebaseTasks: true,
54-
},
55-
};
56-
5748
// Return extended config
58-
return extendWithFirebaseConfig(modifiedConfig);
49+
return extendWithFirebaseConfig(cypressConfig);
5950
}

src/tasks.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export function callRtdb(
4848
action: RTDBAction,
4949
actionPath: string,
5050
options?: RTDBCommandOptions,
51-
data?: FixtureData,
51+
data?: FixtureData | string | boolean,
5252
): Promise<any> {
5353
/**
5454
* @param err - Error to handle
@@ -156,7 +156,7 @@ export function callFirestore(
156156
}
157157
// Falling back to null in the case of falsey value prevents Cypress error with message:
158158
// "You must return a promise, a value, or null to indicate that the task was handled."
159-
return snap?.data() || null;
159+
return (typeof snap?.data === 'function' && snap.data()) || null;
160160
})
161161
.catch(handleError);
162162
}

test/setup.ts

+26-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,34 @@
11
/* eslint-disable no-unused-vars */
22
import chai from 'chai';
3-
import sinon from 'sinon';
43
import sinonChai from 'sinon-chai';
4+
import * as admin from 'firebase-admin';
55

6+
const projectId = 'test-project';
7+
const databaseEmulatorPort = 9000;
8+
const firstoreEmulatorPort = 8080;
9+
const databaseURL = `http://localhost:${databaseEmulatorPort}?ns=${projectId}`;
10+
11+
// Set environment variables
612
process.env.NODE_ENV = 'test';
13+
process.env.GCLOUD_PROJECT = projectId;
14+
process.env.FIREBASE_DATABASE_EMULATOR_HOST = `localhost:${databaseEmulatorPort}`;
15+
process.env.FIRESTORE_EMULATOR_HOST = `localhost:${firstoreEmulatorPort}`;
716

817
chai.use(sinonChai);
918

10-
// globals
11-
(global as any).expect = chai.expect;
12-
(global as any).sinon = sinon;
13-
(global as any).chai = chai;
19+
// Set global variables
20+
(global as any).projectId = projectId;
21+
(global as any).databaseURL = databaseURL;
22+
23+
// Initialize admin SDK with emulator settings for RTDB
24+
admin.initializeApp({
25+
projectId,
26+
databaseURL,
27+
// credential: admin.credential.applicationDefault(),
28+
});
29+
30+
// Initialize Firestore with emulator settings
31+
admin.firestore().settings({
32+
servicePath: 'localhost',
33+
port: firstoreEmulatorPort,
34+
});

test/unit/tasks-mocks.spec.ts

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { expect } from 'chai';
2+
import sinon from 'sinon';
3+
import * as tasks from '../../src/tasks';
4+
5+
let adminInstance: any;
6+
let collectionSpy: any;
7+
let rtdbRefSpy: any;
8+
let rtdbVal: any;
9+
let rtdbRemoveSpy: any;
10+
let docSpy: any;
11+
let rtdbValSpy: any;
12+
let getSpy: any;
13+
const firestoreData: any = {};
14+
15+
describe('tasks with mocks/spies', () => {
16+
beforeEach(() => {
17+
const createCustomTokenSpy = sinon.spy(() => Promise.resolve('someToken'));
18+
const authSpy = sinon.spy(() => ({
19+
createCustomToken: createCustomTokenSpy,
20+
}));
21+
getSpy = sinon.spy(
22+
(): Promise<any> => Promise.resolve({ data: () => firestoreData }),
23+
);
24+
docSpy = sinon.spy(() => ({
25+
get: getSpy,
26+
}));
27+
collectionSpy = sinon.spy(() => ({ doc: docSpy, get: getSpy }));
28+
const firestoreSpy = sinon.spy(() => ({
29+
collection: collectionSpy,
30+
doc: docSpy,
31+
}));
32+
const rtdbOnSpy = sinon.spy();
33+
rtdbVal = { some: 'value' };
34+
rtdbValSpy = sinon.spy(() => rtdbVal);
35+
const onceSpy = sinon.spy(
36+
(): Promise<any> => Promise.resolve({ val: rtdbValSpy }),
37+
);
38+
rtdbRemoveSpy = sinon.spy((): Promise<any> => Promise.resolve());
39+
rtdbRefSpy = sinon.spy(() => ({
40+
once: onceSpy,
41+
remove: rtdbRemoveSpy,
42+
on: rtdbOnSpy,
43+
}));
44+
const rtdbSpy = sinon.spy(() => ({
45+
ref: rtdbRefSpy,
46+
}));
47+
adminInstance = {
48+
auth: authSpy,
49+
firestore: firestoreSpy,
50+
database: rtdbSpy,
51+
};
52+
});
53+
54+
describe('callFirestore', () => {
55+
it('is exported', () => {
56+
expect(tasks).to.have.property('callFirestore');
57+
expect(tasks.callFirestore).to.be.a('function');
58+
});
59+
it('returns a promise', () => {
60+
expect(
61+
tasks.callFirestore(adminInstance, 'get', 'some/path').then,
62+
).to.be.a('function');
63+
});
64+
describe('get action', () => {
65+
it('gets collections', async () => {
66+
const result = await tasks.callFirestore(adminInstance, 'get', 'some');
67+
expect(collectionSpy).to.be.calledOnceWith('some');
68+
expect(getSpy).to.be.calledOnce;
69+
expect(result).to.be.equal(firestoreData);
70+
});
71+
72+
it('gets documents', async () => {
73+
const result = await tasks.callFirestore(
74+
adminInstance,
75+
'get',
76+
'some/path',
77+
);
78+
expect(docSpy).to.be.calledOnceWith('some/path');
79+
expect(getSpy).to.be.calledOnce;
80+
expect(result).to.be.equal(firestoreData);
81+
});
82+
});
83+
});
84+
85+
describe('callRtdb', () => {
86+
it('is exported', () => {
87+
expect(tasks).to.have.property('callRtdb');
88+
expect(tasks.callRtdb).to.be.a('function');
89+
});
90+
91+
it('returns a promise', () => {
92+
expect(tasks.callRtdb(adminInstance, 'get', 'some/path').then).to.be.a(
93+
'function',
94+
);
95+
});
96+
97+
describe('get action', () => {
98+
it('gets data', async () => {
99+
const result = await tasks.callRtdb(adminInstance, 'get', 'some/path');
100+
expect(rtdbRefSpy).to.have.been.calledOnceWith('some/path');
101+
expect(result).to.equal(rtdbVal);
102+
});
103+
});
104+
105+
describe('remove action', () => {
106+
it('removes data', async () => {
107+
await tasks.callRtdb(adminInstance, 'remove', 'some/path');
108+
expect(rtdbRefSpy).to.have.been.calledOnceWith('some/path');
109+
expect(rtdbRemoveSpy).to.have.been.calledOnce;
110+
});
111+
112+
it('supports "delete" action alias', async () => {
113+
await tasks.callRtdb(adminInstance, 'delete', 'some/path');
114+
expect(rtdbRefSpy).to.have.been.calledOnceWith('some/path');
115+
expect(rtdbRemoveSpy).to.have.been.calledOnce;
116+
});
117+
});
118+
});
119+
120+
describe('createCustomToken', () => {
121+
it('is exported', () => {
122+
expect(tasks).to.have.property('createCustomToken');
123+
expect(tasks.createCustomToken).to.be.a('function');
124+
});
125+
it('returns a promise', () => {
126+
expect(tasks.createCustomToken(adminInstance, 'someuid').then).to.be.a(
127+
'function',
128+
);
129+
});
130+
});
131+
});

0 commit comments

Comments
 (0)