UNPKG

7.97 kBJavaScriptView Raw
1import test from 'ava';
2import td from 'testdouble';
3import _ from 'lodash';
4
5import Coupler from './Coupler.js';
6import Bridge from './Bridge.js';
7import Dispatcher from './Dispatcher.js';
8
9td.verifyNoCalls = call => td.verify(call, {times: 0, ignoreExtraArgs: true});
10
11test.beforeEach(t => {
12 t.context = {
13 rootUrl: 'https://example.firebaseio.com',
14 bridge: td.object(Bridge),
15 dispatcher: td.object(Dispatcher),
16 applySnapshot: td.function(),
17 prunePath: td.function()
18 };
19 t.context.coupler = new Coupler(
20 t.context.rootUrl, t.context.bridge, t.context.dispatcher, t.context.applySnapshot,
21 t.context.prunePath
22 );
23 t.context.op1 = td.object({_disconnect: _.noop});
24 t.context.op2 = td.object({});
25 t.context.op3 = td.object({});
26 t.context.verifyOn = (url, times = 1) => td.verify(t.context.bridge.on(
27 url, url, null, 'value', td.matchers.isA(Function), td.matchers.isA(Function),
28 td.matchers.anything(), {sync: true}
29 ), {times});
30 t.context.verifyOff = (url, times = 1) => td.verify(t.context.bridge.off(
31 url, url, null, 'value', td.matchers.isA(Function), td.matchers.anything()
32 ), {times});
33});
34
35test.afterEach(t => {
36 t.context.coupler.destroy();
37});
38
39test('couple, decouple on root node', t => {
40 const that = t.context.coupler;
41 const url = t.context.rootUrl + '/';
42 t.false(that.isTrunkCoupled('/'));
43
44 that.couple('/', t.context.op1);
45 t.is(that._root.count, 1);
46 t.deepEqual(that._root.operations, [t.context.op1]);
47 t.true(that._root.listening);
48 t.true(that.isTrunkCoupled('/'));
49 t.context.verifyOn(url);
50 td.verifyNoCalls(t.context.bridge.off());
51
52 that.decouple('/', t.context.op1);
53 t.is(that._root.count, 0);
54 t.deepEqual(that._root.operations, []);
55 t.false(that._root.listening);
56 t.false(that.isTrunkCoupled('/'));
57 t.context.verifyOn(url);
58 t.context.verifyOff(url);
59});
60
61test('couple, decouple, on root child', t => {
62 const that = t.context.coupler;
63 const url = t.context.rootUrl + '/foo';
64 t.false(that.isTrunkCoupled('/foo'));
65
66 that.couple('/foo', t.context.op1);
67 t.is(that._root.children.foo.count, 1);
68 t.true(that._root.children.foo.listening);
69 t.true(that.isTrunkCoupled('/foo'));
70 t.context.verifyOn(url);
71 td.verifyNoCalls(t.context.bridge.off());
72
73 that.decouple('/foo', t.context.op1);
74 t.true(_.isEmpty(that._root.children));
75 t.false(that.isTrunkCoupled('/foo'));
76 t.context.verifyOn(url);
77 t.context.verifyOff(url);
78});
79
80test('couple, decouple, on root descendant', t => {
81 const that = t.context.coupler;
82 const url = t.context.rootUrl + '/foo/bar';
83 t.false(that.isTrunkCoupled('/foo/bar'));
84
85 that.couple('/foo/bar', t.context.op1);
86 t.is(that._root.children.foo.children.bar.count, 1);
87 t.true(that._root.children.foo.children.bar.listening);
88 t.true(that.isTrunkCoupled('/foo/bar'));
89 t.context.verifyOn(url);
90 td.verifyNoCalls(t.context.bridge.off());
91
92 that.decouple('/foo/bar', t.context.op1);
93 t.true(_.isEmpty(that._root.children));
94 t.false(that.isTrunkCoupled('/foo/bar'));
95 t.context.verifyOn(url);
96 t.context.verifyOff(url);
97});
98
99test('multiple coupler on same node', t => {
100 const that = t.context.coupler;
101 const url = t.context.rootUrl + '/foo';
102
103 that.couple('/foo', t.context.op1);
104 that.couple('/foo', t.context.op2);
105 t.is(that._root.children.foo.count, 2);
106 t.deepEqual(that._root.children.foo.operations, [t.context.op1, t.context.op2]);
107 t.true(that._root.children.foo.listening);
108 t.true(that.isTrunkCoupled('/foo'));
109 t.context.verifyOn(url);
110 td.verifyNoCalls(t.context.bridge.off());
111
112 that.decouple('/foo', t.context.op1);
113 t.is(that._root.children.foo.count, 1);
114 t.deepEqual(that._root.children.foo.operations, [t.context.op2]);
115 t.true(that._root.children.foo.listening);
116 t.true(that.isTrunkCoupled('/foo'));
117 t.context.verifyOn(url);
118 td.verifyNoCalls(t.context.bridge.off());
119});
120
121test('override child coupling', t => {
122 const that = t.context.coupler;
123 const rootUrl = t.context.rootUrl;
124
125 that.couple('/foo/bar', t.context.op1);
126 that.couple('/foo', t.context.op2);
127 t.is(that._root.children.foo.count, 1);
128 t.true(that._root.children.foo.listening);
129 t.true(that.isTrunkCoupled('/foo'));
130 t.is(that._root.children.foo.children.bar.count, 1);
131 t.true(that._root.children.foo.children.bar.listening);
132 t.true(that.isTrunkCoupled('/foo/bar'));
133 t.context.verifyOn(rootUrl + '/foo/bar');
134 t.context.verifyOn(rootUrl + '/foo');
135
136 that._root.children.foo._handleSnapshot(td.object({path: '/foo'}));
137 t.false(that._root.children.foo.children.bar.listening);
138 t.context.verifyOff(rootUrl + '/foo/bar');
139
140 that.decouple('/foo', t.context.op2);
141 t.is(that._root.children.foo.count, 0);
142 t.false(that._root.children.foo.listening);
143 t.false(that.isTrunkCoupled('/foo'));
144 t.is(that._root.children.foo.children.bar.count, 1);
145 t.true(that._root.children.foo.children.bar.listening);
146 t.true(that.isTrunkCoupled('/foo/bar'));
147 t.context.verifyOn(rootUrl + '/foo/bar', 2);
148 t.context.verifyOn(rootUrl + '/foo');
149 t.context.verifyOff(rootUrl + '/foo/bar');
150 t.context.verifyOff(rootUrl + '/foo');
151});
152
153test('superseded coupling', t => {
154 const that = t.context.coupler;
155 const rootUrl = t.context.rootUrl;
156
157 that.couple('/foo', t.context.op1);
158 that.couple('/foo/bar', t.context.op2);
159 t.is(that._root.children.foo.count, 1);
160 t.true(that._root.children.foo.listening);
161 t.true(that.isTrunkCoupled('/foo'));
162 t.is(that._root.children.foo.children.bar.count, 1);
163 t.falsy(that._root.children.foo.children.bar.listening);
164 t.true(that.isTrunkCoupled('/foo/bar'));
165 t.context.verifyOn(rootUrl + '/foo');
166 t.context.verifyOff(rootUrl + '/foo', 0);
167 t.context.verifyOn(rootUrl + '/foo/bar', 0);
168 t.context.verifyOff(rootUrl + '/foo/bar', 0);
169});
170
171test('uncoupled parents with coupled children are not deleted', t => {
172 const that = t.context.coupler;
173
174 that.couple('/foo', t.context.op1);
175 that.couple('/foo/bar', t.context.op2);
176 that.couple('/foo/baz', t.context.op3);
177 that.decouple('/foo/bar', t.context.op2);
178 t.is(that._root.children.foo.children.baz.count, 1);
179});
180
181test('handle snapshot', t => {
182 const that = t.context.coupler;
183
184 that.couple('/foo/bar', t.context.op1);
185 const node = that._root.children.foo.children.bar;
186 t.falsy(node.ready);
187 node._handleSnapshot({path: '/foo/bar'});
188 t.true(node.ready);
189 node._handleSnapshot({path: '/foo/bar/baz'});
190 node._handleSnapshot({path: '/foo'});
191
192 td.verify(t.context.applySnapshot({path: '/foo/bar'}), {times: 1});
193 td.verify(t.context.applySnapshot({path: '/foo/bar/baz'}), {times: 1});
194 td.verify(t.context.applySnapshot({path: '/foo'}), {times: 0});
195});
196
197test('handle error', t => {
198 const that = t.context.coupler;
199 const error = new Error('test');
200
201 that.couple('/foo/bar', t.context.op1);
202 that.couple('/foo/bar/baz', t.context.op2);
203 const bar = that._root.children.foo.children.bar;
204 const baz = bar.children.baz;
205
206 t.is(bar.count, 1);
207 t.true(bar.listening);
208 t.is(baz.count, 1);
209 t.falsy(baz.listening);
210
211 bar._handleSnapshot({path: '/foo/bar'});
212 t.true(bar.ready);
213
214 baz._handleError(error); // ignored, not listening to baz
215
216 t.is(bar.count, 1);
217 t.true(bar.listening);
218 t.is(baz.count, 1);
219 t.falsy(baz.listening);
220
221 td.when(t.context.dispatcher.retry(t.context.op1, error), {times: 1}).thenResolve(true);
222 const handlerPromise = bar._handleError(error);
223 t.false(bar.ready);
224 t.false(baz.ready);
225 t.false(bar.listening);
226
227 return handlerPromise.then(() => {
228 t.true(bar.listening);
229 bar._handleSnapshot({path: '/foo/bar'});
230 t.true(bar.ready);
231
232 td.when(t.context.dispatcher.retry(t.context.op1, error), {times: 1}).thenResolve(false);
233 td.when(t.context.op1._disconnect(error), {times: 1}).thenDo(() => {
234 that.decouple('/foo/bar', t.context.op1);
235 });
236 return bar._handleError(error);
237
238 }).then(() => {
239 t.false(bar.listening);
240 t.is(bar.count, 0);
241 t.true(baz.listening);
242 t.is(baz.count, 1);
243 });
244});