diff --git a/packages/mobx-state-tree/__tests__/core/__snapshots__/async.test.ts.snap b/packages/mobx-state-tree/__tests__/core/__snapshots__/async.test.ts.snap index b1fad41bc..144818963 100644 --- a/packages/mobx-state-tree/__tests__/core/__snapshots__/async.test.ts.snap +++ b/packages/mobx-state-tree/__tests__/core/__snapshots__/async.test.ts.snap @@ -23,7 +23,7 @@ Array [ "black", ], "id": 4, - "name": "fetchData", + "name": "startFetch", "parentActionEvent": Object { "allParentIds": Array [], "args": Array [ @@ -62,7 +62,7 @@ Array [ undefined, ], "id": 4, - "name": "fetchData", + "name": "startFetch", "parentActionEvent": Object { "allParentIds": Array [], "args": Array [ @@ -101,7 +101,7 @@ Array [ "drinking coffee", ], "id": 4, - "name": "fetchData", + "name": "startFetch", "parentActionEvent": Object { "allParentIds": Array [], "args": Array [ @@ -140,7 +140,7 @@ Array [ "awake", ], "id": 4, - "name": "fetchData", + "name": "startFetch", "parentActionEvent": Object { "allParentIds": Array [], "args": Array [ @@ -197,7 +197,7 @@ Array [ "black", ], "id": 6, - "name": "fetchData", + "name": "startFetch", "parentActionEvent": Object { "allParentIds": Array [], "args": Array [ @@ -236,7 +236,7 @@ Array [ undefined, ], "id": 6, - "name": "fetchData", + "name": "startFetch", "parentActionEvent": Object { "allParentIds": Array [], "args": Array [ @@ -275,7 +275,7 @@ Array [ "black", ], "id": 6, - "name": "fetchData", + "name": "startFetch", "parentActionEvent": Object { "allParentIds": Array [], "args": Array [ @@ -332,7 +332,7 @@ Array [ "black", ], "id": 26, - "name": "fetchData", + "name": "startFetch", "parentActionEvent": Object { "allParentIds": Array [], "args": Array [ @@ -371,7 +371,7 @@ Array [ undefined, ], "id": 26, - "name": "fetchData", + "name": "startFetch", "parentActionEvent": Object { "allParentIds": Array [], "args": Array [ @@ -433,7 +433,7 @@ Array [ undefined, ], "id": 26, - "name": "fetchData", + "name": "startFetch", "parentActionEvent": Object { "allParentIds": Array [], "args": Array [ @@ -499,7 +499,7 @@ Array [ undefined, ], "id": 26, - "name": "fetchData", + "name": "startFetch", "parentActionEvent": Object { "allParentIds": Array [], "args": Array [ @@ -565,7 +565,7 @@ Array [ undefined, ], "id": 26, - "name": "fetchData", + "name": "startFetch", "parentActionEvent": Object { "allParentIds": Array [], "args": Array [ @@ -631,7 +631,7 @@ Array [ undefined, ], "id": 26, - "name": "fetchData", + "name": "startFetch", "parentActionEvent": Object { "allParentIds": Array [], "args": Array [ @@ -674,7 +674,7 @@ Array [ "DRINKING BLACK", ], "id": 26, - "name": "fetchData", + "name": "startFetch", "parentActionEvent": Object { "allParentIds": Array [], "args": Array [ @@ -713,7 +713,7 @@ Array [ "DRINKING BLACK", ], "id": 26, - "name": "fetchData", + "name": "startFetch", "parentActionEvent": Object { "allParentIds": Array [], "args": Array [ @@ -770,7 +770,7 @@ Array [ "black", ], "id": 12, - "name": "fetchData", + "name": "startFetch", "parentActionEvent": Object { "allParentIds": Array [], "args": Array [ @@ -809,7 +809,7 @@ Array [ undefined, ], "id": 12, - "name": "fetchData", + "name": "startFetch", "parentActionEvent": Object { "allParentIds": Array [], "args": Array [ @@ -848,7 +848,7 @@ Array [ "x", ], "id": 12, - "name": "fetchData", + "name": "startFetch", "parentActionEvent": Object { "allParentIds": Array [], "args": Array [ @@ -887,7 +887,7 @@ Array [ "x", ], "id": 12, - "name": "fetchData", + "name": "startFetch", "parentActionEvent": Object { "allParentIds": Array [], "args": Array [ @@ -944,7 +944,7 @@ Array [ "black", ], "id": 8, - "name": "fetchData", + "name": "startFetch", "parentActionEvent": Object { "allParentIds": Array [], "args": Array [ @@ -983,7 +983,7 @@ Array [ undefined, ], "id": 8, - "name": "fetchData", + "name": "startFetch", "parentActionEvent": Object { "allParentIds": Array [], "args": Array [ @@ -1022,7 +1022,7 @@ Array [ "tea", ], "id": 8, - "name": "fetchData", + "name": "startFetch", "parentActionEvent": Object { "allParentIds": Array [], "args": Array [ @@ -1061,7 +1061,7 @@ Array [ "biscuit", ], "id": 8, - "name": "fetchData", + "name": "startFetch", "parentActionEvent": Object { "allParentIds": Array [], "args": Array [ @@ -1118,7 +1118,7 @@ Array [ "black", ], "id": 10, - "name": "fetchData", + "name": "startFetch", "parentActionEvent": Object { "allParentIds": Array [], "args": Array [ @@ -1157,7 +1157,7 @@ Array [ undefined, ], "id": 10, - "name": "fetchData", + "name": "startFetch", "parentActionEvent": Object { "allParentIds": Array [], "args": Array [ @@ -1196,7 +1196,7 @@ Array [ undefined, ], "id": 10, - "name": "fetchData", + "name": "startFetch", "parentActionEvent": Object { "allParentIds": Array [], "args": Array [ diff --git a/packages/mobx-state-tree/__tests__/core/action.test.ts b/packages/mobx-state-tree/__tests__/core/action.test.ts index 6718c6895..2aaf03eae 100644 --- a/packages/mobx-state-tree/__tests__/core/action.test.ts +++ b/packages/mobx-state-tree/__tests__/core/action.test.ts @@ -19,13 +19,10 @@ const Task = types .model({ done: false }) - .actions((self) => { - function toggle() { - self.done = !self.done - return self.done - } - return { - toggle + .actions({ + toggle() { + this.done = !this.done + return this.done } }) test("it should be possible to invoke a simple action", () => { @@ -108,17 +105,13 @@ const Order = types .model("Order", { customer: types.maybeNull(types.reference(Customer)) }) - .actions((self) => { - function setCustomer(customer: Instance) { - self.customer = customer - } - function noopSetCustomer(_: Instance) { + .actions({ + setCustomer(customer: Instance) { + this.customer = customer + }, + noopSetCustomer(_: Instance) { // noop } - return { - setCustomer, - noopSetCustomer - } }) const OrderStore = types.model("OrderStore", { customers: types.array(Customer), @@ -255,16 +248,14 @@ test("snapshot should be available and updated during an action", () => { .model({ x: types.number }) - .actions((self) => { - function inc() { - self.x += 1 - const res = getSnapshot(self).x - self.x += 1 + .actions({ + inc() { + this.x += 1 + // @ts-ignore + const res = getSnapshot(this).x + this.x += 1 return res } - return { - inc - } }) const a = Model.create({ x: 2 }) expect(a.inc()).toBe(3) @@ -277,16 +268,16 @@ test("indirectly called private functions should be able to modify state", () => .model({ x: 3 }) - .actions((self) => { - function incrementBy(delta: number) { - self.x += delta + .actions(() => { + function incrementBy(model: any, delta: number) { + model.x += delta } return { inc() { - incrementBy(1) + incrementBy(this, +1) }, dec() { - incrementBy(-1) + incrementBy(this, -1) } } }) @@ -296,15 +287,16 @@ test("indirectly called private functions should be able to modify state", () => expect(cnt.x).toBe(2) expect((cnt as any).incrementBy).toBe(undefined) }) + test("volatile state survives reonciliation", () => { - const Model = types.model({ x: 3 }).actions((self) => { + const Model = types.model({ x: 3 }).actions(() => { let incrementor = 1 return { setIncrementor(value: number) { incrementor = value }, inc() { - self.x += incrementor + this.x += incrementor } } }) @@ -327,14 +319,14 @@ test("middleware events are correct", () => { useProxies: "never" }) - const A = types.model({}).actions((self) => ({ + const A = types.model({}).actions({ a(x: number) { return this.b(x * 2) }, b(y: number) { return y + 1 } - })) + }) const a = A.create() const events: IMiddlewareEvent[] = [] addMiddleware(a, function (call, next) { @@ -344,26 +336,26 @@ test("middleware events are correct", () => { a.a(7) const event1 = { args: [7], - context: {}, + context: a, id: process.env.NODE_ENV !== "production" ? 29 : 28, name: "a", parentId: 0, rootId: process.env.NODE_ENV !== "production" ? 29 : 28, allParentIds: [], - tree: {}, + tree: a, type: "action", parentEvent: undefined, parentActionEvent: undefined } const event2 = { args: [14], - context: {}, + context: a, id: process.env.NODE_ENV !== "production" ? 30 : 29, name: "b", parentId: process.env.NODE_ENV !== "production" ? 29 : 28, rootId: process.env.NODE_ENV !== "production" ? 29 : 28, allParentIds: [process.env.NODE_ENV !== "production" ? 29 : 28], - tree: {}, + tree: a, type: "action", parentEvent: event1, parentActionEvent: event1 @@ -378,16 +370,16 @@ test("actions are mockable", () => { const M = types .model() - .actions((self) => ({ + .actions({ method(): number { return 3 } - })) - .views((self) => ({ + }) + .views({ view(): number { return 3 } - })) + }) const m = M.create() if (process.env.NODE_ENV === "production") { expect(() => { @@ -417,20 +409,20 @@ test("after attach action should work correctly", () => { .model({ title: "test" }) - .actions((self) => ({ + .actions({ remove() { - getRoot(self).remove(cast(self)) + getRoot(this).remove(cast(this)) } - })) + }) const S = types .model({ todos: types.array(Todo) }) - .actions((self) => ({ + .actions({ remove(todo: Instance) { - self.todos.remove(todo) + this.todos.remove(todo) } - })) + }) const s = S.create({ todos: [{ title: "todo" }] diff --git a/packages/mobx-state-tree/__tests__/core/actionTrackingMiddleware2.test.ts b/packages/mobx-state-tree/__tests__/core/actionTrackingMiddleware2.test.ts index d83959c98..bf6019e4f 100644 --- a/packages/mobx-state-tree/__tests__/core/actionTrackingMiddleware2.test.ts +++ b/packages/mobx-state-tree/__tests__/core/actionTrackingMiddleware2.test.ts @@ -62,22 +62,22 @@ async function syncTest(mode: "success" | "fail") { y: 2, z: 3 }) - .actions((self) => ({ + .actions({ setX(v: number) { - self.x = v + this.x = v if (mode === "fail") { throw "error" } }, setY(v: number) { - self.y = v + this.y = v this.setX(v + 1) }, setZ(v: number) { - self.z = v + this.z = v this.setY(v + 1) } - })) + }) const m = M.create() @@ -116,24 +116,24 @@ async function flowTest(mode: "success" | "fail") { y: 2, z: 3 }) - .actions((self) => ({ - setX: flow(function* flowSetX(v: number) { + .actions({ + setX: function* flowSetX(v: number) { yield Promise.resolve() yield _subFlow() - self.x = v + this.x = v if (mode === "fail") { throw "error" } - }), - setY: flow(function* flowSetY(v: number) { - self.y = v - yield (self as any).setX(v + 1) - }), - setZ: flow(function* flowSetZ(v: number) { - self.z = v - yield (self as any).setY(v + 1) - }) - })) + }, + setY: function* flowSetY(v: number) { + this.y = v + yield this.setX(v + 1) + }, + setZ: function* flowSetZ(v: number) { + this.z = v + yield this.setY(v + 1) + } + }) const m = M.create() @@ -167,15 +167,15 @@ test("#1250", async () => { x: 0, y: 0 }) - .actions((self) => ({ - setX: flow(function* () { - self.x = 10 + .actions({ + setX: function* () { + this.x = 10 yield new Promise((resolve) => setTimeout(resolve, 1000)) - }), + }, setY() { - self.y = 10 + this.y = 10 } - })) + }) const calls: string[] = [] const mware = createActionTrackingMiddleware2({ diff --git a/packages/mobx-state-tree/__tests__/core/array.test.ts b/packages/mobx-state-tree/__tests__/core/array.test.ts index ef2cfaf78..7815747fd 100644 --- a/packages/mobx-state-tree/__tests__/core/array.test.ts +++ b/packages/mobx-state-tree/__tests__/core/array.test.ts @@ -380,11 +380,11 @@ test("it should correctly handle re-adding of the same objects", () => { .model("Task", { objects: types.array(types.maybe(types.frozen())) }) - .actions((self) => ({ + .actions({ setObjects(objects: {}[]) { - self.objects.replace(objects) + this.objects.replace(objects) } - })) + }) const store = Store.create({ objects: [] }) @@ -464,11 +464,9 @@ test("#1105 - it should return pop/shift'ed values for scalar arrays", () => { .model({ array: types.array(types.number) }) - .actions((self) => { - return { - shift() { - return self.array.shift() - } + .actions({ + shift() { + return this.array.shift() } }) @@ -484,14 +482,12 @@ test("it should return pop/shift'ed values for object arrays", () => { .model({ array: types.array(TestObject) }) - .actions((self) => { - return { - shift() { - return self.array.shift() - }, - pop() { - return self.array.pop() - } + .actions({ + shift() { + return this.array.shift() + }, + pop() { + return this.array.pop() } }) @@ -541,11 +537,11 @@ test("assigning filtered instances works", () => { .model({ todos: types.array(Task) }) - .actions((self) => ({ + .actions({ clearFinishedTodos() { - self.todos = cast(self.todos.filter((todo) => !todo.done)) + this.todos = cast(this.todos.filter((todo) => !todo.done)) } - })) + }) .create({ todos: [{ done: true }, { done: false }, { done: true }] }) diff --git a/packages/mobx-state-tree/__tests__/core/async.test.ts b/packages/mobx-state-tree/__tests__/core/async.test.ts index c65ebc0ac..140630739 100644 --- a/packages/mobx-state-tree/__tests__/core/async.test.ts +++ b/packages/mobx-state-tree/__tests__/core/async.test.ts @@ -26,8 +26,9 @@ function delay(time: number, value: TV, shouldThrow = false): Promise { function testCoffeeTodo( done: () => void, generator: ( - self: any - ) => (str: string) => Generator, string | void | undefined, undefined>, + this: any, + str: string + ) => Generator, string | void | undefined, undefined>, shouldError: boolean, resultValue: string | undefined, producedCoffees: any[] @@ -37,9 +38,9 @@ function testCoffeeTodo( .model({ title: "get coffee" }) - .actions((self) => ({ - startFetch: flow(generator(self)) - })) + .actions({ + startFetch: generator + }) const events: IMiddlewareEvent[] = [] const coffees: any[] = [] const t1 = Todo.create({}) @@ -60,11 +61,12 @@ function testCoffeeTodo( done() } t1.startFetch("black").then( - (r) => { + (r: any) => { expect(shouldError).toBe(false) handleResult(r) }, - (r) => { + (r: any) => { + console.log("r", r) expect(shouldError).toBe(true) handleResult(r) } @@ -75,15 +77,15 @@ test("flow happens in single ticks", (done) => { .model({ y: 1 }) - .actions((self) => ({ - p: flow(function* () { - self.y++ - self.y++ + .actions({ + p: function* () { + this.y++ + this.y++ yield delay(1, true, false) - self.y++ - self.y++ - }) - })) + this.y++ + this.y++ + } + }) const x = X.create() const values: number[] = [] reaction( @@ -99,12 +101,11 @@ test("flow happens in single ticks", (done) => { test("can handle async actions", (done) => { testCoffeeTodo( done, - (self) => - function* fetchData(kind: string) { - self.title = "getting coffee " + kind - self.title = yield delay(100, "drinking coffee") - return "awake" - }, + function* fetchData(kind: string) { + this.title = "getting coffee " + kind + this.title = yield delay(100, "drinking coffee") + return "awake" + }, false, "awake", ["getting coffee black", "drinking coffee"] @@ -113,10 +114,9 @@ test("can handle async actions", (done) => { test("can handle erroring actions", (done) => { testCoffeeTodo( done, - (self) => - function* fetchData(kind: string) { - throw kind - }, + function* fetchData(kind: string) { + throw kind + }, true, "black", [] @@ -125,50 +125,46 @@ test("can handle erroring actions", (done) => { test("can handle try catch", (t) => { testCoffeeTodo( t, - (self) => - function* fetchData(kind: string) { - try { - yield delay(10, "tea", true) - return undefined - } catch (e) { - self.title = e - return "biscuit" - } - }, + function* fetchData(kind: string) { + try { + yield delay(10, "tea", true) + return undefined + } catch (e) { + this.title = e + return "biscuit" + } + }, false, "biscuit", ["tea"] ) }) test("empty sequence works", (t) => { - testCoffeeTodo(t, () => function* fetchData(kind: string) {}, false, undefined, []) + testCoffeeTodo(t, function* fetchData(kind: string) {}, false, undefined, []) }) test("can handle throw from yielded promise works", (t) => { testCoffeeTodo( t, - () => - function* fetchData(kind: string) { - yield delay(10, "x", true) - }, + function* fetchData(kind: string) { + yield delay(10, "x", true) + }, true, "x", [] ) }) test("typings", (done) => { - const M = types.model({ title: types.string }).actions((self) => { - function* a(x: string) { + const M = types.model({ title: types.string }).actions({ + a: function* a(x: string) { yield delay(10, "x", false) - self.title = "7" + this.title = "7" return 23 - } - // tslint:disable-next-line:no-shadowed-variable - const b = flow(function* b(x: string) { + }, + b: function* b(x: string) { yield delay(10, "x", false) - self.title = "7" + this.title = "7" return 24 - }) - return { a: flow(a), b } + } }) const m1 = M.create({ title: "test " }) const resA = m1.a("z") @@ -180,19 +176,17 @@ test("typings", (done) => { }) }) test("typings", (done) => { - const M = types.model({ title: types.string }).actions((self) => { - function* a(x: string) { + const M = types.model({ title: types.string }).actions({ + a: function* a(x: string) { yield delay(10, "x", false) - self.title = "7" + this.title = "7" return 23 - } - // tslint:disable-next-line:no-shadowed-variable - const b = flow(function* b(x: string) { + }, + b: function* b(x: string) { yield delay(10, "x", false) - self.title = "7" + this.title = "7" return 24 - }) - return { a: flow(a), b } + } }) const m1 = M.create({ title: "test " }) const resA = m1.a("z") @@ -209,15 +203,12 @@ test("recordActions should only emit invocation", (done) => { .model({ title: types.string }) - .actions((self) => { - function* a(x: string) { + .actions({ + a: function* a(x: string) { yield delay(10, "x", false) calls++ return 23 } - return { - a: flow(a) - } }) const m1 = M.create({ title: "test " }) const recorder = recordActions(m1) @@ -240,17 +231,18 @@ test("recordActions should only emit invocation", (done) => { }) test("can handle nested async actions", (t) => { // tslint:disable-next-line:no-shadowed-variable - const uppercase = flow(function* uppercase(value: string) { + const uppercase = flow(function* uppercase(value: string): any { const res = yield delay(20, value.toUpperCase()) return res }) testCoffeeTodo( t, - (self) => - function* fetchData(kind: string) { - self.title = yield uppercase("drinking " + kind) - return self.title - }, + // @ts-ignore + function* fetchData(kind: string) { + console.log("this", this) + this.title = yield uppercase("drinking " + kind) + return this.title + }, false, "DRINKING BLACK", ["DRINKING BLACK"] @@ -264,19 +256,19 @@ test("can handle nested async actions when using decorate", (done) => { } // tslint:disable-next-line:no-shadowed-variable const uppercase = flow(function* uppercase(value: string) { + // @ts-ignore const res = yield delay(20, value.toUpperCase()) return res }) - const Todo = types.model({}).actions((self) => { - // tslint:disable-next-line:no-shadowed-variable - const act = flow(function* act(value: string) { + const Todo = types.model({}).actions({ + act: decorate(middleware, function* actUppercase(value: string) { + // @ts-ignore return yield uppercase(value) }) - return { act: decorate(middleware, act) } }) Todo.create() .act("x") - .then((res) => { + .then((res: any) => { expect(res).toBe("X") expect(events).toEqual([ ["action", "act"], @@ -311,6 +303,7 @@ test("flow gain back control when node become not alive during yield", async () const p = m.doAction() destroy(m) try { + console.log("what is p", p) await p } catch (e) { expect(e).toEqual(rejectError) @@ -328,19 +321,19 @@ function filterRelevantStuff(stuff: IMiddlewareEvent[]) { test("flow typings", async () => { const promise = Promise.resolve() - const M = types.model({ x: 5 }).actions((self) => ({ + const M = types.model({ x: 5 }).actions(() => ({ // should be () => Promise - voidToVoid: flow(function* () { + voidToVoid: function* () { yield promise - }), // should be (val: number) => Promise - numberToNumber: flow(function* (val: number) { + }, // should be (val: number) => Promise + numberToNumber: function* (val: number) { yield promise return val - }), // should be () => Promise - voidToNumber: flow(function* () { + }, // should be () => Promise + voidToNumber: function* () { yield promise return Promise.resolve(2) - }) + } })) const m = M.create() @@ -352,7 +345,7 @@ test("flow typings", async () => { expect(b).toBe(4) const c: number = await m.voidToNumber() expect(c).toBe(2) - await m.voidToNumber().then((d) => { + await m.voidToNumber().then((d: any) => { const _d: number = d expect(_d).toBe(2) }) @@ -379,7 +372,7 @@ test("yield* typings for toGeneratorFunction", async () => { const numberGen = toGeneratorFunction(numberPromise) const stringWithArgsGen = toGeneratorFunction(stringWithArgsPromise) - const M = types.model({ x: 5 }).actions((self) => { + const M = types.model({ x: 5 }).actions(() => { function* testAction() { const voidResult = yield* voidGen() ensureNotAnyType(voidResult) @@ -394,14 +387,14 @@ test("yield* typings for toGeneratorFunction", async () => { } return { - testAction: flow(testAction) + testAction } }) const m = M.create() const result = await m.testAction() - ensureNotAnyType(result) + ensureNotAnyType(result as never) expect(result).toBe("test-result") }) @@ -411,7 +404,7 @@ test("yield* typings for toGenerator", async () => { const stringWithArgsPromise = (input1: string, input2: boolean) => Promise.resolve("test-result") - const M = types.model({ x: 5 }).actions((self) => { + const M = types.model({ x: 5 }).actions(() => { function* testAction() { const voidResult = yield* toGenerator(voidPromise()) ensureNotAnyType(voidResult) @@ -426,13 +419,13 @@ test("yield* typings for toGenerator", async () => { } return { - testAction: flow(testAction) + testAction } }) const m = M.create() const result = await m.testAction() - ensureNotAnyType(result) + ensureNotAnyType(result as never) expect(result).toBe("test-result") }) diff --git a/packages/mobx-state-tree/__tests__/core/boxes-store.test.ts b/packages/mobx-state-tree/__tests__/core/boxes-store.test.ts index 92e10f248..3203d2cef 100644 --- a/packages/mobx-state-tree/__tests__/core/boxes-store.test.ts +++ b/packages/mobx-state-tree/__tests__/core/boxes-store.test.ts @@ -19,26 +19,24 @@ export const Box = types x: 0, y: 0 }) - .views((self) => ({ + .views({ + // @ts-ignore get width() { - return self.name.length * 15 + // @ts-ignore + return this.name.length * 15 }, get isSelected(): boolean { - if (!hasParent(self)) return false - return getParent(getParent(self)).selection === self + if (!hasParent(this)) return false + return getParent(getParent(this)).selection === this } - })) - .actions((self) => { - function move(dx: number, dy: number) { - self.x += dx - self.y += dy - } - function setName(newName: string) { - self.name = newName - } - return { - move, - setName + }) + .actions({ + move(dx: number, dy: number) { + this.x += dx + this.y += dy + }, + setName(newName: string) { + this.name = newName } }) export const Arrow = types.model("Arrow", { @@ -52,22 +50,22 @@ export const Store = types arrows: types.array(Arrow), selection: types.reference(Box) }) - .actions((self) => { - function afterCreate() { - unprotect(self) - } - function addBox(id: string, name: string, x: number, y: number) { + .actions({ + afterCreate() { + unprotect(this) + }, + addBox(id: string, name: string, x: number, y: number) { const box = Box.create({ name, x, y, id }) - self.boxes.put(box) + this.boxes.put(box) return box - } - function addArrow(id: string, from: string, to: string) { - self.arrows.push(Arrow.create({ id, from, to })) - } - function setSelection(selection: Instance) { - self.selection = selection - } - function createBox( + }, + addArrow(id: string, from: string, to: string) { + this.arrows.push(Arrow.create({ id, from, to })) + }, + setSelection(selection: Instance) { + this.selection = selection + }, + createBox( id: string, name: string, x: number, @@ -75,16 +73,9 @@ export const Store = types source: Instance | null | undefined, arrowId: string | null ) { - const box = addBox(id, name, x, y) - setSelection(box) - if (source) addArrow(arrowId!, source.id, box.id) - } - return { - afterCreate, - addBox, - addArrow, - setSelection, - createBox + const box = this.addBox(id, name, x, y) + this.setSelection(box) + if (source) this.addArrow(arrowId!, source.id, box.id) } }) function createStore() { diff --git a/packages/mobx-state-tree/__tests__/core/env.test.ts b/packages/mobx-state-tree/__tests__/core/env.test.ts index 71ff6886c..45ce24d1e 100644 --- a/packages/mobx-state-tree/__tests__/core/env.test.ts +++ b/packages/mobx-state-tree/__tests__/core/env.test.ts @@ -23,11 +23,11 @@ const Todo = types .model({ title: "test" }) - .views((self) => ({ + .views({ get description() { - return getEnv(self).useUppercase ? self.title.toUpperCase() : self.title + return getEnv(this).useUppercase ? this.title.toUpperCase() : this.title } - })) + }) const Store = types.model({ todos: types.array(Todo) }) diff --git a/packages/mobx-state-tree/__tests__/core/frozen.test.ts b/packages/mobx-state-tree/__tests__/core/frozen.test.ts index 3618b70fa..bec829451 100644 --- a/packages/mobx-state-tree/__tests__/core/frozen.test.ts +++ b/packages/mobx-state-tree/__tests__/core/frozen.test.ts @@ -38,12 +38,12 @@ test("it should type strongly", () => { .model({ loc: types.frozen() }) - .actions((self) => ({ + .actions({ moveABit() { - // self.loc.x += 1; // compile error, x is readonly! - ;(self.loc as any).x += 1 // throws, frozen! + // this.loc.x += 1; // compile error, x is readonly! + ;(this.loc as any).x += 1 // throws, frozen! } - })) + }) expect(Mouse.is({})).toBeTruthy() // any value is acceptable to frozen, even undefined... diff --git a/packages/mobx-state-tree/__tests__/core/hooks.test.ts b/packages/mobx-state-tree/__tests__/core/hooks.test.ts index 4ec842a6a..f270341cd 100644 --- a/packages/mobx-state-tree/__tests__/core/hooks.test.ts +++ b/packages/mobx-state-tree/__tests__/core/hooks.test.ts @@ -19,50 +19,40 @@ function createTestStore(listener: (s: string) => void) { .model("Todo", { title: "" }) - .actions((self) => { - function afterCreate() { - listener("new todo: " + self.title) - addDisposer(self, () => { - listener("custom disposer 1 for " + self.title) + .actions({ + afterCreate() { + listener("new todo: " + this.title) + addDisposer(this, () => { + listener("custom disposer 1 for " + this.title) }) - addDisposer(self, () => { - listener("custom disposer 2 for " + self.title) + addDisposer(this, () => { + listener("custom disposer 2 for " + this.title) }) - } - function beforeDestroy() { - listener("destroy todo: " + self.title) - } - function afterAttach() { - listener("attach todo: " + self.title) - } - function beforeDetach() { - listener("detach todo: " + self.title) - } - return { - afterCreate, - beforeDestroy, - afterAttach, - beforeDetach + }, + beforeDestroy() { + listener("destroy todo: " + this.title) + }, + afterAttach() { + listener("attach todo: " + this.title) + }, + beforeDetach() { + listener("detach todo: " + this.title) } }) const Store = types .model("Store", { todos: types.array(Todo) }) - .actions((self) => { - function afterCreate() { - unprotect(self) - listener("new store: " + self.todos.length) - addDisposer(self, () => { + .actions({ + afterCreate() { + unprotect(this) + listener("new store: " + this.todos.length) + addDisposer(this, () => { listener("custom disposer for store") }) - } - function beforeDestroy() { - listener("destroy store: " + self.todos.length) - } - return { - afterCreate, - beforeDestroy + }, + beforeDestroy() { + listener("destroy store: " + this.todos.length) } }) return { @@ -148,25 +138,25 @@ test("lifecycle hooks can access their children", () => { .model("Todo", { title: "" }) - .actions((self) => ({ + .actions({ afterCreate() { - listener("new child: " + self.title) + listener("new child: " + this.title) }, afterAttach() { - listener("parent available: " + !!getParent(self)) + listener("parent available: " + !!getParent(this)) } - })) + }) const Parent = types .model("Parent", { child: Child }) - .actions((self) => ({ + .actions({ afterCreate() { // **This the key line**: it is trying to access the child - listener("new parent, child.title: " + self.child?.title) + listener("new parent, child.title: " + this.child?.title) } - })) + }) const Store = types.model("Store", { parent: types.maybe(Parent) @@ -293,35 +283,33 @@ test("base hooks can be composed", () => { } const Todo = types .model("Todo", { title: "" }) - .actions((self) => { - function afterCreate() { + .actions({ + afterCreate() { listener("aftercreate1") - } - function beforeDestroy() { + }, + beforeDestroy() { listener("beforedestroy1") - } - function afterAttach() { + }, + afterAttach() { listener("afterattach1") - } - function beforeDetach() { + }, + beforeDetach() { listener("beforedetach1") } - return { afterCreate, beforeDestroy, afterAttach, beforeDetach } }) - .actions((self) => { - function afterCreate() { + .actions({ + afterCreate() { listener("aftercreate2") - } - function beforeDestroy() { + }, + beforeDestroy() { listener("beforedestroy2") - } - function afterAttach() { + }, + afterAttach() { listener("afterattach2") - } - function beforeDetach() { + }, + beforeDetach() { listener("beforedetach2") } - return { afterCreate, beforeDestroy, afterAttach, beforeDetach } }) const Store = types.model("Store", { todos: types.array(Todo) }) const store = Store.create({ todos: [] }) @@ -366,8 +354,8 @@ test("snapshot processors can be composed", () => { test("addDisposer must return the passed disposer", () => { const listener = jest.fn() - const M = types.model({}).actions((self) => { - expect(addDisposer(self, listener)).toBe(listener) + const M = types.model({}).actions(() => { + expect(addDisposer(this, listener)).toBe(listener) return {} }) M.create() @@ -379,7 +367,7 @@ test("array calls all hooks", () => { events.push(message) } const Item = types.model("Item", { id: types.string }) - const Collection = types.array(Item).hooks((self) => ({ + const Collection = types.array(Item).hooks({ afterCreate() { listener("afterCreate") }, @@ -392,7 +380,7 @@ test("array calls all hooks", () => { beforeDestroy() { listener("beforeDestroy") } - })) + }) const Holder = types.model("Holder", { items: types.maybe(Collection) }) const collection = Collection.create([{ id: "1" }, { id: "2" }, { id: "3" }]) @@ -420,7 +408,7 @@ test("map calls all hooks", () => { events.push(message) } const Item = types.model("Item", { id: types.string }) - const Collection = types.map(Item).hooks((self) => ({ + const Collection = types.map(Item).hooks({ afterCreate() { listener("afterCreate") }, @@ -433,7 +421,7 @@ test("map calls all hooks", () => { beforeDestroy() { listener("beforeDestroy") } - })) + }) const Holder = types.model("Holder", { items: types.maybe(Collection) }) const collection = Collection.create({ "1": { id: "1" }, "2": { id: "2" }, "3": { id: "3" } }) diff --git a/packages/mobx-state-tree/__tests__/core/identifier.test.ts b/packages/mobx-state-tree/__tests__/core/identifier.test.ts index 57853ed8e..c1dd5091c 100644 --- a/packages/mobx-state-tree/__tests__/core/identifier.test.ts +++ b/packages/mobx-state-tree/__tests__/core/identifier.test.ts @@ -20,20 +20,20 @@ if (process.env.NODE_ENV !== "production") { (identifier) => identifier.indexOf("Model_") === 0 ) }) - .actions((self) => ({ + .actions({ setId(id: string) { - self.id = id + this.id = id } - })) + }) const ParentModel = types .model("ParentModel", { models: types.array(Model) }) - .actions((self) => ({ + .actions({ addModel(model: SnapshotOrInstance) { - self.models.push(model) + this.models.push(model) } - })) + }) expect(() => { ParentModel.create({ models: [{ id: "WrongId_1" }] }) }).toThrow() @@ -233,18 +233,18 @@ test("#1019", () => { .model("CommentStore", { items: types.array(CommentModel) }) - .actions((self) => ({ + .actions({ test1() { expect(calls).toBe(0) const comment = CommentModel.create({}) expect(calls).toBe(1) - self.items.push(comment) - const item = resolveIdentifier(CommentModel, self.items, comment.uid) - const item2 = self.items.find((i) => i.uid === comment.uid) + this.items.push(comment) + const item = resolveIdentifier(CommentModel, this.items, comment.uid) + const item2 = this.items.find((i) => i.uid === comment.uid) expect(item).toBe(item2) } - })) + }) const c = CommentStore.create({}) c.test1() @@ -271,11 +271,11 @@ test("identifierAttribute of the type", () => { test("items detached from arrays don't corrupt identifierCache", () => { const Item = types.model("Item", { id: types.identifier }) - const ItemArray = types.model("ItemArray", { items: types.array(Item) }).actions((self) => ({ + const ItemArray = types.model("ItemArray", { items: types.array(Item) }).actions({ removeSecondItemWithDetach() { - detach(self.items[1]) + detach(this.items[1]) } - })) + }) const smallArray = ItemArray.create({ items: [{ id: "A" }, { id: "B" }, { id: "C" }, { id: "D" }, { id: "E" }] diff --git a/packages/mobx-state-tree/__tests__/core/lazy.test.ts b/packages/mobx-state-tree/__tests__/core/lazy.test.ts index 0d38edd48..f12490eea 100644 --- a/packages/mobx-state-tree/__tests__/core/lazy.test.ts +++ b/packages/mobx-state-tree/__tests__/core/lazy.test.ts @@ -11,11 +11,11 @@ test("it should load the correct type", async () => { width: types.number, height: types.number }) - .views((self) => ({ + .views({ get area() { - return self.height * self.width + return this.height * this.width } - })) + }) const Root = types .model("Root", { @@ -25,11 +25,11 @@ test("it should load the correct type", async () => { shouldLoadPredicate: (parent) => parent.shouldLoad == true }) }) - .actions((self) => ({ - load: () => { - self.shouldLoad = true + .actions({ + load: function () { + this.shouldLoad = true } - })) + }) const store = Root.create({ lazyModel: { @@ -60,12 +60,12 @@ test("maintains the tree structure when loaded", async () => { width: types.number, height: types.number }) - .views((self) => ({ + .views({ get area() { - const root = getRoot<{ rootValue: number }>(self) - return self.height * self.width * root.rootValue + const root = getRoot<{ rootValue: number }>(this) + return this.height * this.width * root.rootValue } - })) + }) const Root = types .model("Root", { @@ -80,11 +80,11 @@ test("maintains the tree structure when loaded", async () => { return 5 } })) - .actions((self) => ({ - load: () => { - self.shouldLoad = true + .actions({ + load: function () { + this.shouldLoad = true } - })) + }) const store = Root.create({ lazyModel: { diff --git a/packages/mobx-state-tree/__tests__/core/map.test.ts b/packages/mobx-state-tree/__tests__/core/map.test.ts index 25ed767e9..37137fd7c 100644 --- a/packages/mobx-state-tree/__tests__/core/map.test.ts +++ b/packages/mobx-state-tree/__tests__/core/map.test.ts @@ -198,12 +198,9 @@ test("#192 - put should not throw when identifier is a number", () => { .model("TodoStore", { todos: types.optional(types.map(Todo), {}) }) - .actions((self) => { - function addTodo(todo: typeof Todo.Type | typeof Todo.CreationType) { - self.todos.put(todo) - } - return { - addTodo + .actions({ + addTodo(todo: typeof Todo.Type | typeof Todo.CreationType) { + this.todos.put(todo) } }) const todoStore = TodoStore.create({}) @@ -227,12 +224,9 @@ test("#192 - map should not mess up keys when putting twice", () => { .model("TodoStore", { todos: types.optional(types.map(Todo), {}) }) - .actions((self) => { - function addTodo(todo: typeof Todo.Type | typeof Todo.CreationType) { - self.todos.put(todo) - } - return { - addTodo + .actions({ + addTodo(todo: typeof Todo.Type | typeof Todo.CreationType) { + this.todos.put(todo) } }) const todoStore = TodoStore.create({}) @@ -260,12 +254,9 @@ test("#694 - map.put should return new node", () => { .model("TodoStore", { todos: types.map(Todo) }) - .actions((self) => { - function addAndReturnTodo(todo: typeof Todo.Type | typeof Todo.CreationType) { - return self.todos.put(todo) - } - return { - addAndReturnTodo + .actions({ + addAndReturnTodo(todo: typeof Todo.Type | typeof Todo.CreationType) { + return this.todos.put(todo) } }) const todoStore = TodoStore.create({ todos: {} }) @@ -301,12 +292,9 @@ test("it should not throw when removing a non existing item from a map", () => { .model({ myMap: types.map(types.number) }) - .actions((self) => { - function something() { - return self.myMap.delete("1020") - } - return { - something + .actions({ + something() { + return this.myMap.delete("1020") } }) const store = AppModel.create() @@ -318,11 +306,11 @@ test("it should get map keys from reversePatch when deleted an item from a neste .model({ value: types.map(types.map(types.map(types.number))) }) - .actions((self) => ({ + .actions({ remove(k: string) { - self.value.delete(k) + this.value.delete(k) } - })) + }) const store = AppModel.create({ value: { a: { b: { c: 10 } } } }) onPatch(store, (patch, reversePatch) => { expect(patch).toEqual({ op: "remove", path: "/value/a" }) @@ -361,15 +349,15 @@ test("issue #876 - map.put works fine for models with preProcessSnapshot", () => .model("Store", { items: types.optional(types.map(Item), {}) }) - .actions((self) => ({ + .actions({ afterCreate() { - self.items.put({ + this.items.put({ id: "1", title: "", notes: [{ text: "first note" }, { text: "second note" }] }) } - })) + }) let store!: typeof Store.Type expect(() => { @@ -412,12 +400,9 @@ test("get should return value when key is a number", () => { .model("TodoStore", { todos: types.optional(types.map(Todo), {}) }) - .actions((self) => { - function addTodo(aTodo: typeof Todo.Type | typeof Todo.CreationType) { - self.todos.put(aTodo) - } - return { - addTodo + .actions({ + addTodo(aTodo: typeof Todo.Type | typeof Todo.CreationType) { + this.todos.put(aTodo) } }) const todoStore = TodoStore.create({}) @@ -428,7 +413,7 @@ test("get should return value when key is a number", () => { } todoStore.addTodo(todo) - expect(todoStore.todos.get((1 as any) as string)!.title).toEqual("Test") + expect(todoStore.todos.get(1 as any as string)!.title).toEqual("Test") }) test("numeric keys should work", () => { @@ -447,30 +432,30 @@ test("numeric keys should work", () => { unprotect(s) s.mies.set(7 as any, { id: "7" }) - const i7 = s.mies.get((7 as any) as string)! + const i7 = s.mies.get(7 as any as string)! expect(i7.title).toBe("test") expect(s.mies.has("7")).toBeTruthy() - expect(s.mies.has((7 as any) as string)).toBeTruthy() + expect(s.mies.has(7 as any as string)).toBeTruthy() expect(s.mies.get("7")).toBeTruthy() - expect(s.mies.get((7 as any) as string)).toBeTruthy() + expect(s.mies.get(7 as any as string)).toBeTruthy() s.mies.set("8", { id: "8" }) expect(s.mies.has("8")).toBeTruthy() - expect(s.mies.has((8 as any) as string)).toBeTruthy() + expect(s.mies.has(8 as any as string)).toBeTruthy() expect(s.mies.get("8")).toBeTruthy() - expect(s.mies.get((8 as any) as string)).toBeTruthy() + expect(s.mies.get(8 as any as string)).toBeTruthy() expect(Array.from(s.mies.keys())).toEqual(["7", "8"]) s.mies.put({ id: "7", title: "coffee" }) expect(s.mies.size).toBe(2) expect(s.mies.has("7")).toBeTruthy() - expect(s.mies.has((7 as any) as string)).toBeTruthy() + expect(s.mies.has(7 as any as string)).toBeTruthy() expect(s.mies.get("7")).toBeTruthy() - expect(s.mies.get((7 as any) as string)).toBeTruthy() + expect(s.mies.get(7 as any as string)).toBeTruthy() expect(i7.title).toBe("coffee") - expect(s.mies.delete((8 as any) as string)).toBeTruthy() + expect(s.mies.delete(8 as any as string)).toBeTruthy() expect(s.mies.size).toBe(1) }) @@ -479,11 +464,11 @@ describe("#826, adding stuff twice", () => { .model({ map: types.optional(types.map(types.boolean), {}) }) - .actions((self) => ({ - toogleMap: (id: string) => { - self.map.set(id, !self.map.get(id)) + .actions({ + toogleMap(id: string) { + this.map.set(id, !this.map.get(id)) } - })) + }) // This one pass fine 👍 test("Toogling once shouldn't throw", () => { @@ -516,10 +501,10 @@ test("#751 restore from snapshot should work", () => { // We add an item with a number id unprotect(server) - server.map.set((1 as any) as string, { id: 1 }) + server.map.set(1 as any as string, { id: 1 }) // We can access it using a number - expect(server.map.get((1 as any) as string)!.id).toBe(1) + expect(server.map.get(1 as any as string)!.id).toBe(1) // But if we get a snapshot... const snapshot = getSnapshot(server) @@ -530,7 +515,7 @@ test("#751 restore from snapshot should work", () => { expect(server.map.get("1")!.id).toBe(1) // And as number - expect(server.map.get((1 as any) as string)!.id).toBe(1) + expect(server.map.get(1 as any as string)!.id).toBe(1) expect(server.map.size).toBe(1) }) diff --git a/packages/mobx-state-tree/__tests__/core/model.test.ts b/packages/mobx-state-tree/__tests__/core/model.test.ts index fe131bacd..9ea57f47b 100644 --- a/packages/mobx-state-tree/__tests__/core/model.test.ts +++ b/packages/mobx-state-tree/__tests__/core/model.test.ts @@ -202,11 +202,12 @@ describe("Model instantiation", () => { id: types.identifier, name: types.string }) - .views((user) => ({ + .views({ + // @ts-ignore get name() { - return user.name + return this.name } - })) + }) expect(() => UserModel.create({ diff --git a/packages/mobx-state-tree/__tests__/core/node.test.ts b/packages/mobx-state-tree/__tests__/core/node.test.ts index 79fbeea40..e0be0329f 100644 --- a/packages/mobx-state-tree/__tests__/core/node.test.ts +++ b/packages/mobx-state-tree/__tests__/core/node.test.ts @@ -290,12 +290,9 @@ test("make sure array filter works properly", () => { .model({ rows: types.optional(types.array(Row), []) }) - .actions((self) => { - function clearDone() { - self.rows.filter((row) => row.done === true).forEach(destroy) - } - return { - clearDone + .actions({ + clearDone() { + this.rows.filter((row) => row.done === true).forEach(destroy) } }) const doc = Document.create() @@ -331,12 +328,9 @@ test("it can record and replay actions", () => { .model({ article_id: 0 }) - .actions((self) => { - function setArticle(article_id: number) { - self.article_id = article_id - } - return { - setArticle + .actions({ + setArticle(article_id: number) { + this.article_id = article_id } }) const Document = types @@ -344,16 +338,12 @@ test("it can record and replay actions", () => { customer_id: 0, rows: types.optional(types.array(Row), []) }) - .actions((self) => { - function setCustomer(customer_id: number) { - self.customer_id = customer_id - } - function addRow() { - self.rows.push(Row.create()) - } - return { - setCustomer, - addRow + .actions({ + setCustomer(customer_id: number) { + this.customer_id = customer_id + }, + addRow() { + this.rows.push(Row.create()) } }) const source = Document.create() @@ -373,18 +363,17 @@ test("Liveliness issue #683", () => { .model({ list: types.map(User) }) - .actions((self) => ({ + .actions({ put(aUser: typeof User.CreationType | typeof User.Type) { - // if (self.has(user.id)) detach(self.get(user.id)); - self.list.put(aUser) + this.list.put(aUser) }, get(id: string) { - return self.list.get(id) + return this.list.get(id) }, has(id: string) { - return self.list.has(id) + return this.list.has(id) } - })) + }) const users = Users.create({ list: { @@ -407,11 +396,11 @@ test("triggers on changing paths - 1", () => { .model({ todos: types.array(Todo) }) - .actions((self) => ({ + .actions({ do(fn: () => void) { fn() } - })) + }) const t1 = Todo.create({ title: "t1 " }) const t2 = Todo.create({ title: "t2 " }) diff --git a/packages/mobx-state-tree/__tests__/core/object.test.ts b/packages/mobx-state-tree/__tests__/core/object.test.ts index b5675d7e0..46fe94f01 100644 --- a/packages/mobx-state-tree/__tests__/core/object.test.ts +++ b/packages/mobx-state-tree/__tests__/core/object.test.ts @@ -27,12 +27,9 @@ const createTestFactories = () => { .model({ to: "world" }) - .actions((self) => { - function setTo(to: string) { - self.to = to - } - return { - setTo + .actions({ + setTo(to: string) { + this.to = to } }) const ComputedFactory = types @@ -40,30 +37,26 @@ const createTestFactories = () => { width: 100, height: 200 }) - .views((self) => ({ + .views({ get area() { - return self.width * self.height + return this.width * this.height } - })) + }) const ComputedFactory2 = types .model({ props: types.map(types.number) }) - .views((self) => ({ + .views({ get area() { - return self.props.get("width")! * self.props.get("height")! - } - })) - .actions((self) => { - function setWidth(value: number) { - self.props.set("width", value) + return this.props.get("width")! * this.props.get("height")! } - function setHeight(value: number) { - self.props.set("height", value) - } - return { - setWidth, - setHeight + }) + .actions({ + setWidth(value: number) { + this.props.set("width", value) + }, + setHeight(value: number) { + this.props.set("height", value) } }) const BoxFactory = types.model({ @@ -81,22 +74,22 @@ const createFactoryWithChildren = () => { .model("File", { name: types.string }) - .actions((self) => ({ + .actions({ rename(value: string) { - self.name = value + this.name = value } - })) + }) const Folder = types .model("Folder", { name: types.string, files: types.array(File) }) - .actions((self) => ({ + .actions({ rename(value: string) { - self.name = value + this.name = value } - })) + }) return Folder } // === FACTORY TESTS === @@ -284,17 +277,17 @@ test("it should throw if a replaced object is read or written to", () => { .model("Sub", { title: "test2" }) - .actions((self) => ({ + .actions({ fn2() {} - })), + }), {} ) }) - .actions((self) => ({ + .actions({ fn() { - self.sub.fn2() + this.sub.fn2() } - })) + }) const Store = types.model("Store", { todo: Todo }) @@ -403,11 +396,8 @@ test("it should warn if a replaced object is read or written to", () => { .model("Todo", { title: "test" }) - .actions((self) => { - function fn() {} - return { - fn - } + .actions({ + fn() {} }) const Store = types.model("Store", { todo: Todo @@ -461,19 +451,16 @@ test("methods get overridden by compose", () => { .model({ count: types.optional(types.number, 0) }) - .actions((self) => { - function increment() { - self.count += 1 - } - return { - increment + .actions({ + increment() { + this.count += 1 } }) - const B = A.actions((self) => ({ + const B = A.actions({ increment() { - self.count += 10 + this.count += 10 } - })) + }) const store = B.create() expect(getSnapshot(store)).toEqual({ count: 0 }) expect(store.count).toBe(0) @@ -496,22 +483,19 @@ test("models should expose their actions to be used in a composable way", () => .model({ count: types.optional(types.number, 0) }) - .actions((self) => { - function increment() { - self.count += 1 - } - return { - increment + .actions({ + increment() { + this.count += 1 } }) const B = A.props({ called: types.optional(types.number, 0) - }).actions((self) => { - const baseIncrement = self.increment + }).actions(function () { + const baseIncrement = this.increment return { increment() { baseIncrement() - self.called += 1 + this.called += 1 } } }) @@ -528,18 +512,18 @@ test("compose should be overwrite", () => { name: "", alias: "" }) - .views((self) => ({ + .views({ get displayName() { - return self.alias || self.name + return this.alias || this.name } - })) + }) const B = A.props({ type: "" - }).views((self) => ({ + }).views({ get displayName() { - return self.alias || self.name + self.type + return this.alias || this.name + this.type } - })) + }) const storeA = A.create({ name: "nameA", alias: "aliasA" }) const storeB = B.create({ name: "nameB", alias: "aliasB", type: "typeB" }) const storeC = B.create({ name: "nameC", type: "typeC" }) @@ -615,11 +599,11 @@ test("view functions should be tracked", () => { .model({ x: 3 }) - .views((self) => ({ + .views({ doubler() { - return self.x * 2 + return this.x * 2 } - })) + }) .create() unprotect(model) const values: number[] = [] @@ -634,17 +618,14 @@ test("view functions should not be allowed to change state", () => { .model({ x: 3 }) - .views((self) => ({ + .views({ doubler() { - self.x *= 2 - } - })) - .actions((self) => { - function anotherDoubler() { - self.x *= 2 + this.x *= 2 } - return { - anotherDoubler + }) + .actions({ + anotherDoubler() { + this.x *= 2 } }) .create() @@ -719,7 +700,7 @@ if (process.env.NODE_ENV !== "production") { }) } test("it should be possible to share states between views and actions using enhance", () => { - const A = types.model({}).extend((self) => { + const A = types.model({}).extend(() => { const localState = observable.box(3) return { views: { @@ -794,50 +775,50 @@ test("#967 - changing values in afterCreate/afterAttach when node is instantiate title: types.string, selected: false }) - .actions((self) => ({ + .actions({ toggle() { - self.selected = !self.selected + this.selected = !this.selected } - })) + }) const Question = types .model("Question", { title: types.string, answers: types.array(Answer) }) - .views((self) => ({ + .views({ get brokenView() { // this should not be allowed // MWE: disabled, MobX 6 no longer forbids this // expect(() => { - // self.answers[0].toggle() + // this.answers[0].toggle() // }).toThrow() return 0 } - })) - .actions((self) => ({ + }) + .actions({ afterCreate() { // we should allow changes even when inside a computed property when done inside afterCreate/afterAttach - self.answers[0].toggle() + this.answers[0].toggle() // but not further computed changes - expect(self.brokenView).toBe(0) + expect(this.brokenView).toBe(0) }, afterAttach() { // we should allow changes even when inside a computed property when done inside afterCreate/afterAttach - self.answers[0].toggle() - expect(self.brokenView).toBe(0) + this.answers[0].toggle() + expect(this.brokenView).toBe(0) } - })) + }) const Product = types .model("Product", { questions: types.array(Question) }) - .views((self) => ({ + .views({ get selectedAnswers() { const result = [] - for (const question of self.questions) { + for (const question of this.questions) { result.push(question.answers.find((a) => a.selected)) } return result } - })) + }) const product = Product.create({ questions: [ @@ -856,38 +837,38 @@ test("#993-1 - after attach should have a parent when accesing a reference direc id: types.identifier, finished: false }) - .actions((self) => ({ + .actions({ afterAttach() { - expect(getParent(self)).toBeTruthy() + expect(getParent(this)).toBeTruthy() } - })) + }) - const L3 = types.model({ l4: L4 }).actions((self) => ({ + const L3 = types.model({ l4: L4 }).actions({ afterAttach() { - expect(getParent(self)).toBeTruthy() + expect(getParent(this)).toBeTruthy() } - })) + }) const L2 = types .model({ l3: L3 }) - .actions((self) => ({ + .actions({ afterAttach() { - expect(getParent(self)).toBeTruthy() + expect(getParent(this)).toBeTruthy() } - })) + }) const L1 = types .model({ l2: L2, selected: types.reference(L4) }) - .actions((self) => ({ + .actions({ afterAttach() { throw fail("should never be called") } - })) + }) const createL1 = () => L1.create({ @@ -926,9 +907,9 @@ test("#993-2 - references should have a parent even when the parent has not been id: types.identifier, finished: false }) - .actions((self) => ({ + .actions({ toggle() { - self.finished = !self.finished + this.finished = !this.finished }, afterCreate() { events.push("l4-ac") @@ -936,43 +917,43 @@ test("#993-2 - references should have a parent even when the parent has not been afterAttach() { events.push("l4-at") } - })) + }) - const L3 = types.model({ l4: L4 }).actions((self) => ({ + const L3 = types.model({ l4: L4 }).actions({ afterCreate() { events.push("l3-ac") }, afterAttach() { events.push("l3-at") } - })) + }) const L2 = types .model({ l3: L3 }) - .actions((self) => ({ + .actions({ afterCreate() { events.push("l2-ac") }, afterAttach() { events.push("l2-at") } - })) + }) const L1 = types .model({ l2: L2, selected: types.reference(L4) }) - .actions((self) => ({ + .actions({ afterCreate() { events.push("l1-ac") }, afterAttach() { events.push("l1-at") } - })) + }) const createL1 = () => L1.create({ @@ -1147,11 +1128,11 @@ test("#1112 - identifier cache should be cleared for unaccessed wrapped objects" list: types.optional(types.array(Wrapper), []), selectedId: 2 }) - .views((self) => ({ + .views({ get selectedEntity() { - return resolveIdentifier(Entity, self, self.selectedId) + return resolveIdentifier(Entity, this, this.selectedId) } - })) + }) const store = Store.create() unprotect(store) @@ -1182,11 +1163,11 @@ test("#1702 - should not throw with useProxies: 'ifavailable'", () => { useProxies: "ifavailable" }) - const M = types.model({ x: 5 }).views((self) => ({ + const M = types.model({ x: 5 }).views({ get y() { - return self.x + return this.x } - })) + }) expect(() => { M.create({}) diff --git a/packages/mobx-state-tree/__tests__/core/parent-properties.test.ts b/packages/mobx-state-tree/__tests__/core/parent-properties.test.ts index de8e8b9da..9948c3841 100644 --- a/packages/mobx-state-tree/__tests__/core/parent-properties.test.ts +++ b/packages/mobx-state-tree/__tests__/core/parent-properties.test.ts @@ -6,32 +6,32 @@ const ChildModel = types parentEnvIsNullAfterCreate: false, parentPropertyIsNullAfterAttach: false }) - .views((self) => { - return { - get parent(): IParentModelInstance { - return getParent(self) - } + .views({ + get parent(): IParentModelInstance { + return getParent(this) } }) - .actions((self) => ({ + .actions({ afterCreate() { - self.parentPropertyIsNullAfterCreate = typeof self.parent.fetch === "undefined" - self.parentEnvIsNullAfterCreate = typeof getEnv(self.parent).fetch === "undefined" + // @ts-ignore + this.parentPropertyIsNullAfterCreate = typeof this.parent.fetch === "undefined" + this.parentEnvIsNullAfterCreate = typeof getEnv(this.parent).fetch === "undefined" }, afterAttach() { - self.parentPropertyIsNullAfterAttach = typeof self.parent.fetch === "undefined" + // @ts-ignore + this.parentPropertyIsNullAfterAttach = typeof this.parent.fetch === "undefined" } - })) + }) const ParentModel = types .model("Parent", { child: types.optional(ChildModel, {}) }) - .views((self) => ({ + .views({ get fetch() { - return getEnv(self).fetch + return getEnv(this).fetch } - })) + }) interface IParentModelInstance extends Instance {} @@ -58,16 +58,16 @@ test("#917", () => { title: types.string, finished: false }) - .views((self) => ({ + .views({ get path() { - return getPath(self) + return getPath(this) } - })) - .actions((self) => ({ + }) + .actions({ toggle() { - self.finished = !self.finished + this.finished = !this.finished } - })) + }) const Todo = types .model("Todo", { @@ -76,29 +76,29 @@ test("#917", () => { finished: false, subTodos: types.array(SubTodo) }) - .views((self) => ({ + .views({ get path() { - return getPath(self) + return getPath(this) } - })) - .actions((self) => ({ + }) + .actions({ toggle() { - self.finished = !self.finished + this.finished = !this.finished } - })) + }) const TodoStore = types .model("TodoStore", { todos: types.array(Todo) }) - .views((self) => ({ + .views({ get unfinishedTodoCount() { - return self.todos.filter((todo) => !todo.finished).length + return this.todos.filter((todo) => !todo.finished).length } - })) - .actions((self) => ({ + }) + .actions({ addTodo(title: string) { - self.todos.push({ + this.todos.push({ title, subTodos: [ { @@ -107,7 +107,7 @@ test("#917", () => { ] }) } - })) + }) const store2 = TodoStore.create({ todos: [ diff --git a/packages/mobx-state-tree/__tests__/core/primitives.test.ts b/packages/mobx-state-tree/__tests__/core/primitives.test.ts index 1d3abb77b..76df6be25 100644 --- a/packages/mobx-state-tree/__tests__/core/primitives.test.ts +++ b/packages/mobx-state-tree/__tests__/core/primitives.test.ts @@ -13,16 +13,12 @@ test("Date instance can be reused", () => { one: Model, index: types.array(Model) }) - .actions((self) => { - function set(one: typeof Model.Type) { - self.one = one - } - function push(model: typeof Model.Type) { - self.index.push(model) - } - return { - set, - push + .actions({ + set(one: typeof Model.Type) { + this.one = one + }, + push(model: typeof Model.Type) { + this.index.push(model) } }) const object = { a: { b: "string" }, c: new Date() } // string -> date (number) diff --git a/packages/mobx-state-tree/__tests__/core/protect.test.ts b/packages/mobx-state-tree/__tests__/core/protect.test.ts index 81bbc5bcb..bdb192f7b 100644 --- a/packages/mobx-state-tree/__tests__/core/protect.test.ts +++ b/packages/mobx-state-tree/__tests__/core/protect.test.ts @@ -4,12 +4,9 @@ const Todo = types .model("Todo", { title: "" }) - .actions((self) => { - function setTitle(newTitle: string) { - self.title = newTitle - } - return { - setTitle + .actions({ + setTitle(newTitle: string) { + this.title = newTitle } }) const Store = types.model("Store", { @@ -108,11 +105,11 @@ test("action cannot modify parent", () => { .model("Child", { x: 2 }) - .actions((self) => ({ + .actions({ setParentX() { - getParent(self).x += 1 + getParent(this).x += 1 } - })) + }) const Parent = types.model("Parent", { x: 3, child: Child diff --git a/packages/mobx-state-tree/__tests__/core/reference-custom.test.ts b/packages/mobx-state-tree/__tests__/core/reference-custom.test.ts index 5d165ddb6..69d3b44b1 100644 --- a/packages/mobx-state-tree/__tests__/core/reference-custom.test.ts +++ b/packages/mobx-state-tree/__tests__/core/reference-custom.test.ts @@ -139,31 +139,31 @@ test("it should support dynamic loading", (done) => { users: types.array(User), selection: UserByNameReference }) - .actions((self) => ({ - loadUser: flow(function* loadUser(name: string) { + .actions({ + loadUser: function* loadUser(name: string) { events.push("loading " + name) - self.users.push({ name }) + this.users.push({ name }) yield new Promise((resolve) => { setTimeout(resolve, 200) }) events.push("loaded " + name) - const user = (self.users.find((u) => u.name === name)!.age = name.length * 3) // wonderful! - }) - })) - .views((self) => ({ + const user = (this.users.find((u) => u.name === name)!.age = name.length * 3) // wonderful! + } + }) + .views({ // Important: a view so that the reference will automatically react to the reference being changed! getOrLoadUser(name: string) { - const user = self.users.find((u) => u.name === name) || null + const user = this.users.find((u) => u.name === name) || null if (!user) { /* TODO: this is ugly, but workaround the idea that views should be side effect free. We need a more elegant solution.. */ - setImmediate(() => self.loadUser(name)) + setImmediate(() => this.loadUser(name)) } return user } - })) + }) const s = Store.create({ users: [], selection: "Mattia" diff --git a/packages/mobx-state-tree/__tests__/core/reference-onInvalidated.test.ts b/packages/mobx-state-tree/__tests__/core/reference-onInvalidated.test.ts index 4310e1914..261d4b55b 100644 --- a/packages/mobx-state-tree/__tests__/core/reference-onInvalidated.test.ts +++ b/packages/mobx-state-tree/__tests__/core/reference-onInvalidated.test.ts @@ -321,11 +321,11 @@ test("#1115 - safe reference doesn't become invalidated when the reference has n mapOfRef: types.map(MyRefModel), arrayOfSafeRef: types.array(SafeRef) }) - .actions((self) => ({ + .actions({ deleteSqr(id: string) { - self.mapOfRef.delete(id) + this.mapOfRef.delete(id) } - })) + }) const rootModel = RootModel.create({ mapOfRef: { diff --git a/packages/mobx-state-tree/__tests__/core/reference.test.ts b/packages/mobx-state-tree/__tests__/core/reference.test.ts index aaebfc95b..a7152038c 100644 --- a/packages/mobx-state-tree/__tests__/core/reference.test.ts +++ b/packages/mobx-state-tree/__tests__/core/reference.test.ts @@ -123,11 +123,11 @@ test("it should resolve refs during creation, when using path", () => { .model({ book: types.reference(Book) }) - .views((self) => ({ + .views({ get price() { - return self.book.price * 2 + return this.book.price * 2 } - })) + }) const Store = types.model({ books: types.array(Book), entries: types.optional(types.array(BookEntry), []) @@ -159,11 +159,11 @@ test("it should resolve refs over late types", () => { .model({ book: types.reference(types.late(() => Book)) }) - .views((self) => ({ + .views({ get price() { - return self.book.price * 2 + return this.book.price * 2 } - })) + }) const Store = types.model({ books: types.array(Book), entries: types.array(BookEntry) @@ -187,11 +187,11 @@ test("it should resolve refs during creation, when using generic reference", () .model({ book: types.reference(Book) }) - .views((self) => ({ + .views({ get price() { - return self.book.price * 2 + return this.book.price * 2 } - })) + }) const Store = types.model({ books: types.array(Book), entries: types.optional(types.array(BookEntry), []) @@ -261,7 +261,7 @@ test("122 - identifiers should support numbers as well", () => { expect(F.is({ id: "bla" })).toBe(false) }) -test("self reference with a late type", () => { +test("this reference with a late type", () => { const Book = types.model("Book", { id: types.identifier, genre: types.string, @@ -271,12 +271,9 @@ test("self reference with a late type", () => { .model("Store", { books: types.array(Book) }) - .actions((self) => { - function addBook(book: SnapshotOrInstance) { - self.books.push(book) - } - return { - addBook + .actions({ + addBook(book: SnapshotOrInstance) { + this.books.push(book) } }) const s = Store.create({ @@ -569,15 +566,14 @@ test("References in recursive structures", () => { children: types.array(types.late((): IAnyModelType => Tree)), data: types.maybeNull(types.reference(Folder)) }) - .actions((self) => { - function addFolder(data: SnapshotOrInstance) { + .actions({ + addFolder(data: SnapshotOrInstance) { const folder3 = Folder.create(data) - getRoot(self).putFolderHelper(folder3) - self.children.push( + getRoot(this).putFolderHelper(folder3) + this.children.push( Tree.create({ data: castToReferenceSnapshot(folder3), children: [] }) ) } - return { addFolder } }) const Storage = types @@ -585,11 +581,11 @@ test("References in recursive structures", () => { objects: types.map(Folder), tree: Tree }) - .actions((self) => ({ + .actions({ putFolderHelper(aFolder: SnapshotOrInstance) { - self.objects.put(aFolder) + this.objects.put(aFolder) } - })) + }) const store = Storage.create({ objects: {}, tree: { children: [], data: null } }) const folder = { id: 1, name: "Folder 1", files: ["a.jpg", "b.jpg"] } store.tree.addFolder(folder) @@ -657,20 +653,15 @@ test("it should applyPatch references in array", () => { objects: types.map(Item), hovers: types.array(types.reference(Item)) }) - .actions((self) => { - function addObject(anItem: typeof Item.Type) { - self.objects.put(anItem) - } - function addHover(anItem: typeof Item.Type) { - self.hovers.push(anItem) - } - function removeHover(anItem: typeof Item.Type) { - self.hovers.remove(anItem) - } - return { - addObject, - addHover, - removeHover + .actions({ + addObject(anItem: typeof Item.Type) { + this.objects.put(anItem) + }, + addHover(anItem: typeof Item.Type) { + this.hovers.push(anItem) + }, + removeHover(anItem: typeof Item.Type) { + this.hovers.remove(anItem) } }) const folder = Folder.create({ id: "folder 1", objects: {}, hovers: [] }) @@ -769,12 +760,10 @@ test("array of references should work fine", () => { blocks: types.array(B), blockRefs: types.array(types.reference(B)) }) - .actions((self) => { - return { - order() { - const res = self.blockRefs.slice() - self.blockRefs.replace([res[1], res[0]]) - } + .actions({ + order() { + const res = this.blockRefs.slice() + this.blockRefs.replace([res[1], res[0]]) } }) const a = S.create({ blocks: [{ id: "1" }, { id: "2" }], blockRefs: ["1", "2"] }) @@ -850,21 +839,21 @@ test("#1052 - Reference returns destroyed model after subtree replacing", () => lastWithId: types.maybe(types.reference(Todo)), counter: -1 }) - .actions((self) => ({ + .actions({ load() { - self.counter++ - self.todos = Todos.create({ + this.counter++ + this.todos = Todos.create({ items: [ - { id: 1, title: "Get Coffee " + self.counter }, - { id: 2, title: "Write simpler code " + self.counter } + { id: 1, title: "Get Coffee " + this.counter }, + { id: 2, title: "Write simpler code " + this.counter } ] }) }, select(todo: Instance) { - self.last = todo - self.lastWithId = todo.id as any + this.last = todo + this.lastWithId = todo.id as any } - })) + }) const store = Store.create({ todos: {} }) store.load() @@ -927,31 +916,31 @@ test("#1080 - does not crash trying to resolve a reference to a destroyed+recrea .model("BranchStore", { activeBranch: types.maybeNull(types.reference(Branch)) }) - .actions((self) => ({ + .actions({ setActiveBranch(branchId: any) { - self.activeBranch = branchId + this.activeBranch = branchId } - })) + }) const RootStore = types .model("RootStore", { user: types.maybeNull(User), branchStore: types.maybeNull(BranchStore) }) - .actions((self) => ({ + .actions({ setUser(snapshot: typeof userSnapshot) { - self.user = cast(snapshot) + this.user = cast(snapshot) }, setBranchStore(snapshot: typeof branchStoreSnapshot) { - self.branchStore = cast(snapshot) + this.branchStore = cast(snapshot) }, destroyUser() { - destroy(self.user!) + destroy(this.user!) }, destroyBranchStore() { - destroy(self.branchStore!) + destroy(this.branchStore!) } - })) + }) const userSnapshot = { id: 1, @@ -1000,15 +989,15 @@ test("tryReference / isValidReference", () => { ref2: types.maybeNull(types.reference(Todo)), ref3: types.maybe(types.reference(Todo)) }) - .actions((self) => ({ + .actions({ clearRef3() { - self.ref3 = undefined + this.ref3 = undefined }, afterCreate() { addDisposer( - self, + this, reaction( - () => isValidReference(() => self.ref3), + () => isValidReference(() => this.ref3), (valid) => { if (!valid) { this.clearRef3() @@ -1018,7 +1007,7 @@ test("tryReference / isValidReference", () => { ) ) } - })) + }) const store = TodoStore.create({ todos: [{ id: "1" }, { id: "2" }, { id: "3" }] diff --git a/packages/mobx-state-tree/__tests__/core/reflection.test.ts b/packages/mobx-state-tree/__tests__/core/reflection.test.ts index cb9efc476..970c41385 100644 --- a/packages/mobx-state-tree/__tests__/core/reflection.test.ts +++ b/packages/mobx-state-tree/__tests__/core/reflection.test.ts @@ -22,28 +22,25 @@ const Model = types dogs: types.array(User), user: types.maybe(types.late(() => User)) }) - .volatile((self) => ({ + .volatile({ volatileProperty: { propName: "halo" } - })) - .actions((self) => { - function actionName() { + }) + .actions({ + actionName() { return 1 - } - return { - actionName, - generatorAction: flow(function* generatorAction() { - const promise = new Promise((resolve) => { - resolve(true) - }) - yield promise + }, + generatorAction: flow(function* generatorAction() { + const promise = new Promise((resolve) => { + resolve(true) }) - } + yield promise + }) }) - .views((self) => ({ + .views({ get viewName() { return 1 } - })) + }) function expectPropertyMembersToMatchMembers( propertyMembers: IModelReflectionPropertiesData, @@ -59,6 +56,7 @@ test("reflection - model", () => { const node = Model.create() const reflection = getMembers(node) expect(reflection.name).toBe("AnonymousModel") + console.log("actions", reflection.actions) expect(reflection.actions.includes("actionName")).toBe(true) expect(reflection.flowActions.includes("generatorAction")).toBe(true) expect(reflection.views.includes("viewName")).toBe(true) @@ -151,30 +149,26 @@ test("reflection - members chained", () => { .model({ isPerson: false }) - .actions((self) => { - return { - actionName() { - return 1 - } + .actions({ + actionName() { + return 1 } }) - .actions((self) => { - return { - anotherAction() { - return 1 - } + .actions({ + anotherAction() { + return 1 } }) - .views((self) => ({ + .views({ get viewName() { return 1 } - })) - .views((self) => ({ + }) + .views({ anotherView(prop: string) { return 1 } - })) + }) const node = ChainedModel.create() const reflection = getMembers(node) const keys = Object.keys(reflection.properties || {}) @@ -190,12 +184,12 @@ test("reflection - conditionals respected", () => { .model({ isPerson: false }) - .actions((self) => ({ + .actions({ actionName0() { return 1 } - })) - .actions((self): { actionName1(): number } | { actionName2(): number } => { + }) + .actions(function (): { actionName1(): number } | { actionName2(): number } { if (swap) { return { actionName1() { @@ -210,7 +204,7 @@ test("reflection - conditionals respected", () => { } } }) - .views((self) => { + .views(() => { if (swap) { return { get view1() { diff --git a/packages/mobx-state-tree/__tests__/core/snapshotProcessor.test.ts b/packages/mobx-state-tree/__tests__/core/snapshotProcessor.test.ts index 2d41c11ff..abfc15dd2 100644 --- a/packages/mobx-state-tree/__tests__/core/snapshotProcessor.test.ts +++ b/packages/mobx-state-tree/__tests__/core/snapshotProcessor.test.ts @@ -503,14 +503,14 @@ describe("snapshotProcessor", () => { todos: types.map(TodoWithProcessor), instance: types.optional(TodoWithProcessor, { id: "new" }) }) - .actions((self) => ({ + .actions({ addTodo(todo: { id: string }) { - self.todos.put(todo) + this.todos.put(todo) }, setInstance(next: { id: string }) { - self.instance = next + this.instance = next } - })) + }) test("using instances in maps work", () => { const store = Store.create() @@ -543,11 +543,11 @@ describe("snapshotProcessor", () => { test("using the processed type in place of the non processed one works", () => { const store = types .model("Store", { instance: Todo }) - .actions((self) => ({ + .actions({ setInstance(next: { id: string }) { - self.instance = next + this.instance = next } - })) + }) .create({ instance: { id: "new" } }) const todo = TodoWithProcessor.create({ id: "map" }) @@ -617,9 +617,9 @@ describe("snapshotProcessor", () => { } } ) - const Store = types.model({ items: types.array(SP) }).actions((self) => ({ + const Store = types.model({ items: types.array(SP) }).actions(() => ({ setItems(items: SnapshotIn[]) { - self.items = cast(items) + this.items = cast(items) } })) const store = Store.create({ items: [{ id: "1", y: 0 }] }) @@ -639,9 +639,9 @@ describe("snapshotProcessor", () => { } } ) - const Store = types.model({ items: types.array(SP) }).actions((self) => ({ + const Store = types.model({ items: types.array(SP) }).actions(() => ({ setItems(items: SnapshotIn[]) { - self.items = cast(items) + this.items = cast(items) } })) const store = Store.create({ items: [{ foo: "1" }] }) @@ -668,9 +668,9 @@ describe("snapshotProcessor", () => { } } ) - const Store = types.model({ item: SP }).actions((self) => ({ + const Store = types.model({ item: SP }).actions(() => ({ setItem(item: SnapshotIn) { - self.item = cast(item) + this.item = cast(item) } })) const store = Store.create({ item: { id: "1", y: 0 } }) @@ -691,9 +691,9 @@ describe("snapshotProcessor", () => { } } ) - const Store = types.model({ item: SP }).actions((self) => ({ + const Store = types.model({ item: SP }).actions(() => ({ setItem(item: SnapshotIn) { - self.item = cast(item) + this.item = cast(item) } })) const store = Store.create({ item: { foo: "1" } }) @@ -715,9 +715,9 @@ describe("snapshotProcessor", () => { } } ) - const Store = types.model({ item: types.maybe(SP) }).actions((self) => ({ + const Store = types.model({ item: types.maybe(SP) }).actions(() => ({ setItem(item: SnapshotIn) { - self.item = cast(item) + this.item = cast(item) } })) const store = Store.create({ item: { id: "1", y: 0 } }) @@ -741,9 +741,9 @@ describe("snapshotProcessor", () => { ) const Store = types .model({ item: types.optional(SP, { id: "1", y: 0 }) }) - .actions((self) => ({ + .actions(() => ({ setItem(item?: SnapshotIn) { - self.item = cast(item) + this.item = cast(item) } })) const store = Store.create() @@ -770,9 +770,9 @@ describe("snapshotProcessor", () => { } } ) - const Store = types.model({ item: types.maybe(SP) }).actions((self) => ({ + const Store = types.model({ item: types.maybe(SP) }).actions(() => ({ setItem(item?: SnapshotIn) { - self.item = cast(item) + this.item = cast(item) } })) const store = Store.create() @@ -794,9 +794,10 @@ describe("snapshotProcessor", () => { "a" ) }) - .actions((self) => ({ - setProp(prop: typeof self.prop) { - self.prop = prop + .actions(() => ({ + // @ts-ignore + setProp(prop: typeof this.prop) { + this.prop = prop } })) diff --git a/packages/mobx-state-tree/__tests__/core/this.test.ts b/packages/mobx-state-tree/__tests__/core/this.test.ts index 873fc9d51..34d89dbcd 100644 --- a/packages/mobx-state-tree/__tests__/core/this.test.ts +++ b/packages/mobx-state-tree/__tests__/core/this.test.ts @@ -5,9 +5,9 @@ import { isObservableProp, isComputedProp } from "mobx" test.skip("this support", () => { const M = types .model({ x: 5 }) - .views((self) => ({ + .views({ get x2() { - return self.x * 2 + return this.x * 2 }, get x4() { return this.x2 * 2 @@ -26,8 +26,8 @@ test.skip("this support", () => { isComputedProp(this, "x2") ) } - })) - .volatile((self) => ({ + }) + .volatile({ localState: 3, getLocalState() { return this.localState @@ -35,25 +35,22 @@ test.skip("this support", () => { getLocalState2() { return this.getLocalState() * 2 } - })) - - .actions((self) => { - return { - xBy(by: number) { - return self.x * by - }, - setX(x: number) { - self.x = x - }, - setThisX(x: number) { - ;(this as any).x = x // this should not affect self.x - }, - setXBy(x: number) { - this.setX(this.xBy(x)) - }, - setLocalState(x: number) { - self.localState = x - } + }) + .actions({ + xBy(by: number) { + return this.x * by + }, + setX(x: number) { + this.x = x + }, + setThisX(x: number) { + ;(this as any).x = x // this should not affect this.x + }, + setXBy(x: number) { + this.setX(this.xBy(x)) + }, + setLocalState(x: number) { + this.localState = x } }) @@ -87,7 +84,7 @@ test.skip("this support", () => { mi.setLocalState(7) expect(mi.localState).toBe(7) - // make sure attempts to modify this (as long as it is not an action) doesn't affect self + // make sure attempts to modify this (as long as it is not an action) doesn't affect this const oldX = mi.x mi.setThisX(oldX + 1) expect(mi.x).toBe(oldX) diff --git a/packages/mobx-state-tree/__tests__/core/type-system.test.ts b/packages/mobx-state-tree/__tests__/core/type-system.test.ts index f02181a78..d860ffa7a 100644 --- a/packages/mobx-state-tree/__tests__/core/type-system.test.ts +++ b/packages/mobx-state-tree/__tests__/core/type-system.test.ts @@ -69,21 +69,17 @@ test("it should do typescript type inference correctly", () => { x: types.number, y: types.maybeNull(types.string) }) - .views((self) => ({ + .views({ get z(): string { return "hi" } - })) - .actions((self) => { - function method() { - const x: string = self.z + self.x + self.y - anotherMethod(x) - } - function anotherMethod(x: string) {} - return { - method, - anotherMethod - } + }) + .actions({ + method() { + const x: string = this.z + this.x + this.y + this.anotherMethod(x) + }, + anotherMethod(x: string) {} }) // factory is invokable const a = A.create({ x: 2, y: "7" }) @@ -199,11 +195,8 @@ test("it is possible to refer to a type", () => { .model({ title: types.string }) - .actions((self) => { - function setTitle(v: string) {} - return { - setTitle - } + .actions({ + setTitle(v: string) {} }) function x(): typeof Todo.Type { return Todo.create({ title: "test" }) @@ -220,11 +213,8 @@ test(".Type should not be callable", () => { .model({ title: types.string }) - .actions((self) => { - function setTitle(v: string) {} - return { - setTitle - } + .actions({ + setTitle(v: string) {} }) expect(() => Todo.Type).toThrow() }) @@ -233,26 +223,17 @@ test(".SnapshotType should not be callable", () => { .model({ title: types.string }) - .actions((self) => { - function setTitle(v: string) {} - return { - setTitle - } + .actions({ + setTitle(v: string) {} }) expect(() => Todo.SnapshotType).toThrow() }) test("types instances with compatible snapshots should not be interchangeable", () => { - const A = types.model("A", {}).actions((self) => { - function doA() {} - return { - doA - } + const A = types.model("A", {}).actions({ + doA() {} }) - const B = types.model("B", {}).actions((self) => { - function doB() {} - return { - doB - } + const B = types.model("B", {}).actions({ + doB() {} }) const C = types.model("C", { x: types.maybe(A) @@ -280,35 +261,29 @@ test("it handles complex types correctly", () => { .model({ title: types.string }) - .actions((self) => { - function setTitle(v: string) {} - return { - setTitle - } + .actions({ + setTitle(v: string) {} }) const Store = types .model({ todos: types.map(Todo) }) - .views((self) => { - function getActualAmount() { - return self.todos.size + .views(() => { + function getActualAmount(store) { + return store.todos.size } return { get amount() { - return getActualAmount() + return getActualAmount(this) }, getAmount(): number { - return self.todos.size + getActualAmount() + return this.todos.size + getActualAmount(this) } } }) - .actions((self) => { - function setAmount() { - const x: number = self.todos.size + self.amount + self.getAmount() - } - return { - setAmount + .actions({ + setAmount() { + const x: number = this.todos.size + this.amount + this.getAmount() } }) expect(true).toBe(true) // supress no asserts warning @@ -319,30 +294,24 @@ if (process.env.NODE_ENV !== "production") { .model({ title: types.string }) - .actions((self) => { - function setTitle(v: string) {} - return { - setTitle - } + .actions({ + setTitle(v: string) {} }) const Store = types .model({ todos: types.map(Todo) }) - .views((self) => ({ + .views({ get amount() { - return self.todos.size + return this.todos.size }, getAmount(): number { - return self.todos.size + self.todos.size + return this.todos.size + this.todos.size } - })) - .actions((self) => { - function setAmount() { - const x: number = self.todos.size + self.amount + self.getAmount() - } - return { - setAmount + }) + .actions({ + setAmount() { + const x: number = this.todos.size + this.amount + this.getAmount() } }) expect(() => @@ -367,7 +336,7 @@ test("it should type compose correctly", () => { .model({ wheels: 3 }) - .actions((self) => { + .actions(() => { let connection = null as any as Promise function drive() {} function afterCreate() { @@ -382,11 +351,8 @@ test("it should type compose correctly", () => { .model({ logNode: "test" }) - .actions((self) => { - function log(msg: string) {} - return { - log - } + .actions({ + log(msg: string) {} }) const LoggableCar = types.compose(Car, Logger) const x = LoggableCar.create({ wheels: 3, logNode: "test" /* compile error: x: 7 */ }) @@ -448,18 +414,15 @@ test("it should extend types correctly", () => { .model({ wheels: 3 }) - .actions((self) => { - function drive() {} - return { - drive - } + .actions({ + drive() {} }) const Logger = types .model("Logger") .props({ logNode: "test" }) - .actions((self) => { + .actions(() => { let connection: Promise return { log(msg: string) {}, @@ -474,17 +437,14 @@ test("it should extend types correctly", () => { x.drive() x.log("z") }) -test("self referring views", () => { - const Car = types.model({ x: 3 }).views((self) => { - const views = { - get tripple() { - return self.x + views.double - }, - get double() { - return self.x * 2 - } +test("this referring views", () => { + const Car = types.model({ x: 3 }).views({ + get tripple() { + return this.x + this.double + }, + get double() { + return this.x * 2 } - return views }) expect(Car.create().tripple).toBe(9) }) @@ -656,110 +616,110 @@ test("cast and SnapshotOrInstance", () => { const NumberMap = types.map(types.number) const A = types .model({ n: 123, n2: types.number, arr: NumberArray, map: NumberMap }) - .actions((self) => ({ + .actions((this) => ({ // for primitives (although not needed) - setN(nn: SnapshotOrInstance) { - self.n = cast(nn) + setN(nn: SnapshotOrInstance) { + this.n = cast(nn) }, setN2(nn: SnapshotOrInstance) { - self.n = cast(nn) + this.n = cast(nn) }, setN3(nn: SnapshotOrInstance) { - self.n = cast(nn) + this.n = cast(nn) }, setN4(nn: number) { - self.n = cast(nn) + this.n = cast(nn) }, setN5() { - self.n = cast(5) + this.n = cast(5) }, // for arrays - setArr(nn: SnapshotOrInstance) { - self.arr = cast(nn) + setArr(nn: SnapshotOrInstance) { + this.arr = cast(nn) }, setArr2(nn: SnapshotOrInstance) { - self.arr = cast(nn) + this.arr = cast(nn) }, setArr3(nn: SnapshotIn) { - self.arr = cast(nn) + this.arr = cast(nn) }, setArr31(nn: number[]) { - self.arr = cast(nn) + this.arr = cast(nn) }, setArr4() { // it works even without specifying the target type, magic! - self.arr = cast([2, 3, 4]) - self.arr = cast(NumberArray.create([2, 3, 4])) + this.arr = cast([2, 3, 4]) + this.arr = cast(NumberArray.create([2, 3, 4])) }, // for maps - setMap(nn: SnapshotOrInstance) { - self.map = cast(nn) + setMap(nn: SnapshotOrInstance) { + this.map = cast(nn) }, setMap2(nn: SnapshotOrInstance) { - self.map = cast(nn) + this.map = cast(nn) }, setMap3(nn: SnapshotIn) { - self.map = cast(nn) + this.map = cast(nn) }, setMap31(nn: { [k: string]: number }) { - self.map = cast(nn) + this.map = cast(nn) }, setMap4() { // it works even without specifying the target type, magic! - self.map = cast({ a: 2, b: 3 }) - self.map = cast(NumberMap.create({ a: 2, b: 3 })) + this.map = cast({ a: 2, b: 3 }) + this.map = cast(NumberMap.create({ a: 2, b: 3 })) } })) const C = types .model({ a: A, maybeA: types.maybe(A), maybeNullA: types.maybeNull(A) }) - .actions((self) => ({ - // for submodels, using typeof self.var - setA(na: SnapshotOrInstance) { - self.a = cast(na) + .actions((this) => ({ + // for submodels, using typeof this.var + setA(na: SnapshotOrInstance) { + this.a = cast(na) // we just want to check it compiles if (0 !== 0) { - self.maybeA = cast(na) - self.maybeNullA = cast(na) + this.maybeA = cast(na) + this.maybeNullA = cast(na) } }, // for submodels, using the type directly setA2(na: SnapshotOrInstance) { - self.a = cast(na) + this.a = cast(na) // we just want to check it compiles if (0 !== 0) { - self.maybeA = cast(na) - self.maybeNullA = cast(na) + this.maybeA = cast(na) + this.maybeNullA = cast(na) } }, setA3(na: SnapshotIn) { - self.a = cast(na) + this.a = cast(na) // we just want to check it compiles if (0 !== 0) { - self.maybeA = cast(na) - self.maybeNullA = cast(na) + this.maybeA = cast(na) + this.maybeNullA = cast(na) } }, - setA4(na: Instance) { - self.a = cast(na) + setA4(na: Instance) { + this.a = cast(na) // we just want to check it compiles if (0 !== 0) { - self.maybeA = cast(na) - self.maybeNullA = cast(na) + this.maybeA = cast(na) + this.maybeNullA = cast(na) } }, setA5() { // it works even without specifying the target type, magic! - self.a = cast({ n2: 5 }) - self.a = cast(A.create({ n2: 5 })) + this.a = cast({ n2: 5 }) + this.a = cast(A.create({ n2: 5 })) // we just want to check it compiles if (0 !== 0) { - self.maybeA = cast({ n2: 5 }) - self.maybeA = cast(A.create({ n2: 5 })) - self.maybeNullA = cast({ n2: 5 }) - self.maybeNullA = cast(A.create({ n2: 5 })) + this.maybeA = cast({ n2: 5 }) + this.maybeA = cast(A.create({ n2: 5 })) + this.maybeNullA = cast({ n2: 5 }) + this.maybeNullA = cast(A.create({ n2: 5 })) } } })) @@ -831,7 +791,7 @@ test("castToSnapshot", () => { const firstModel = types.model({ brew1: types.map(types.number) }) const secondModel = types .model({ brew2: types.map(firstModel) }) - .actions((self) => ({ do() {} })) + .actions((this) => ({ do() {} })) const appMod = types.model({ aaa: secondModel }) const storeSnapshot: SnapshotIn = { @@ -956,9 +916,9 @@ test("MST array type should be assignable to plain array type", () => { done: false, name: types.string }) - .actions((self) => ({ + .actions((this) => ({ toggleDone() { - self.done = !self.done + this.done = !this.done } })) const TodoArray = types.array(Todo) @@ -1051,9 +1011,9 @@ test("#1307 custom types failing", () => { someProp: types.boolean, someType: CustomType }) - .views((self) => ({ + .views((this) => ({ get isSomePropTrue(): boolean { - return self.someProp + return this.someProp } })) } @@ -1061,7 +1021,7 @@ test("#1307 custom types failing", () => { test("#1343", () => { function createTypeA(t: T) { - return types.model("TypeA", t).views((self) => ({ + return types.model("TypeA", t).views((this) => ({ get someView() { return null } @@ -1073,9 +1033,9 @@ test("#1343", () => { .model("TypeB", { a: createTypeA(t) }) - .views((self) => ({ + .views((this) => ({ get someViewFromA() { - return self.a.someView + return this.a.someView } })) } @@ -1087,14 +1047,14 @@ test("#1330", () => { foo: types.string, bar: types.boolean }) - .views((self) => ({ + .views((this) => ({ get root(): IRootStore { - return getRoot(self) + return getRoot(this) } })) - .actions((self) => ({ + .actions((this) => ({ test() { - const { childStore } = self.root + const { childStore } = this.root // childStore and childStore.foo is properly inferred in TS 3.4 but not in 3.5 console.log(childStore.foo) } diff --git a/packages/mobx-state-tree/__tests__/core/union.test.ts b/packages/mobx-state-tree/__tests__/core/union.test.ts index 589ffcf2b..1bfc3607d 100644 --- a/packages/mobx-state-tree/__tests__/core/union.test.ts +++ b/packages/mobx-state-tree/__tests__/core/union.test.ts @@ -138,22 +138,22 @@ test("dispatch", () => { .model({ value: types.number }) - .actions((self) => ({ + .actions({ isOdd() { return true }, isEven() { return false } - })) - const Even = types.model({ value: types.number }).actions((self) => ({ + }) + const Even = types.model({ value: types.number }).actions({ isOdd() { return false }, isEven() { return true } - })) + }) const Num = types.union( { dispatcher: (snapshot) => (snapshot.value % 2 === 0 ? Even : Odd) }, Even, diff --git a/packages/mobx-state-tree/__tests__/core/volatile.test.ts b/packages/mobx-state-tree/__tests__/core/volatile.test.ts index a24e7a5c1..bc86690b5 100644 --- a/packages/mobx-state-tree/__tests__/core/volatile.test.ts +++ b/packages/mobx-state-tree/__tests__/core/volatile.test.ts @@ -5,17 +5,17 @@ const Todo = types .model({ done: false }) - .volatile((self) => ({ + .volatile({ state: Promise.resolve(1) - })) - .actions((self) => ({ + }) + .actions({ toggle() { - self.done = !self.done + this.done = !this.done }, reload() { - self.state = Promise.resolve(2) + this.state = Promise.resolve(2) } - })) + }) test("Properties should be readable and writable", () => { const i = Todo.create() @@ -53,9 +53,9 @@ test("VS be observable", () => { test("VS should not be deeply observable", () => { const i = types .model({}) - .volatile((self) => ({ + .volatile({ x: { a: 1 } - })) + }) .create() unprotect(i) expect(isObservableProp(i, "x")).toBe(true) @@ -105,9 +105,8 @@ test("VS should not be modifiable when unprotected", () => { }) test("VS sample from the docs should work (1)", () => { - const T = types.model({}).extend((self) => { + const T = types.model({}).extend(() => { const localState = observable.box(3) - return { views: { get x() { @@ -140,9 +139,8 @@ test("VS sample from the docs should work (1)", () => { }) test("VS sample from the docs should work (2)", () => { - const T = types.model({}).extend((self) => { + const T = types.model({}).extend(() => { let localState = 3 - return { views: { getX() { diff --git a/packages/mobx-state-tree/package.json b/packages/mobx-state-tree/package.json index bd74a8847..eb311e964 100644 --- a/packages/mobx-state-tree/package.json +++ b/packages/mobx-state-tree/package.json @@ -17,7 +17,7 @@ "scripts": { "clean": "shx rm -rf dist && shx rm -rf lib", "build": "yarn clean && shx cp ../../README.md . && tsc && cpr lib dist --filter=\\.js$ && rollup -c", - "jest": "jest --testPathPattern=/__tests__/core/", + "jest": "jest --testPathPattern=/__tests__/core/reflection", "jest:perf": "jest --testPathPattern=/__tests__/perf/", "test:perf": "yarn build && yarn jest:perf && TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' /usr/bin/time node --expose-gc --require ts-node/register __tests__/perf/report.ts", "test": "yarn test:dev && yarn test:prod && yarn test:others", diff --git a/packages/mobx-state-tree/src/core/action.ts b/packages/mobx-state-tree/src/core/action.ts index 80570bd09..a25a27ccd 100644 --- a/packages/mobx-state-tree/src/core/action.ts +++ b/packages/mobx-state-tree/src/core/action.ts @@ -10,7 +10,9 @@ import { warnError, AnyObjectNode, devMode, - IActionContext + IActionContext, + flow, + isGeneratorFunction } from "../internal" export type IMiddlewareEventType = @@ -113,27 +115,36 @@ export function getParentActionContext(parentContext: IMiddlewareEvent | undefin } /** - * @internal - * @hidden */ -export function createActionInvoker( - target: IAnyStateTreeNode, - name: string, - fn: T -) { - const res = function () { +export function createActionInvoker(name: string, fn: T) { + // @ts-ignore + const res: (...args: Parameters) => ReturnType = function ( + this: any, + // @ts-ignore + ...args: Parameters + ) { const id = getNextActionId() const parentContext = currentActionContext const parentActionContext = getParentActionContext(parentContext) + if (!this || !this.$treenode) console.log("target", this) + + const boundFn = fn.bind(this) + Object.defineProperty(boundFn, "name", { value: name }) + Object.defineProperty(boundFn, "_isMSTAction", { value: true }) + + const boundAction = isGeneratorFunction(fn) ? flow(boundFn) : boundFn + boundAction._isFlowAction = (fn as FunctionWithFlag)._isFlowAction || false + boundAction.$mst_middleware = (fn as any).$mst_middleware + boundAction._isMSTAction = true return runWithActionContext( { type: "action", name, id, - args: argsToArray(arguments), - context: target, - tree: getRoot(target), + args: args, + context: this, + tree: getRoot(this), rootId: parentContext ? parentContext.rootId : id, parentId: parentContext ? parentContext.id : 0, allParentIds: parentContext @@ -142,7 +153,7 @@ export function createActionInvoker( parentEvent: parentContext, parentActionEvent: parentActionContext }, - fn + boundAction ) } ;(res as FunctionWithFlag)._isMSTAction = true @@ -251,6 +262,10 @@ function runMiddleWares( originalFn: Function ): any { const middlewares = new CollectedMiddlewares(node, originalFn) + + // @ts-ignore + // originalFn = isGenerator(originalFn) ? flow(originalFn) : originalFn + // Short circuit if (middlewares.isEmpty) return mobxAction(originalFn).apply(null, baseCall.args) diff --git a/packages/mobx-state-tree/src/core/flow.ts b/packages/mobx-state-tree/src/core/flow.ts index f94aceb44..3c2fda5c6 100644 --- a/packages/mobx-state-tree/src/core/flow.ts +++ b/packages/mobx-state-tree/src/core/flow.ts @@ -88,12 +88,27 @@ export function* toGenerator(p: Promise) { return (yield p) as R } +export function isGeneratorFunction(obj: any): boolean { + const constructor = obj?.constructor + if (!constructor) { + return false + } + if ( + "GeneratorFunction" === constructor.name || + "GeneratorFunction" === constructor.displayName + ) { + return true + } + return false +} + /** * @internal * @hidden */ export function createFlowSpawner(name: string, generator: FunctionWithFlag) { - const spawner = function flowSpawner(this: any) { + // @ts-ignore + const spawner = function flowSpawner() { // Implementation based on https://github.com/tj/co/blob/master/index.js const runId = getNextActionId() const parentContext = getCurrentActionContext()! @@ -121,6 +136,7 @@ export function createFlowSpawner(name: string, generator: FunctionWithFlag) { function wrap(fn: any, type: IMiddlewareEventType, arg: any) { fn.$mst_middleware = (spawner as any).$mst_middleware // pick up any middleware attached to the flow + fn = isGeneratorFunction(fn) ? flow(fn) : fn return runWithActionContext( { ...contextBase, @@ -134,10 +150,11 @@ export function createFlowSpawner(name: string, generator: FunctionWithFlag) { return new Promise(function (resolve, reject) { let gen: any const init = function asyncActionInit() { + // @ts-ignore gen = generator.apply(null, arguments) onFulfilled(undefined) // kick off the flow } - ; (init as any).$mst_middleware = (spawner as any).$mst_middleware + ;(init as any).$mst_middleware = (spawner as any).$mst_middleware runWithActionContext( { @@ -154,7 +171,7 @@ export function createFlowSpawner(name: string, generator: FunctionWithFlag) { // prettier-ignore const cancelError: any = wrap((r: any) => { ret = gen.next(r) }, "flow_resume", res) if (cancelError instanceof Error) { - ret = gen.throw(cancelError); + ret = gen.throw(cancelError) } } catch (e) { // prettier-ignore @@ -189,11 +206,10 @@ export function createFlowSpawner(name: string, generator: FunctionWithFlag) { wrap((r: any) => { resolve(r) }, "flow_return", ret.value) }) return - } - // TODO: support more type of values? See https://github.com/tj/co/blob/249bbdc72da24ae44076afd716349d2089b31c4c/index.js#L100 - if (!ret.value || typeof ret.value.then !== "function") { + } else if (!ret.value || typeof ret.value.then !== "function") { // istanbul ignore next - throw fail("Only promises can be yielded to `async`, got: " + ret) + console.log("ret", ret, ret.value) + throw fail("Only promises can be yielded to `async`, got: " + ret.value) } return ret.value.then(onFulfilled, onRejected) } diff --git a/packages/mobx-state-tree/src/core/mst-operations.ts b/packages/mobx-state-tree/src/core/mst-operations.ts index 8fd4cf77f..ba9421f6b 100644 --- a/packages/mobx-state-tree/src/core/mst-operations.ts +++ b/packages/mobx-state-tree/src/core/mst-operations.ts @@ -862,14 +862,20 @@ export function getMembers(target: IAnyStateTreeNode): IModelReflectionData { } const props = Object.getOwnPropertyNames(target) + console.log("props", props) + props.forEach((key) => { + if (key === "actionName") debugger if (key in reflected.properties) return - const descriptor = Object.getOwnPropertyDescriptor(target, key)! + const descriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(target), key)! + if (!descriptor) debugger if (descriptor.get) { if (isComputedProp(target, key)) reflected.views.push(key) else reflected.volatile.push(key) return } + if (key === "actionName") + console.log("getMembers", descriptor.value, descriptor.value._isMSTAction) if (descriptor.value._isMSTAction === true) reflected.actions.push(key) if (descriptor.value._isFlowAction === true) reflected.flowActions.push(key) else if (isObservableProp(target, key)) reflected.volatile.push(key) diff --git a/packages/mobx-state-tree/src/core/node/object-node.ts b/packages/mobx-state-tree/src/core/node/object-node.ts index 9624e1364..6d0baf43f 100644 --- a/packages/mobx-state-tree/src/core/node/object-node.ts +++ b/packages/mobx-state-tree/src/core/node/object-node.ts @@ -93,15 +93,36 @@ export class ObjectNode extends BaseNode { isProtectionEnabled = true middlewares?: IMiddleware[] - private _applyPatches?: (patches: IJsonPatch[]) => void + _applyPatches: (patches: IJsonPatch[]) => void = createActionInvoker( + "@APPLY_PATCHES", + (patches: IJsonPatch[]) => { + patches.forEach((patch) => { + if (!patch.path) { + this.type.applySnapshot(this, patch.value) + return + } + const parts = splitJsonPath(patch.path) + const node = resolveNodeByPathParts(this, parts.slice(0, -1)) as AnyObjectNode + node.applyPatchLocally(parts[parts.length - 1], patch) + }) + } + ) + + _applySnapshot: (snapshot: C) => void = createActionInvoker( + "@APPLY_SNAPSHOT", + (snapshot: C) => { + // if the snapshot is the same as the current one, avoid performing a reconcile + if (snapshot === (this.snapshot as any)) return + // else, apply it by calling the type logic + return this.type.applySnapshot(this, snapshot as any) + } + ) applyPatches(patches: IJsonPatch[]): void { this.createObservableInstanceIfNeeded() this._applyPatches!(patches) } - private _applySnapshot?: (snapshot: C) => void - applySnapshot(snapshot: C): void { this.createObservableInstanceIfNeeded() this._applySnapshot!(snapshot) @@ -215,7 +236,9 @@ export class ObjectNode extends BaseNode { const type = this.type try { + // Regular mobx observable. this.storedValue = type.createNewInstance(this._childNodes) + // Bind ObjectNode to it using hidden prop. this.preboot() this._isRunningAction = true @@ -506,33 +529,8 @@ export class ObjectNode extends BaseNode { } private preboot(): void { - const self = this - this._applyPatches = createActionInvoker( - this.storedValue, - "@APPLY_PATCHES", - (patches: IJsonPatch[]) => { - patches.forEach((patch) => { - if (!patch.path) { - self.type.applySnapshot(self, patch.value) - return - } - const parts = splitJsonPath(patch.path) - const node = resolveNodeByPathParts(self, parts.slice(0, -1)) as AnyObjectNode - node.applyPatchLocally(parts[parts.length - 1], patch) - }) - } - ) - this._applySnapshot = createActionInvoker( - this.storedValue, - "@APPLY_SNAPSHOT", - (snapshot: C) => { - // if the snapshot is the same as the current one, avoid performing a reconcile - if (snapshot === (self.snapshot as any)) return - // else, apply it by calling the type logic - return self.type.applySnapshot(self, snapshot as any) - } - ) - + this._applyPatches = this._applyPatches.bind(this.storedValue) + this._applySnapshot = this._applySnapshot.bind(this.storedValue) addHiddenFinalProp(this.storedValue, "$treenode", this) addHiddenFinalProp(this.storedValue, "toJSON", toJSON) } diff --git a/packages/mobx-state-tree/src/internal.ts b/packages/mobx-state-tree/src/internal.ts index c45fbe3b0..4bacd7175 100644 --- a/packages/mobx-state-tree/src/internal.ts +++ b/packages/mobx-state-tree/src/internal.ts @@ -6,6 +6,8 @@ export * from "./core/node/livelinessChecking" export * from "./core/node/Hook" export * from "./core/mst-operations" +export * from "./core/action" +export * from "./core/actionContext" export * from "./core/node/BaseNode" export * from "./core/node/scalar-node" export * from "./core/node/object-node" @@ -13,8 +15,6 @@ export * from "./core/type/type" export * from "./middlewares/create-action-tracking-middleware" export * from "./middlewares/createActionTrackingMiddleware2" export * from "./middlewares/on-action" -export * from "./core/action" -export * from "./core/actionContext" export * from "./core/type/type-checker" export * from "./core/node/identifier-cache" export * from "./core/node/create-node" diff --git a/packages/mobx-state-tree/src/types/complex-types/array.ts b/packages/mobx-state-tree/src/types/complex-types/array.ts index de3115e51..6ff7311ff 100644 --- a/packages/mobx-state-tree/src/types/complex-types/array.ts +++ b/packages/mobx-state-tree/src/types/complex-types/array.ts @@ -134,7 +134,7 @@ export class ArrayType extends ComplexType< const hooks = initializer(instance) Object.keys(hooks).forEach((name) => { const hook = hooks[name as keyof typeof hooks]! - const actionInvoker = createActionInvoker(instance as IAnyStateTreeNode, name, hook) + const actionInvoker = createActionInvoker(name, hook).bind(instance) ;(!devMode() ? addHiddenFinalProp : addHiddenWritableProp)( instance, name, diff --git a/packages/mobx-state-tree/src/types/complex-types/map.ts b/packages/mobx-state-tree/src/types/complex-types/map.ts index eb78a92a6..fd4425c35 100644 --- a/packages/mobx-state-tree/src/types/complex-types/map.ts +++ b/packages/mobx-state-tree/src/types/complex-types/map.ts @@ -295,7 +295,7 @@ export class MapType extends ComplexType< const hooks = initializer(instance as unknown as IMSTMap) Object.keys(hooks).forEach((name) => { const hook = hooks[name as keyof typeof hooks]! - const actionInvoker = createActionInvoker(instance as IAnyStateTreeNode, name, hook) + const actionInvoker = createActionInvoker(name, hook).bind(instance) ;(!devMode() ? addHiddenFinalProp : addHiddenWritableProp)( instance, name, diff --git a/packages/mobx-state-tree/src/types/complex-types/model.ts b/packages/mobx-state-tree/src/types/complex-types/model.ts index 14c8877f6..0c67df731 100644 --- a/packages/mobx-state-tree/src/types/complex-types/model.ts +++ b/packages/mobx-state-tree/src/types/complex-types/model.ts @@ -11,7 +11,8 @@ import { observe, set, IObjectDidChange, - makeObservable + makeObservable, + AnnotationsMap } from "mobx" import { addHiddenFinalProp, @@ -54,9 +55,10 @@ import { devMode, assertIsString, assertArg, - FunctionWithFlag + FunctionWithFlag, + IStateTreeNode } from "../../internal" -import { ComputedValue } from "mobx/dist/internal" +import { makeAutoObservable } from "mobx" const PRE_PROCESS_SNAPSHOT = "preProcessSnapshot" const POST_PROCESS_SNAPSHOT = "postProcessSnapshot" @@ -195,19 +197,21 @@ export interface IModelType< ): IModelType, OTHERS, CustomC, CustomS> views( - fn: (self: Instance) => V + v: + | (ThisType> & V) + | (() => ThisType> & V) ): IModelType actions( - fn: (self: Instance) => A + a: + | ThisType & A> + | (() => ThisType & A>) ): IModelType - volatile( - fn: (self: Instance) => TP - ): IModelType + volatile(fn: TP): IModelType extend( - fn: (self: Instance) => { actions?: A; views?: V; state?: VS } + e: { actions?: A; views?: V; state?: VS } | (() => { actions?: A; views?: V; state?: VS }) ): IModelType preProcessSnapshot>( @@ -244,15 +248,19 @@ function objectTypeToString(this: any) { export interface ModelTypeConfig { name?: string properties?: ModelPropertiesDeclaration - initializers?: ReadonlyArray<(instance: any) => any> preProcessor?: (snapshot: any) => any postProcessor?: (snapshot: any) => any + actionDescriptors?: ReadonlyArray + viewDescriptors?: ReadonlyArray + volatileDescriptors?: ReadonlyArray } const defaultObjectOptions = { name: "AnonymousModel", properties: {}, - initializers: EMPTY_ARRAY + actionDescriptors: EMPTY_ARRAY as any[], + viewDescriptors: EMPTY_ARRAY as any[], + volatileDescriptors: EMPTY_ARRAY as any[] } function toPropertiesObject(declaredProps: ModelPropertiesDeclaration): ModelProperties { @@ -341,9 +349,13 @@ export class ModelType< /* * The original object definition */ - public readonly initializers!: ((instance: any) => any)[] + public readonly actionDescriptors!: any[] + public readonly viewDescriptors!: any[] + public readonly volatileDescriptors!: any[] public readonly properties!: PROPS + private baseClass?: new (param: string) => any + private preProcessor!: (snapshot: any) => any | undefined private postProcessor!: (snapshot: any) => any | undefined private readonly propertyNames: string[] @@ -376,21 +388,24 @@ export class ModelType< return new ModelType({ name: opts.name || this.name, properties: Object.assign({}, this.properties, opts.properties), - initializers: this.initializers.concat(opts.initializers || []), preProcessor: opts.preProcessor || this.preProcessor, - postProcessor: opts.postProcessor || this.postProcessor + postProcessor: opts.postProcessor || this.postProcessor, + actionDescriptors: this.actionDescriptors.concat(opts.actionDescriptors || []), + viewDescriptors: this.viewDescriptors.concat(opts.viewDescriptors || []), + volatileDescriptors: this.volatileDescriptors.concat(opts.volatileDescriptors || []) }) } - actions(fn: (self: Instance) => A) { - const actionInitializer = (self: Instance) => { - this.instantiateActions(self, fn(self)) - return self - } - return this.cloneAndEnhance({ initializers: [actionInitializer] }) + actions(actions: A) { + return this.cloneAndEnhance({ actionDescriptors: [actions] }) } - private instantiateActions(self: this["T"], actions: ModelActions): void { + private instantiateActions( + self: this["T"], + actions: ModelActions | (() => ModelActions) + ): void { + if (typeof actions === "function") actions = actions() + // check if return is correct if (!isPlainObject(actions)) throw fail(`actions initializer should return a plain object containing actions`) @@ -408,29 +423,36 @@ export class ModelType< `Cannot define action '${POST_PROCESS_SNAPSHOT}', it should be defined using 'type.postProcessSnapshot(fn)' instead` ) - let action2 = actions[name] + // @ts-ignore + let action = actions[name] // apply hook composition let baseAction = (self as any)[name] if (name in Hook && baseAction) { - let specializedAction = action2 - action2 = function () { + let specializedAction = action + action = function () { baseAction.apply(null, arguments) specializedAction.apply(null, arguments) } } - // the goal of this is to make sure actions using "this" can call themselves, - // while still allowing the middlewares to register them - const middlewares = (action2 as any).$mst_middleware // make sure middlewares are not lost - let boundAction = action2.bind(actions) - boundAction._isFlowAction = (action2 as FunctionWithFlag)._isFlowAction || false - boundAction.$mst_middleware = middlewares - const actionInvoker = createActionInvoker(self as any, name, boundAction) + const actionInvoker = createActionInvoker(name, action) + // @ts-ignore + console.log("instantiateActions", name, actionInvoker._isMSTAction) + + // @ts-ignore actions[name] = actionInvoker // See #646, allow models to be mocked - ;(!devMode() ? addHiddenFinalProp : addHiddenWritableProp)(self, name, actionInvoker) + ;(!devMode() ? addHiddenFinalProp : addHiddenWritableProp)( + self.prototype, + name, + actionInvoker + ) + // @ts-ignore + // self.prototype[name] = actionInvoker + + if (name === "actionName") debugger }) } @@ -442,66 +464,82 @@ export class ModelType< return this.cloneAndEnhance({ properties }) } - volatile(fn: (self: Instance) => TP) { - if (typeof fn !== "function") { - throw fail( - `You passed an ${typeof fn} to volatile state as an argument, when function is expected` - ) - } - const stateInitializer = (self: Instance) => { - this.instantiateVolatileState(self, fn(self)) - return self - } - return this.cloneAndEnhance({ initializers: [stateInitializer] }) + volatile(volatile: TP) { + return this.cloneAndEnhance({ volatileDescriptors: [volatile] }) } private instantiateVolatileState( self: this["T"], - state: { - [key: string]: any - } + state: + | { + [key: string]: any + } + | (() => { + [key: string]: any + }) ): void { + if (typeof state === "function") state = state() + // check views return if (!isPlainObject(state)) throw fail(`volatile state initializer should return a plain object containing state`) - set(self, state) + + Object.getOwnPropertyNames(state).forEach((key) => { + Object.defineProperty(self, key, { + // @ts-ignore + value: state[key] + }) + }) } - extend( - fn: (self: Instance) => { actions?: A; views?: V; state?: VS } - ) { - const initializer = (self: Instance) => { - const { actions, views, state, ...rest } = fn(self) - for (let key in rest) - throw fail( - `The \`extend\` function should return an object with a subset of the fields 'actions', 'views' and 'state'. Found invalid key '${key}'` - ) - if (state) this.instantiateVolatileState(self, state) - if (views) this.instantiateViews(self, views) - if (actions) this.instantiateActions(self, actions) - return self - } - return this.cloneAndEnhance({ initializers: [initializer] }) + extend({ + actions, + views, + state + }: { + actions?: A + views?: V + state?: VS + }) { + // const initializer = (self: Instance) => { + // const { actions, views, state, ...rest } = fn(self) + // for (let key in rest) + // throw fail( + // `The \`extend\` function should return an object with a subset of the fields 'actions', 'views' and 'state'. Found invalid key '${key}'` + // ) + // if (state) this.instantiateVolatileState(self, state) + // if (views) this.instantiateViews(self, views) + // if (actions) this.instantiateActions(self, actions) + // return self + // } + return this.cloneAndEnhance({ + actionDescriptors: [actions], + viewDescriptors: [views], + volatileDescriptors: [state] + }) } - views(fn: (self: Instance) => V) { - const viewInitializer = (self: Instance) => { - this.instantiateViews(self, fn(self)) - return self - } - return this.cloneAndEnhance({ initializers: [viewInitializer] }) + views(views: V) { + return this.cloneAndEnhance({ viewDescriptors: [views] }) } - private instantiateViews(self: this["T"], views: Object): void { + private instantiateViews(self: this["T"], views: Object | (() => Object)): void { + if (typeof views === "function") views = views() + // check views return - if (!isPlainObject(views)) - throw fail(`views initializer should return a plain object containing views`) + if (!isPlainObject(views)) { + // throw fail(`views initializer should return a plain object containing views`) + // @ts-ignore + views = views(self) + } Object.getOwnPropertyNames(views).forEach((key) => { - // is this a computed property? + if (key in self.prototype || key in this.properties) { + throw fail(`${key} property is declared twice`) + } + const descriptor = Object.getOwnPropertyDescriptor(views, key)! if ("get" in descriptor) { - defineProperty(self, key, descriptor) - makeObservable(self, { [key]: computed } as any) + Object.defineProperty(self.prototype, key, descriptor) } else if (typeof descriptor.value === "function") { // this is a view function, merge as is! // See #646, allow models to be mocked @@ -540,12 +578,52 @@ export class ModelType< environment: any, initialValue: this["C"] | this["T"] ): this["N"] { + // Optimization: record all prop- view- and action names after first construction, and generate an optimal base class + // that pre-reserves all these fields for fast object-member lookups + if (!this.baseClass) { + const options = { ...mobxShallow, name: this.describe() } + // @ts-ignore + this.baseClass = class { + constructor(childNodes: IChildNodesMap) { + // const annotations = {} as AnnotationsMap + Object.keys(childNodes).forEach((key) => { + // @ts-ignore + this[key] = childNodes[key] + // annotations[key] = observable + }) + // makeObservable(this, annotations, options); + + // makeAutoObservable(this, EMPTY_OBJECT, options) + + // Binds all actions to the current instance. + // Remove this because autoObservable does it. + const prototype = Reflect.getPrototypeOf(this)! + for (const key of Reflect.ownKeys(prototype)) { + if (key === "constructor") continue + const descriptor = Reflect.getOwnPropertyDescriptor(prototype, key) + if (descriptor && typeof descriptor.value === "function") { + // @ts-ignore + this[key] = this[key].bind(this) + } + } + } + } + Object.defineProperty(this.baseClass, "name", { value: this.name }) + // @ts-ignore + this.volatileDescriptors.forEach((v) => + // @ts-ignore + this.instantiateVolatileState(this.baseClass, v) + ) + // @ts-ignore + this.viewDescriptors.forEach((v) => this.instantiateViews(this.baseClass, v)) + // @ts-ignore + this.actionDescriptors.forEach((v) => this.instantiateActions(this.baseClass, v)) + } + const value = isStateTreeNode(initialValue) ? initialValue : this.applySnapshotPreProcessor(initialValue) return createObjectNode(this, parent, subpath, environment, value) - // Optimization: record all prop- view- and action names after first construction, and generate an optimal base class - // that pre-reserves all these fields for fast object-member lookups } initializeChildNodes(objNode: this["N"], initialSnapshot: any = {}): IChildNodesMap { @@ -563,8 +641,8 @@ export class ModelType< } createNewInstance(childNodes: IChildNodesMap): this["T"] { - const options = { ...mobxShallow, name: this.describe() } - return observable.object(childNodes, EMPTY_OBJECT, options) as any + // @ts-ignore + return new this.baseClass!(childNodes) } finalizeNewInstance(node: this["N"], instance: this["T"]): void { @@ -574,7 +652,6 @@ export class ModelType< _interceptReads(instance, name, node.unbox) }) - this.initializers.reduce((self, fn) => fn(self), instance) intercept(instance, this.willChange) observe(instance, this.didChange) } @@ -838,7 +915,8 @@ export function compose(...args: any[]): any { prev.cloneAndEnhance({ name: prev.name + "_" + cur.name, properties: cur.properties, - initializers: cur.initializers, + actionDescriptors: cur.actionDescriptors, + viewDescriptors: cur.viewDescriptors, preProcessor: (snapshot: any) => cur.applySnapshotPreProcessor(prev.applySnapshotPreProcessor(snapshot)), postProcessor: (snapshot: any) =>