Skip to content

Commit 082210f

Browse files
authored
Merge pull request #1691 from ramybenaroya/compose-property-decorator-mobx4
decorate - compose decorators for a single prop (mobx4)
2 parents ad4288d + a703d0c commit 082210f

File tree

4 files changed

+110
-8
lines changed

4 files changed

+110
-8
lines changed

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
"rollup": "^0.41.6",
6767
"rollup-plugin-filesize": "^1.3.2",
6868
"rollup-plugin-node-resolve": "^3.0.0",
69+
"serializr": "^1.3.0",
6970
"size-limit": "^0.2.0",
7071
"tape": "^4.2.2",
7172
"ts-jest": "^22.0.0",
@@ -115,4 +116,4 @@
115116
"<rootDir>/node_modules/"
116117
]
117118
}
118-
}
119+
}

src/api/decorate.ts

+24-6
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,43 @@ import { invariant, isPlainObject } from "../utils/utils"
22

33
export function decorate<T>(
44
clazz: new (...args: any[]) => T,
5-
decorators: { [P in keyof T]?: MethodDecorator | PropertyDecorator }
5+
decorators: {
6+
[P in keyof T]?:
7+
| MethodDecorator
8+
| PropertyDecorator
9+
| Array<MethodDecorator>
10+
| Array<PropertyDecorator>
11+
}
612
): void
713
export function decorate<T>(
814
object: T,
9-
decorators: { [P in keyof T]?: MethodDecorator | PropertyDecorator }
15+
decorators: {
16+
[P in keyof T]?:
17+
| MethodDecorator
18+
| PropertyDecorator
19+
| Array<MethodDecorator>
20+
| Array<PropertyDecorator>
21+
}
1022
): T
1123
export function decorate(thing: any, decorators: any) {
1224
process.env.NODE_ENV !== "production" &&
1325
invariant(isPlainObject(decorators), "Decorators should be a key value map")
1426
const target = typeof thing === "function" ? thing.prototype : thing
1527
for (let prop in decorators) {
16-
const decorator = decorators[prop]
28+
let propertyDecorators = decorators[prop]
29+
if (!Array.isArray(propertyDecorators)) {
30+
propertyDecorators = [propertyDecorators]
31+
}
1732
process.env.NODE_ENV !== "production" &&
1833
invariant(
19-
typeof decorator === "function",
20-
`Decorate: expected a decorator function for '${prop}'`
34+
propertyDecorators.every(decorator => typeof decorator === "function"),
35+
`Decorate: expected a decorator function or array of decorator functions for '${prop}'`
2136
)
2237
const descriptor = Object.getOwnPropertyDescriptor(target, prop)
23-
const newDescriptor = decorator(target, prop, descriptor)
38+
const newDescriptor = propertyDecorators.reduce(
39+
(accDescriptor, decorator) => decorator(target, prop, accDescriptor),
40+
descriptor
41+
)
2442
if (newDescriptor) Object.defineProperty(target, prop, newDescriptor)
2543
}
2644
return thing

test/base/decorate.js

+80-1
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@ import {
1414
isComputedProp,
1515
spy,
1616
isAction,
17-
decorate
17+
decorate,
18+
reaction
1819
} from "../../src/mobx"
1920

21+
import { serializable, primitive, serialize, deserialize } from "serializr"
22+
2023
test("decorate should work", function() {
2124
class Box {
2225
// @ts-ignore
@@ -368,3 +371,79 @@ test("decorate should not allow @observable on getter", function() {
368371
expect(() => obj.x).toThrow(/"y"/)
369372
expect(() => obj.y).toThrow()
370373
})
374+
375+
test("decorate a function property with two decorators", function() {
376+
let callsCount = 0
377+
let spyCount = 0
378+
const spyDisposer = spy(ev => {
379+
if (ev.type === "action" && ev.name === "fn") spyCount++
380+
})
381+
382+
const countFunctionCallsDecorator = (target, key, descriptor) => {
383+
const func = descriptor.value
384+
descriptor.value = function wrapper(...args) {
385+
const result = func.call(this, ...args)
386+
callsCount++
387+
return result
388+
}
389+
for (const key in func) {
390+
descriptor.value[key] = func[key]
391+
}
392+
return descriptor
393+
}
394+
395+
class Obj {
396+
fn() {}
397+
}
398+
399+
decorate(Obj, {
400+
fn: [action("fn"), countFunctionCallsDecorator]
401+
})
402+
403+
const obj = new Obj()
404+
405+
expect(isAction(obj.fn)).toBe(true)
406+
407+
obj.fn()
408+
409+
expect(callsCount).toEqual(1)
410+
expect(spyCount).toEqual(1)
411+
412+
obj.fn()
413+
414+
expect(callsCount).toEqual(2)
415+
expect(spyCount).toEqual(2)
416+
417+
spyDisposer()
418+
})
419+
420+
test("decorate a property with two decorators", function() {
421+
let updatedByAutorun
422+
423+
class Obj {
424+
x = null
425+
}
426+
427+
decorate(Obj, {
428+
x: [serializable(primitive()), observable]
429+
})
430+
431+
const obj = deserialize(Obj, {
432+
x: 0
433+
})
434+
435+
const d = autorun(() => {
436+
updatedByAutorun = obj.x
437+
})
438+
439+
expect(isObservableProp(obj, "x")).toBe(true)
440+
expect(updatedByAutorun).toEqual(0)
441+
442+
obj.x++
443+
444+
expect(obj.x).toEqual(1)
445+
expect(updatedByAutorun).toEqual(1)
446+
expect(serialize(obj).x).toEqual(1)
447+
448+
d()
449+
})

yarn.lock

+4
Original file line numberDiff line numberDiff line change
@@ -4982,6 +4982,10 @@ [email protected]:
49824982
range-parser "~1.2.0"
49834983
statuses "~1.3.1"
49844984

4985+
serializr@^1.3.0:
4986+
version "1.3.0"
4987+
resolved "https://registry.yarnpkg.com/serializr/-/serializr-1.3.0.tgz#6c7f977461d54a24bb1f17a03ed0ce61d239b010"
4988+
49854989
49864990
version "1.12.3"
49874991
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.12.3.tgz#9f4ba19e2f3030c547f8af99107838ec38d5b1e2"

0 commit comments

Comments
 (0)