Skip to content

Commit 9313679

Browse files
authored
v0.2.4 (#42)
* fix(query): issue with query path when using subcollections - #41 - @diagramatics * feat(query): tests added to prevent issues with query paths - #41 * feat(query): Errors thrown for invalid parameters passed to `setListeners` (not including `meta` or `firebase._`) * feat(core): tons of tests added for things such as query and utils
1 parent 2c83d0b commit 9313679

File tree

5 files changed

+272
-2
lines changed

5 files changed

+272
-2
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "redux-firestore",
3-
"version": "0.2.3",
3+
"version": "0.2.4",
44
"description": "Redux bindings for Firestore.",
55
"main": "lib/index.js",
66
"module": "es/index.js",

src/utils/query.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isObject, isString, isArray, size, trim, forEach } from 'lodash';
1+
import { isObject, isString, isArray, size, trim, forEach, has } from 'lodash';
22
import { actionTypes } from '../constants';
33

44
/**
@@ -140,7 +140,14 @@ const getQueryName = (meta) => {
140140
* @return {Object} Object containing all listeners
141141
*/
142142
export const attachListener = (firebase, dispatch, meta, unsubscribe) => {
143+
if (!meta) {
144+
throw new Error('Meta data is required to attach listener.');
145+
}
146+
if (!has(firebase, '_.listeners')) {
147+
throw new Error('Internal Firebase object required to attach listener. Confirm that reduxFirestore enhancer was added when you were creating your store');
148+
}
143149
const name = getQueryName(meta);
150+
144151
if (!firebase._.listeners[name]) {
145152
firebase._.listeners[name] = unsubscribe; // eslint-disable-line no-param-reassign
146153
}

tests/unit/actions/firestore.spec.js

+32
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import createFirestoreInstance from '../../../src/createFirestoreInstance';
22
import { firestoreActions } from '../../../src/actions';
3+
import { setListeners } from '../../../src/actions/firestore';
34

45
describe('firestoreActions', () => {
56
describe('exports', () => {
@@ -73,6 +74,37 @@ describe('firestoreActions', () => {
7374
expect(err.message).to.equal('Listeners must be an Array of listener configs (Strings/Objects)');
7475
}
7576
});
77+
78+
it('calls dispatch if listeners provided', () => {
79+
const instance = createFirestoreInstance({}, { helpersNamespace: 'test' });
80+
try {
81+
instance.test.setListeners({ collection: 'test' });
82+
} catch (err) {
83+
expect(err.message).to.equal('Listeners must be an Array of listener configs (Strings/Objects)');
84+
}
85+
});
86+
87+
it('maps listeners array', () => {
88+
const dispatchSpy = sinon.spy();
89+
// const mapSpy = sinon.spy(listenersArray, 'map');
90+
const fakeFirebase = {
91+
_: { listeners: {} },
92+
firestore: () => ({
93+
collection: () => ({ onSnapshot: () => ({ }) }),
94+
}),
95+
};
96+
setListeners(fakeFirebase, dispatchSpy, [{ collection: 'test' }]);
97+
// expect(mapSpy).to.be.calledOnce;
98+
});
99+
100+
it('supports subcollections', () => {
101+
const instance = createFirestoreInstance({}, { helpersNamespace: 'test' });
102+
try {
103+
instance.test.setListeners({ collection: 'test', doc: '1', subcollections: [{ collection: 'test2' }] });
104+
} catch (err) {
105+
expect(err.message).to.equal('Listeners must be an Array of listener configs (Strings/Objects)');
106+
}
107+
});
76108
});
77109

78110
describe('unsetListener', () => {

tests/unit/utils/actions.spec.js

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { wrapInDispatch } from '../../../src/utils/actions';
2+
3+
describe('actions utils', () => {
4+
describe('wrapInDispatch', () => {
5+
it('is exported', () => {
6+
expect(wrapInDispatch).to.be.a('function');
7+
});
8+
9+
it('calls dispatch', () => {
10+
const dispatch = sinon.spy();
11+
wrapInDispatch(dispatch, { ref: { test: () => Promise.resolve() }, types: ['test', 'test'], method: 'test' });
12+
expect(dispatch).to.have.been.calledOnce;
13+
});
14+
15+
it('handles Object action types', () => {
16+
const dispatch = sinon.spy();
17+
wrapInDispatch(dispatch, { ref: { test: () => Promise.resolve() }, types: [{ type: 'test' }, { type: 'test' }], method: 'test' });
18+
expect(dispatch).to.have.been.calledOnce;
19+
});
20+
21+
it('handles function payload types', () => {
22+
const dispatch = sinon.spy();
23+
const opts = {
24+
ref: { test: () => Promise.resolve() },
25+
types: [
26+
{ type: 'test' },
27+
{ type: 'test', payload: () => ({}) },
28+
],
29+
method: 'test',
30+
};
31+
wrapInDispatch(dispatch, opts);
32+
expect(dispatch).to.have.been.calledOnce;
33+
});
34+
35+
it('handles rejection', () => {
36+
const dispatch = sinon.spy();
37+
const opts = {
38+
ref: { test: () => Promise.reject() },
39+
types: [
40+
{ type: 'test' },
41+
{ type: 'test', payload: () => ({}) },
42+
],
43+
method: 'test',
44+
};
45+
wrapInDispatch(dispatch, opts);
46+
expect(dispatch).to.have.been.calledOnce;
47+
});
48+
});
49+
});

tests/unit/utils/query.spec.js

+182
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import {
2+
attachListener,
3+
getQueryConfigs,
4+
firestoreRef,
5+
} from '../../../src/utils/query';
6+
7+
let dispatch;
8+
let meta;
9+
let result;
10+
11+
describe('query utils', () => {
12+
describe('attachListener', () => {
13+
it('is exported', () => {
14+
expect(attachListener).to.be.a('function');
15+
});
16+
17+
describe('converts slash path to dot path', () => {
18+
beforeEach(() => {
19+
dispatch = sinon.spy();
20+
});
21+
22+
it('for collection', () => {
23+
meta = { collection: 'test' };
24+
attachListener({ _: { listeners: {} } }, dispatch, meta);
25+
expect(dispatch).to.be.calledWith({
26+
meta,
27+
payload: { name: 'test' },
28+
type: '@@reduxFirestore/SET_LISTENER',
29+
});
30+
});
31+
32+
it('for collection and document', () => {
33+
meta = { collection: 'test', doc: 'doc' };
34+
attachListener({ _: { listeners: {} } }, dispatch, meta);
35+
expect(dispatch).to.be.calledWith({
36+
meta,
37+
payload: { name: `${meta.collection}/${meta.doc}` },
38+
type: '@@reduxFirestore/SET_LISTENER',
39+
});
40+
});
41+
42+
it('for collection, document, and subcollections', () => {
43+
meta = { collection: 'test', doc: 'doc', subcollections: [{ collection: 'test' }] };
44+
attachListener({ _: { listeners: {} } }, dispatch, meta);
45+
expect(dispatch).to.be.calledWith({
46+
meta,
47+
payload: { name: `${meta.collection}/${meta.doc}/${meta.subcollections[0].collection}` },
48+
type: '@@reduxFirestore/SET_LISTENER',
49+
});
50+
});
51+
});
52+
53+
it('throws if meta is not included', () => {
54+
expect(() => attachListener({}, dispatch))
55+
.to.Throw('Meta data is required to attach listener.');
56+
});
57+
58+
it('throws if _ variable is not defined on Firebase', () => {
59+
expect(() => attachListener({}, dispatch, { collection: 'test' }))
60+
.to.Throw('Internal Firebase object required to attach listener. Confirm that reduxFirestore enhancer was added when you were creating your store');
61+
});
62+
});
63+
64+
describe('getQueryConfigs', () => {
65+
it('is exported', () => {
66+
expect(getQueryConfigs).to.be.a('function');
67+
});
68+
69+
it('it throws for invalid input', () => {
70+
expect(() => getQueryConfigs(1))
71+
.to.Throw('Querie(s) must be an Array or a string');
72+
});
73+
74+
describe('array', () => {
75+
it('with collection in string', () => {
76+
expect(getQueryConfigs(['test']))
77+
.to.have.nested.property('0.collection', 'test');
78+
});
79+
80+
it('with collection in an object', () => {
81+
expect(getQueryConfigs([{ collection: 'test' }]))
82+
.to.have.nested.property('0.collection', 'test');
83+
});
84+
85+
it('with collection and doc in an object', () => {
86+
meta = [{ collection: 'test', doc: 'other' }];
87+
result = getQueryConfigs(meta);
88+
expect(result)
89+
.to.have.nested.property('0.collection', meta[0].collection);
90+
expect(result).to.have.nested.property('0.doc', meta[0].doc);
91+
});
92+
93+
it('throws invalid object', () => {
94+
meta = [{ test: 'test' }];
95+
expect(() => getQueryConfigs(meta))
96+
.to.Throw('Collection and/or Doc are required parameters within query definition object');
97+
});
98+
});
99+
100+
describe('string', () => {
101+
it('with collection', () => {
102+
expect(getQueryConfigs('test'))
103+
.to.have.property('collection', 'test');
104+
});
105+
});
106+
107+
describe('object', () => {
108+
it('with collection', () => {
109+
expect(getQueryConfigs({ collection: 'test' }))
110+
.to.have.nested.property('0.collection', 'test');
111+
});
112+
113+
it('with doc', () => {
114+
meta = { collection: 'test', doc: 'other' };
115+
result = getQueryConfigs(meta);
116+
expect(result).to.have.nested.property('0.collection', meta.collection);
117+
expect(result).to.have.nested.property('0.doc', meta.doc);
118+
});
119+
120+
it('with subcollections', () => {
121+
meta = { collection: 'test', doc: 'other', subcollections: [{ collection: 'thing' }] };
122+
result = getQueryConfigs(meta);
123+
expect(result).to.have.nested.property('0.collection', meta.collection);
124+
expect(result).to.have.nested.property('0.doc', meta.doc);
125+
expect(result).to.have.nested.property('0.subcollections.0.collection', meta.subcollections[0].collection);
126+
});
127+
});
128+
});
129+
130+
describe('firestoreRef', () => {
131+
beforeEach(() => {
132+
dispatch = sinon.spy();
133+
});
134+
135+
describe('doc', () => {
136+
it('creates ref', () => {
137+
meta = { collection: 'test', doc: 'other' };
138+
const docSpy = sinon.spy(() => ({ }));
139+
const fakeFirebase = { firestore: () => ({ collection: () => ({ doc: docSpy }) }) };
140+
result = firestoreRef(fakeFirebase, dispatch, meta);
141+
expect(result).to.be.an('object');
142+
expect(docSpy).to.be.calledWith(meta.doc);
143+
});
144+
});
145+
146+
describe('subcollections', () => {
147+
it('creates ref with collection', () => {
148+
meta = { collection: 'test', doc: 'other', subcollections: [{ collection: 'thing' }] };
149+
const docSpy = sinon.spy(() => ({ }));
150+
const fakeFirebase = {
151+
firestore: () => ({
152+
collection: () => ({
153+
doc: () => ({
154+
collection: () => ({ doc: docSpy }),
155+
}),
156+
}),
157+
}),
158+
};
159+
result = firestoreRef(fakeFirebase, dispatch, meta);
160+
expect(result).to.be.an('object');
161+
// expect(docSpy).to.be.calledOnce(meta.subcollections[0].collection);
162+
});
163+
164+
it('creates ref with doc', () => {
165+
meta = { collection: 'test', doc: 'other', subcollections: [{ collection: 'thing', doc: 'again' }] };
166+
const docSpy = sinon.spy(() => ({ }));
167+
const fakeFirebase = {
168+
firestore: () => ({
169+
collection: () => ({
170+
doc: () => ({
171+
collection: () => ({ doc: docSpy }),
172+
}),
173+
}),
174+
}),
175+
};
176+
result = firestoreRef(fakeFirebase, dispatch, meta);
177+
expect(result).to.be.an('object');
178+
// expect(docSpy).to.be.calledWith(meta.subcollections[0].collection.doc);
179+
});
180+
});
181+
});
182+
});

0 commit comments

Comments
 (0)