Skip to content

Commit

Permalink
v1.0.0 beta.3 (#108)
Browse files Browse the repository at this point in the history
* fix(callFirestore): handle a case where data function is undefined in firestore get
* feat(tests): add testing against emulators (#107)
  • Loading branch information
prescottprue authored Mar 24, 2020
1 parent f527ebb commit b481d7a
Show file tree
Hide file tree
Showing 11 changed files with 3,986 additions and 246 deletions.
25 changes: 25 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,37 @@ jobs:
CI: true
run: yarn lint

- name: Expose Test Environment Variables
env:
SERVICE_ACCOUNT: ${{ secrets.SERVICE_ACCOUNT }}
GITHUB_HEAD_REF: ${{ github.head_ref }}
GITHUB_REF: ${{ github.ref }}
# Generate Service Account file required to prevent credential error (jq used to format)
run: |
echo "Generating Service Account File..."
echo "$(echo $SERVICE_ACCOUNT | jq .)" > $HOME/serviceAccount.json
echo "::set-env name=GOOGLE_APPLICATION_CREDENTIALS::$HOME/serviceAccount.json"
- name: Run Unit Tests + Coverage
env:
CI: true
CODE_CLIMATE: ${{ secrets.CODE_CLIMATE }}
run: yarn test:cov && yarn codecov

- name: Run Build
run: yarn build

# Skipped since yarn isn't supported
# - name: Size Check
# uses: andresz1/[email protected]
# env:
# CI_JOB_NUMBER: 1
# with:
# github_token: ${{ secrets.GITHUB_TOKEN }}

- name: Size Check
run: yarn size

- name: Publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
Expand Down
30 changes: 25 additions & 5 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,38 @@ jobs:
CI: true
run: yarn install --frozen-lockfile

- name: Run Build
run: yarn build

- name: Check For Lint
env:
CI: true
run: yarn lint

- name: Expose Test Environment Variables
env:
SERVICE_ACCOUNT: ${{ secrets.SERVICE_ACCOUNT }}
GITHUB_HEAD_REF: ${{ github.head_ref }}
GITHUB_REF: ${{ github.ref }}
# Generate Service Account file required to prevent credential error (jq used to format)
run: |
echo "Generating Service Account File..."
echo "$(echo $SERVICE_ACCOUNT | jq .)" > $HOME/serviceAccount.json
echo "::set-env name=GOOGLE_APPLICATION_CREDENTIALS::$HOME/serviceAccount.json"
- name: Run Unit Tests + Coverage
if: success()
run: yarn test:cov

- name: Run Build
run: yarn build

# Skipped since yarn isn't supported
# - name: Size Check
# uses: andresz1/[email protected]
# env:
# CI_JOB_NUMBER: 1
# with:
# github_token: ${{ secrets.GITHUB_TOKEN }}

- name: Size Check
run: yarn size

- name: Upload Coverage
if: success()
env:
Expand Down
2 changes: 1 addition & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ declare module "tasks" {
* @param data - Data to pass to action
* @returns Promsie which resolves with results of calling RTDB
*/
export function callRtdb(adminInstance: any, action: RTDBAction, actionPath: string, options?: RTDBCommandOptions, data?: FixtureData): Promise<any>;
export function callRtdb(adminInstance: any, action: RTDBAction, actionPath: string, options?: RTDBCommandOptions, data?: FixtureData | string | boolean): Promise<any>;
/**
* Options for building Firestore commands
*/
Expand Down
32 changes: 27 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cypress-firebase",
"version": "1.0.0-beta.2",
"version": "1.0.0-beta.3",
"description": "Utilities to help testing Firebase projects with Cypress.",
"main": "lib/index.js",
"module": "lib/index.js",
Expand All @@ -16,15 +16,22 @@
"watch:es": "yarn build:esm --watch",
"lint": "eslint src/**.ts --ext .ts",
"lint:fix": "yarn lint --fix",
"test": "mocha ./test/unit/**/*.spec.ts",
"test:cov": "nyc --reporter=lcov --reporter=html yarn test",
"test:base": "mocha ./test/unit/**/*.spec.ts --exit",
"test:cov:base": "nyc --reporter=lcov --reporter=html yarn test:base",
"test:watch": "yarn test:base --watch",
"emulators": "firebase emulators:start --only firestore,database",
"test": "firebase emulators:exec --only firestore,database \"yarn test:base\"",
"test:cov": "firebase emulators:exec --only firestore,database \"yarn test:cov:base\"",
"size": "yarn build && size-limit",
"prepare": "yarn clean && yarn build"
},
"peerDependencies": {
"firebase-admin": "^8"
},
"devDependencies": {
"@firebase/testing": "^0.17.1",
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@size-limit/preset-small-lib": "^4.4.1",
"@types/chai": "^4.2.10",
"@types/mocha": "^7.0.2",
"@types/node": "^13.9.3",
Expand All @@ -42,7 +49,7 @@
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-prettier": "^3.1.2",
"firebase-admin": "^8.10.0",
"firebase-tools": "^7.15.0",
"firebase-tools": "^7.16.0",
"husky": "^4.2.3",
"lint-staged": "^10.0.8",
"mocha": "^7.1.0",
Expand All @@ -51,6 +58,7 @@
"rimraf": "^3.0.0",
"sinon": "^9.0.0",
"sinon-chai": "^3.5.0",
"size-limit": "^4.4.1",
"source-map-support": "^0.5.16",
"ts-node": "^8.8.1",
"typescript": "^3.8.3"
Expand Down Expand Up @@ -93,5 +101,19 @@
"*.{js,ts}": [
"eslint --fix"
]
}
},
"size-limit": [
{
"name": "CommonJS",
"path": "lib/*.js",
"limit": "9kb",
"webpack": false
},
{
"name": "ESM",
"path": "lib-esm/*.js",
"limit": "8.5kb",
"webpack": false
}
]
}
11 changes: 1 addition & 10 deletions src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,6 @@ export default function pluginWithTasks(
// Attach tasks to Cypress using on function
cypressOnFunc('task', tasksWithFirebase);

// Enable cypress-firebase tasks support in custom commands
const modifiedConfig = {
...cypressConfig,
env: {
...(cypressConfig.env || {}),
useCypressFirebaseTasks: true,
},
};

// Return extended config
return extendWithFirebaseConfig(modifiedConfig);
return extendWithFirebaseConfig(cypressConfig);
}
4 changes: 2 additions & 2 deletions src/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function callRtdb(
action: RTDBAction,
actionPath: string,
options?: RTDBCommandOptions,
data?: FixtureData,
data?: FixtureData | string | boolean,
): Promise<any> {
/**
* @param err - Error to handle
Expand Down Expand Up @@ -156,7 +156,7 @@ export function callFirestore(
}
// Falling back to null in the case of falsey value prevents Cypress error with message:
// "You must return a promise, a value, or null to indicate that the task was handled."
return snap?.data() || null;
return (typeof snap?.data === 'function' && snap.data()) || null;
})
.catch(handleError);
}
Expand Down
31 changes: 26 additions & 5 deletions test/setup.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,34 @@
/* eslint-disable no-unused-vars */
import chai from 'chai';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import * as admin from 'firebase-admin';

const projectId = 'test-project';
const databaseEmulatorPort = 9000;
const firstoreEmulatorPort = 8080;
const databaseURL = `http://localhost:${databaseEmulatorPort}?ns=${projectId}`;

// Set environment variables
process.env.NODE_ENV = 'test';
process.env.GCLOUD_PROJECT = projectId;
process.env.FIREBASE_DATABASE_EMULATOR_HOST = `localhost:${databaseEmulatorPort}`;
process.env.FIRESTORE_EMULATOR_HOST = `localhost:${firstoreEmulatorPort}`;

chai.use(sinonChai);

// globals
(global as any).expect = chai.expect;
(global as any).sinon = sinon;
(global as any).chai = chai;
// Set global variables
(global as any).projectId = projectId;
(global as any).databaseURL = databaseURL;

// Initialize admin SDK with emulator settings for RTDB
admin.initializeApp({
projectId,
databaseURL,
// credential: admin.credential.applicationDefault(),
});

// Initialize Firestore with emulator settings
admin.firestore().settings({
servicePath: 'localhost',
port: firstoreEmulatorPort,
});
131 changes: 131 additions & 0 deletions test/unit/tasks-mocks.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { expect } from 'chai';
import sinon from 'sinon';
import * as tasks from '../../src/tasks';

let adminInstance: any;
let collectionSpy: any;
let rtdbRefSpy: any;
let rtdbVal: any;
let rtdbRemoveSpy: any;
let docSpy: any;
let rtdbValSpy: any;
let getSpy: any;
const firestoreData: any = {};

describe('tasks with mocks/spies', () => {
beforeEach(() => {
const createCustomTokenSpy = sinon.spy(() => Promise.resolve('someToken'));
const authSpy = sinon.spy(() => ({
createCustomToken: createCustomTokenSpy,
}));
getSpy = sinon.spy(
(): Promise<any> => Promise.resolve({ data: () => firestoreData }),
);
docSpy = sinon.spy(() => ({
get: getSpy,
}));
collectionSpy = sinon.spy(() => ({ doc: docSpy, get: getSpy }));
const firestoreSpy = sinon.spy(() => ({
collection: collectionSpy,
doc: docSpy,
}));
const rtdbOnSpy = sinon.spy();
rtdbVal = { some: 'value' };
rtdbValSpy = sinon.spy(() => rtdbVal);
const onceSpy = sinon.spy(
(): Promise<any> => Promise.resolve({ val: rtdbValSpy }),
);
rtdbRemoveSpy = sinon.spy((): Promise<any> => Promise.resolve());
rtdbRefSpy = sinon.spy(() => ({
once: onceSpy,
remove: rtdbRemoveSpy,
on: rtdbOnSpy,
}));
const rtdbSpy = sinon.spy(() => ({
ref: rtdbRefSpy,
}));
adminInstance = {
auth: authSpy,
firestore: firestoreSpy,
database: rtdbSpy,
};
});

describe('callFirestore', () => {
it('is exported', () => {
expect(tasks).to.have.property('callFirestore');
expect(tasks.callFirestore).to.be.a('function');
});
it('returns a promise', () => {
expect(
tasks.callFirestore(adminInstance, 'get', 'some/path').then,
).to.be.a('function');
});
describe('get action', () => {
it('gets collections', async () => {
const result = await tasks.callFirestore(adminInstance, 'get', 'some');
expect(collectionSpy).to.be.calledOnceWith('some');
expect(getSpy).to.be.calledOnce;
expect(result).to.be.equal(firestoreData);
});

it('gets documents', async () => {
const result = await tasks.callFirestore(
adminInstance,
'get',
'some/path',
);
expect(docSpy).to.be.calledOnceWith('some/path');
expect(getSpy).to.be.calledOnce;
expect(result).to.be.equal(firestoreData);
});
});
});

describe('callRtdb', () => {
it('is exported', () => {
expect(tasks).to.have.property('callRtdb');
expect(tasks.callRtdb).to.be.a('function');
});

it('returns a promise', () => {
expect(tasks.callRtdb(adminInstance, 'get', 'some/path').then).to.be.a(
'function',
);
});

describe('get action', () => {
it('gets data', async () => {
const result = await tasks.callRtdb(adminInstance, 'get', 'some/path');
expect(rtdbRefSpy).to.have.been.calledOnceWith('some/path');
expect(result).to.equal(rtdbVal);
});
});

describe('remove action', () => {
it('removes data', async () => {
await tasks.callRtdb(adminInstance, 'remove', 'some/path');
expect(rtdbRefSpy).to.have.been.calledOnceWith('some/path');
expect(rtdbRemoveSpy).to.have.been.calledOnce;
});

it('supports "delete" action alias', async () => {
await tasks.callRtdb(adminInstance, 'delete', 'some/path');
expect(rtdbRefSpy).to.have.been.calledOnceWith('some/path');
expect(rtdbRemoveSpy).to.have.been.calledOnce;
});
});
});

describe('createCustomToken', () => {
it('is exported', () => {
expect(tasks).to.have.property('createCustomToken');
expect(tasks.createCustomToken).to.be.a('function');
});
it('returns a promise', () => {
expect(tasks.createCustomToken(adminInstance, 'someuid').then).to.be.a(
'function',
);
});
});
});
Loading

0 comments on commit b481d7a

Please sign in to comment.