Skip to content

Commit 0ef2ba2

Browse files
Validate state tree instances instead of snapshots in the SnapshotProcessor.is override (#2182)
* Validate state tree instances instead of snapshots in the SnapshotProcessor.is override This fixes a bug where within `.is` on models with snapshot processors, we validated against the model snapshot, instead of the model node when given a node. This means that two different models with compatible snapshots would not `.is` instances of each other normally, but once a snapshot processor is added, they would. This adds a failing test capturing this case. The PR that introduced this behaviour of validating the snapshot was introduced here: #1495, and I think was actually targeting a different use case -- passing *snapshots* to `.is`, not passing instances. The behaviour that PR added was to validate against the __processed__ version of a snapshot if passed a snapshot, which this PR maintains. But, if passed an instance, that PR elected to snapshot it as well, which I don't think is necessary, and breaks the substitutability of snapshot processed instances with their unwrapped counterparts. * docs: bump JSdocon snapshotProcessor is * docs: one more link for snapshotProcessor * chore: version bump to 7.0.0-pre.1 --------- Co-authored-by: Tyler Scott Williams <[email protected]>
1 parent 9c199a5 commit 0ef2ba2

File tree

3 files changed

+41
-2
lines changed

3 files changed

+41
-2
lines changed

__tests__/core/snapshotProcessor.test.ts

+22
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,28 @@ describe("snapshotProcessor", () => {
642642
expect(ProcessedModel.is(Model)).toBe(false)
643643
})
644644

645+
test(".is checks instances against the underlying type", () => {
646+
const ModelA = types.model({ x: types.number })
647+
const ModelB = types.model({ x: types.number })
648+
const modelA = ModelA.create({ x: 1 })
649+
const modelB = ModelB.create({ x: 2 })
650+
651+
// despite having the same snapshot type, .is is false for the instance of one against the other because they are not the same type
652+
expect(ModelA.is(modelA)).toBe(true)
653+
expect(ModelB.is(modelA)).toBe(false)
654+
expect(ModelA.is(modelB)).toBe(false)
655+
expect(ModelB.is(modelB)).toBe(true)
656+
657+
const ProcessedModel = types.snapshotProcessor(ModelA, {})
658+
659+
const processedModel = ProcessedModel.create({ x: 3 })
660+
expect(ProcessedModel.is(processedModel)).toBe(true)
661+
expect(ModelA.is(processedModel)).toBe(true)
662+
expect(ModelB.is(processedModel)).toBe(false)
663+
expect(ProcessedModel.is(modelA)).toBe(true)
664+
expect(ProcessedModel.is(modelB)).toBe(false)
665+
})
666+
645667
describe("1776 - reconciliation in an array", () => {
646668
test("model with transformed property is reconciled", () => {
647669
const SP = types.snapshotProcessor(

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "mobx-state-tree",
3-
"version": "6.0.1",
3+
"version": "7.0.0-pre.1",
44
"description": "Opinionated, transactional, MobX powered state container",
55
"main": "dist/mobx-state-tree.js",
66
"umd:main": "dist/mobx-state-tree.umd.js",

src/types/utility-types/snapshotProcessor.ts

+18-1
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,28 @@ class SnapshotProcessor<IT extends IAnyType, CustomC, CustomS> extends BaseType<
149149
return this._subtype
150150
}
151151

152+
/**
153+
* MST considers a given value to "be" of a subtype is the value is either:
154+
*
155+
* 1. And instance of the subtype
156+
* 2. A valid snapshot *in* of the subtype
157+
*
158+
* Before v7, we used to also consider processed models (as in, SnapshotOut values of this).
159+
* This is no longer the case, and is more in line with our overall "is" philosophy, which you can
160+
* see in `src/core/type/type.ts:104` (assuming lines don't change too much).
161+
*
162+
* For additonal commentary, see discussion in https://github.com/mobxjs/mobx-state-tree/pull/2182
163+
*
164+
* The `is` function specifically checks for `SnapshotIn` or `Instance` of a given type.
165+
*
166+
* @param thing
167+
* @returns
168+
*/
152169
is(thing: any): thing is any {
153170
const value = isType(thing)
154171
? this._subtype
155172
: isStateTreeNode(thing)
156-
? getSnapshot(thing, false)
173+
? thing
157174
: this.preProcessSnapshotSafe(thing)
158175
if (value === $preProcessorFailed) {
159176
return false

0 commit comments

Comments
 (0)