From 2758269139f1577498added3eb2a2b168bab1c93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Tue, 26 Mar 2019 14:30:27 +0100 Subject: [PATCH] add patch/unpatch utility methods --- src/utils.ts | 51 ++++++++++++++++++++++++++++++++++++++++ tests/utils.test.ts | 57 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 107 insertions(+), 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index 12a76fb6..4f686290 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -125,3 +125,54 @@ export function shallowEqual(objA, objB) { } return true; } + +export function patch(C: any, patchName: string, patch: any) { + const proto = C.prototype; + if (!proto.__patches) { + proto.__patches = { + origMethods: {}, + patches: {}, + current: [] + }; + } + if (proto.__patches.patches[patchName]) { + throw new Error(`Patch [${patchName}] already exists`); + } + proto.__patches.patches[patchName] = patch; + applyPatch(proto, patch); + proto.__patches.current.push(patchName); + + function applyPatch(proto, patch) { + Object.keys(patch).forEach(function(methodName) { + const method = patch[methodName]; + if (typeof method === "function") { + const original = proto[methodName]; + if (!(methodName in proto.__patches.origMethods)) { + proto.__patches.origMethods[methodName] = original; + } + proto[methodName] = function(...args) { + this._super = original; + return method.call(this, ...args); + }; + } + }); + } +} + +export function unpatch(C: any, patchName: string) { + const proto = C.prototype; + const patchInfo = proto.__patches; + delete proto.__patches; + + // reset to original + for (let k in patchInfo.origMethods) { + proto[k] = patchInfo.origMethods[k]; + } + + // apply other patches + for (let name of patchInfo.current) { + if (name !== patchName) { + patch(C, name, patchInfo.patches[name]); + } + } +} diff --git a/tests/utils.test.ts b/tests/utils.test.ts index 103c3593..980be636 100644 --- a/tests/utils.test.ts +++ b/tests/utils.test.ts @@ -5,7 +5,9 @@ import { memoize, debounce, findInTree, - shallowEqual + shallowEqual, + patch, + unpatch } from "../src/utils"; describe("escape", () => { @@ -103,3 +105,56 @@ describe("shallowEqual", () => { expect(shallowEqual({ a: 1 }, ["a"])).toBe(false); }); }); + +describe("patch/unpatch", () => { + test("can monkey patch a class", () => { + class Test { + n = 1; + + doSomething(): string { + return "hey"; + } + } + + patch(Test, "some_custo", { + doSomething(): string { + this.n = this.n + 1; + return this._super(); + } + }); + + const t = new Test(); + expect(t.n).toBe(1); + expect(t.doSomething()).toBe("hey"); + expect(t.n).toBe(2); + }); + + test("cannot patch a class twice with same patch name", () => { + class Test {} + + patch(Test, "some_custo", {}); + expect(() => { + patch(Test, "some_custo", {}); + }).toThrow(); + }); + + test("can unpatch a class", () => { + class Test { + doSomething(): number { + return 1; + } + } + + patch(Test, "some_custo", { + doSomething(): number { + return this._super() + 2; + } + }); + + const t = new Test(); + expect(t.doSomething()).toBe(3); + + unpatch(Test, "some_custo"); + expect(t.doSomething()).toBe(1); + }); +});