Skip to content

Commit 5ac8d34

Browse files
feat(proxy-state-tree): allow class instances
1 parent 376134e commit 5ac8d34

File tree

2 files changed

+154
-5
lines changed

2 files changed

+154
-5
lines changed

packages/node_modules/proxy-state-tree/src/Proxyfier.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -295,14 +295,16 @@ export class Proxifier {
295295
const nestedPath = proxifier.concat(path, prop)
296296
const currentTree = trackingTree || proxifier.tree
297297

298-
if (typeof targetValue === 'function') {
298+
if (typeof targetValue === 'function' && target.constructor.name === 'Object') {
299299
return proxifier.tree.master.options.dynamicWrapper
300300
? proxifier.tree.master.options.dynamicWrapper(
301301
trackingTree || proxifier.tree,
302302
nestedPath,
303303
targetValue
304304
)
305-
: targetValue(proxifier.tree, nestedPath)
305+
: targetValue.call(target, proxifier.tree, nestedPath)
306+
} else if (targetValue === 'function') {
307+
return (...args) => targetValue.call(target, ...args)
306308
} else {
307309
currentTree.trackPathListeners.forEach((cb) => cb(nestedPath))
308310
trackingTree && trackingTree.proxifier.trackPath(nestedPath)
@@ -404,11 +406,11 @@ export class Proxifier {
404406
return this.proxify(value[VALUE], path)
405407
} else if (value[IS_PROXY]) {
406408
return value
407-
} else if (isPlainObject(value)) {
408-
return this.createObjectProxy(value, path)
409409
} else if (Array.isArray(value)) {
410410
return this.createArrayProxy(value, path)
411-
}
411+
} else if (typeof value === 'object' && value !== null) {
412+
return this.createObjectProxy(value, path)
413+
}
412414
}
413415

414416
return value

packages/node_modules/proxy-state-tree/src/index.test.ts

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,153 @@ describe('TrackStateAccessTree', () => {
123123
})
124124
describe('TrackMutationTree', () => {})
125125

126+
describe('CLASSES', () => {
127+
describe('ACCESS', () => {
128+
test('should create proxy when accessed', () => {
129+
class User {
130+
name = 'Bob'
131+
}
132+
const state = {
133+
user: new User()
134+
}
135+
const tree = new ProxyStateTree(state)
136+
const trackStateTree = tree.getTrackStateTree().track(() => {})
137+
138+
expect(trackStateTree.state.user[IS_PROXY]).toBeTruthy()
139+
})
140+
141+
test('should access properties', () => {
142+
class User {
143+
name = 'Bob'
144+
}
145+
const state = {
146+
user: new User()
147+
}
148+
const tree = new ProxyStateTree(state)
149+
expect(tree.getTrackStateTree().track(() => {}).state.user.name).toBe('Bob')
150+
})
151+
152+
test('should track access properties', () => {
153+
class User {
154+
name = 'Bob'
155+
}
156+
const state = {
157+
user: new User()
158+
}
159+
const tree = new ProxyStateTree(state)
160+
const trackStateTree = tree.getTrackStateTree().track(() => {})
161+
162+
expect(trackStateTree.state.user.name).toBe('Bob')
163+
164+
expect(Object.keys((tree as any).pathDependencies)).toEqual([
165+
'user',
166+
'user.name',
167+
])
168+
})
169+
})
170+
describe('MUTATIONS', () => {
171+
test('should throw when mutating without tracking', () => {
172+
class User {
173+
name = 'Bob'
174+
}
175+
const state = {
176+
user: new User()
177+
}
178+
const tree = new ProxyStateTree(state)
179+
expect(() => {
180+
tree.getTrackStateTree().track(() => {}).state.user.name = 'bar2'
181+
}).toThrow()
182+
})
183+
184+
test('should throw if parts of state are nested', () => {
185+
class User {
186+
name = 'Bob'
187+
}
188+
const state = {
189+
user: new User()
190+
}
191+
const tree = new ProxyStateTree(state)
192+
expect(() => {
193+
const mutationTree = tree.getMutationTree()
194+
mutationTree.state.user = mutationTree.state.user // eslint-disable-line
195+
}).toThrowError(/exists in the state tree on path "user"/i)
196+
})
197+
test('should not notify changes to same value', () => {
198+
let renderCount = 0
199+
class User {
200+
name = 'Bob'
201+
}
202+
const state = {
203+
user: new User()
204+
}
205+
const tree = new ProxyStateTree(state)
206+
const mutationTree = tree.getMutationTree()
207+
const accessTree = tree.getTrackStateTree().track(() => {
208+
renderCount++
209+
})
210+
accessTree.addTrackingPath('user.name')
211+
mutationTree.state.user.name = 'bar'
212+
expect(mutationTree.mutations).toEqual([
213+
{
214+
method: 'set',
215+
path: 'user.name',
216+
args: ['bar'],
217+
hasChangedValue: true,
218+
revert: mutationTree.mutations[0].revert,
219+
},
220+
])
221+
expect(renderCount).toBe(0)
222+
})
223+
test('should track SET mutations', () => {
224+
class User {
225+
name = 'Bob'
226+
}
227+
const state = {
228+
user: new User()
229+
}
230+
const tree = new ProxyStateTree(state)
231+
const mutationTree = tree.getMutationTree()
232+
mutationTree.state.user.name = 'bar2'
233+
expect(mutationTree.mutations).toEqual([
234+
{
235+
method: 'set',
236+
path: 'user.name',
237+
args: ['bar2'],
238+
hasChangedValue: true,
239+
revert: mutationTree.mutations[0].revert,
240+
},
241+
])
242+
expect(mutationTree.state.user.name).toBe('bar2')
243+
})
244+
test('should track mutations through methods', () => {
245+
class User {
246+
name = 'Bob'
247+
changeName() {
248+
this.name = 'Bob2'
249+
}
250+
}
251+
const state = {
252+
user: new User()
253+
}
254+
const tree = new ProxyStateTree(state)
255+
const mutationTree = tree.getMutationTree()
256+
257+
mutationTree.state.user.changeName()
258+
259+
expect(mutationTree.mutations).toEqual([
260+
{
261+
method: 'set',
262+
path: 'user.name',
263+
args: ['Bob2'],
264+
hasChangedValue: true,
265+
revert: mutationTree.mutations[0].revert,
266+
},
267+
])
268+
expect(mutationTree.state.user.name).toBe('Bob2')
269+
})
270+
})
271+
})
272+
126273
describe('OBJECTS', () => {
127274
describe('ACCESS', () => {
128275
test('should create proxy when accessed', () => {

0 commit comments

Comments
 (0)