Skip to content

Commit 773117e

Browse files
committed
feat(refs): do not rebind already bound refs
Some refactoring slipped in as well. Sinc subscribeToRefs can be called on an object with refs that has already been bound, we must check when we bind that it hasn't been done yet. Because subs are saved with refKey (obj.nested.imaref), we should be safe to skip binding
1 parent 2a94031 commit 773117e

File tree

2 files changed

+116
-84
lines changed

2 files changed

+116
-84
lines changed

src/index.js

Lines changed: 71 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,60 @@
11
import { createSnapshot, extractRefs, callOnceWithArg, deepGetSplit } from './utils'
22

3+
// NOTE not convinced by the naming of subscribeToRefs and subscribeToDocument
4+
// first one is calling the other on every ref and subscribeToDocument may call
5+
// updateDataFromDocumentSnapshot which may call subscribeToRefs as well
6+
function subscribeToRefs ({
7+
subs,
8+
refs,
9+
target,
10+
key,
11+
data,
12+
depth,
13+
resolve
14+
}) {
15+
const refKeys = Object.keys(refs)
16+
if (!refKeys.length) return resolve()
17+
// TODO check if no ref is missing
18+
// TODO max depth param, default to 1?
19+
if (++depth > 3) throw new Error('more than 5 nested refs')
20+
refKeys.forEach(refKey => {
21+
// check if already bound to the same ref -> skip
22+
// TODO reuse if already bound?
23+
const sub = subs[refKey]
24+
const ref = refs[refKey]
25+
26+
if (sub) {
27+
if (sub.path !== ref.path) {
28+
sub.unbind()
29+
} else {
30+
// skip it as it's already bound
31+
// NOTE this is valid as long as target is the same
32+
// which is not checked anywhere but should be ok
33+
// because the subs object is created when needed
34+
return
35+
}
36+
}
37+
38+
// maybe wrap the unbind function to call unbind on every child
39+
const [innerObj, innerKey] = deepGetSplit(target[key], refKey)
40+
if (!innerObj) {
41+
console.log('=== ERROR ===')
42+
console.log(data, refKey, key, innerObj, innerKey)
43+
console.log('===')
44+
}
45+
subs[refKey] = {
46+
unbind: subscribeToDocument({
47+
ref,
48+
target: innerObj,
49+
key: innerKey,
50+
depth,
51+
resolve
52+
}),
53+
path: ref.path
54+
}
55+
})
56+
}
57+
358
function bindCollection ({
459
vm,
560
key,
@@ -9,48 +64,21 @@ function bindCollection ({
964
}) {
1065
// TODO wait to get all data
1166
const array = vm[key] = []
12-
const depth = 0
1367

1468
const change = {
1569
added: ({ newIndex, doc }) => {
1670
const subs = {}
1771
const snapshot = createSnapshot(doc)
1872
const [data, refs] = extractRefs(snapshot)
1973
array.splice(newIndex, 0, data)
20-
const refKeys = Object.keys(refs)
21-
if (!refKeys.length) return // resolve()
22-
// TODO check if no ref is missing
23-
// TODO max depth param, default to 1?
24-
// if (++depth > 3) throw new Error('more than 5 nested refs')
25-
refKeys.forEach(refKey => {
26-
// check if already bound to the same ref -> skip
27-
const sub = subs[refKey]
28-
const ref = refs[refKey]
29-
if (sub && sub.path !== ref.path) {
30-
sub.unbind()
31-
}
32-
// maybe wrap the unbind function to call unbind on every child
33-
const [innerObj, innerKey] = deepGetSplit(array[newIndex], refKey)
34-
if (!innerObj) {
35-
console.log('=== ERROR ===')
36-
console.log(data, refKey, newIndex, innerObj, innerKey)
37-
console.log('===')
38-
}
39-
subs[refKey] = {
40-
unbind: subscribeToDocument({
41-
ref,
42-
target: innerObj,
43-
key: innerKey,
44-
depth,
45-
// TODO parentSubs
46-
resolve
47-
}),
48-
path: ref.path
49-
}
50-
// unbind currently bound ref
51-
// bind ref
52-
// save unbind callback
53-
// probably save key or something as well
74+
subscribeToRefs({
75+
data,
76+
refs,
77+
subs,
78+
target: array,
79+
key: newIndex,
80+
depth: 0,
81+
resolve
5482
})
5583
},
5684
modified: ({ oldIndex, newIndex, doc }) => {
@@ -80,38 +108,16 @@ function bindCollection ({
80108
}
81109

82110
function updateDataFromDocumentSnapshot ({ snapshot, target, key, subs, depth = 0, resolve }) {
83-
// TODO extract refs
84111
const [data, refs] = extractRefs(snapshot)
85112
target[key] = data
86-
const refKeys = Object.keys(refs)
87-
if (!refKeys.length) return resolve()
88-
// TODO check if no ref is missing
89-
// TODO max depth param, default to 1?
90-
if (++depth > 3) throw new Error('more than 5 nested refs')
91-
refKeys.forEach(refKey => {
92-
// check if already bound to the same ref -> skip
93-
const sub = subs[refKey]
94-
const ref = refs[refKey]
95-
if (sub && sub.path !== ref.path) {
96-
sub.unbind()
97-
}
98-
// maybe wrap the unbind function to call unbind on every child
99-
const [innerObj, innerKey] = deepGetSplit(target[key], refKey)
100-
subs[refKey] = {
101-
unbind: subscribeToDocument({
102-
ref,
103-
target: innerObj,
104-
key: innerKey,
105-
depth,
106-
// TODO parentSubs
107-
resolve
108-
}),
109-
path: ref.path
110-
}
111-
// unbind currently bound ref
112-
// bind ref
113-
// save unbind callback
114-
// probably save key or something as well
113+
subscribeToRefs({
114+
data,
115+
subs,
116+
refs,
117+
target,
118+
key,
119+
depth,
120+
resolve
115121
})
116122
}
117123

test/refs-documents.spec.js

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,34 @@ beforeEach(async () => {
4040
await delay(5)
4141
})
4242

43+
function spyUnbind (ref) {
44+
const spy = jest.fn()
45+
const onSnapshot = ref.onSnapshot.bind(ref)
46+
ref.onSnapshot = fn => {
47+
const unbind = onSnapshot(fn)
48+
return () => {
49+
spy()
50+
unbind()
51+
}
52+
}
53+
return spy
54+
}
55+
56+
function spyOnSnapshot (ref) {
57+
const onSnapshot = ref.onSnapshot.bind(ref)
58+
return (ref.onSnapshot = jest.fn((...args) => onSnapshot(...args)))
59+
}
60+
61+
function spyOnSnapshotCallback (ref) {
62+
const onSnapshot = ref.onSnapshot.bind(ref)
63+
const spy = jest.fn()
64+
ref.onSnapshot = fn => onSnapshot((...args) => {
65+
spy()
66+
fn(...args)
67+
})
68+
return spy
69+
}
70+
4371
test('binds refs on documents', async () => {
4472
// create an empty doc and update using the ref instead of plain data
4573
const c = collection.doc()
@@ -129,12 +157,7 @@ test('unbinds previously bound document when overwriting a bound', async () => {
129157
const c = collection.doc()
130158

131159
// Mock c onSnapshot to spy when the callback is called
132-
const spy = jest.fn()
133-
const onSnapshot = c.onSnapshot.bind(c)
134-
c.onSnapshot = jest.fn(fn => onSnapshot((...args) => {
135-
spy()
136-
fn(...args)
137-
}))
160+
const spy = spyOnSnapshotCallback(c)
138161
await c.update({ baz: 'baz' })
139162
await d.update({ ref: c })
140163
await delay(5)
@@ -163,6 +186,22 @@ test('unbinds previously bound document when overwriting a bound', async () => {
163186
spy.mockRestore()
164187
})
165188

189+
test('does not rebind if it is the same ref', async () => {
190+
const c = collection.doc()
191+
192+
const spy = spyOnSnapshot(c)
193+
await c.update({ baz: 'baz' })
194+
await d.update({ ref: c })
195+
await delay(5)
196+
expect(spy).toHaveBeenCalledTimes(1)
197+
198+
await d.update({ ref: c })
199+
await delay(5)
200+
201+
expect(spy).toHaveBeenCalledTimes(1)
202+
spy.mockRestore()
203+
})
204+
166205
test('resolves the promise when refs are resolved in a document', async () => {
167206
await a.update({ a: true })
168207
await b.update({ ref: a })
@@ -194,19 +233,6 @@ test('resolves the promise when the document does not exist', async () => {
194233
expect(vm.item).toBe(null)
195234
})
196235

197-
function spyUnbind (ref) {
198-
const spy = jest.fn()
199-
const onSnapshot = ref.onSnapshot.bind(ref)
200-
ref.onSnapshot = jest.fn(fn => {
201-
const unbind = onSnapshot(fn)
202-
return () => {
203-
spy()
204-
unbind()
205-
}
206-
})
207-
return spy
208-
}
209-
210236
test('unbinds all refs when the document is unbound', async () => {
211237
const cSpy = spyUnbind(c)
212238
const dSpy = spyUnbind(d)

0 commit comments

Comments
 (0)