Skip to content

Commit a6bd922

Browse files
feat: add WILDCARD and toPrimitive support
1 parent aa70d93 commit a6bd922

File tree

8 files changed

+357
-31
lines changed

8 files changed

+357
-31
lines changed

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,9 +200,17 @@ See the [Proxy Handler functions](https://developer.mozilla.org/en-US/docs/Web/J
200200
These symbols are used to construct paths for the following functions:
201201

202202
- `recursiveProxyMock` - the `path` property of the override object
203+
203204
- `hasPathBeenVisited` - the `path` argument to check if that has ever been visited
205+
204206
- `getVisitedPathData` - the `path` argument to query path data for all visits to that path
205207

208+
- There is also a special `ProxySymbol.WILDCARD` which can be used to match 0+ path segments. This is especially useful when mocking a chainable library where the same method can be called with many different paths. So the path for `click()` in `$("div").css("color", "blue").click()` could be expressed as `[ProxySymbol.WILDCARD, "click", ProxySymbol.APPLY]`.
209+
210+
- Do not include a wildcard at the end of the path, only the beginning or middle.
211+
212+
- You can also use the built-in `Symbol.toPrimitive` which can appear as the `prop` of the `GET` proxy handler.
213+
206214
### `hasPathBeenVisited(proxy, path) => boolean`
207215

208216
Function to check if a certain path was ever visited. Useful in conjunction with test assertions.
@@ -234,7 +242,7 @@ A `ProxyData` object contains any relevant details about the operation. For exam
234242

235243
- `APPLY`, `CONSTRUCT`:
236244
- `args` - array of arguments that were passed to the function or constructor
237-
- `DELETE_PROPERTY`, `GET_OWN_PROPERTY_DESCRIPTOR`, `HAS`, `GET` (default):
245+
- `DELETE_PROPERTY`, `GET_OWN_PROPERTY_DESCRIPTOR`, `HAS`, `GET`:
238246
- `prop` - the name of the property which was accessed/operated on
239247
- `SET`:
240248
- `prop` - the name of the property which was accessed/operated on

cspell.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"version": "0.1",
3-
"words": ["bundlephobia", "commitlint", "corejs", "eqeqeq", "iife", "lcov", "ttypescript", "webgl"],
3+
"words": ["bundlephobia", "chainable", "commitlint", "corejs", "eqeqeq", "iife", "lcov", "ttypescript", "webgl"],
44
"ignoreRegExpList": ["\\(#.*\\)"],
55
"ignorePaths": ["/coverage", "/dist", "package-lock.json", "package.json"]
66
}

src/ProxySymbol/ProxySymbol.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const ProxySymbol = {
1717
PREVENT_EXTENSIONS: Symbol("preventExtensions"),
1818
SET: Symbol("set"),
1919
SET_PROTOTYPE_OF: Symbol("setPrototypeOf"),
20+
WILDCARD: Symbol("wildcard"),
2021
} as const;
2122

2223
export const ProxyStackSymbol = Symbol("proxyStack");

src/recursiveProxyMock/__snapshots__/recursiveProxyMock.test.ts.snap

Lines changed: 228 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -232,19 +232,46 @@ Array [
232232
"name": "get",
233233
"parent": 0,
234234
"pathKey": Array [
235-
"a",
235+
"num",
236236
],
237-
"prop": "a",
237+
"prop": "num",
238238
"self": 1,
239239
},
240240
Object {
241241
"name": "get",
242242
"parent": 1,
243+
"pathKey": Array [
244+
"val",
245+
],
246+
"prop": "val",
247+
"self": 2,
248+
},
249+
Object {
250+
"name": "get",
251+
"parent": 2,
252+
"pathKey": Array [
253+
Symbol(Symbol.toPrimitive),
254+
],
255+
"prop": Symbol(Symbol.toPrimitive),
256+
"self": 3,
257+
},
258+
Object {
259+
"name": "get",
260+
"parent": 0,
261+
"pathKey": Array [
262+
"a",
263+
],
264+
"prop": "a",
265+
"self": 4,
266+
},
267+
Object {
268+
"name": "get",
269+
"parent": 4,
243270
"pathKey": Array [
244271
"b",
245272
],
246273
"prop": "b",
247-
"self": 2,
274+
"self": 5,
248275
},
249276
Object {
250277
"name": "get",
@@ -253,24 +280,24 @@ Array [
253280
"b",
254281
],
255282
"prop": "b",
256-
"self": 3,
283+
"self": 6,
257284
},
258285
Object {
259286
"name": "get",
260-
"parent": 3,
287+
"parent": 6,
261288
"pathKey": Array [
262289
"a",
263290
],
264291
"prop": "a",
265-
"self": 4,
292+
"self": 7,
266293
},
267294
Object {
268295
"name": "getPrototypeOf",
269-
"parent": 4,
296+
"parent": 7,
270297
"pathKey": Array [
271298
Symbol(getPrototypeOf),
272299
],
273-
"self": 5,
300+
"self": 8,
274301
},
275302
]
276303
`;
@@ -741,3 +768,196 @@ Array [
741768
},
742769
]
743770
`;
771+
772+
exports[`recursiveProxyMock supports math operations on primitive 1`] = `
773+
Array [
774+
Object {
775+
"name": "get",
776+
"parent": 0,
777+
"pathKey": Array [
778+
"test",
779+
],
780+
"prop": "test",
781+
"self": 1,
782+
},
783+
Object {
784+
"name": "get",
785+
"parent": 1,
786+
"pathKey": Array [
787+
Symbol(Symbol.toPrimitive),
788+
],
789+
"prop": Symbol(Symbol.toPrimitive),
790+
"self": 2,
791+
},
792+
Object {
793+
"name": "set",
794+
"parent": 0,
795+
"pathKey": Array [
796+
"test",
797+
Symbol(set),
798+
],
799+
"prop": "test",
800+
"value": 0,
801+
},
802+
Object {
803+
"name": "get",
804+
"parent": 0,
805+
"pathKey": Array [
806+
"a",
807+
],
808+
"prop": "a",
809+
"self": 3,
810+
},
811+
Object {
812+
"name": "get",
813+
"parent": 0,
814+
"pathKey": Array [
815+
"c",
816+
],
817+
"prop": "c",
818+
"self": 4,
819+
},
820+
Object {
821+
"name": "get",
822+
"parent": 4,
823+
"pathKey": Array [
824+
"d",
825+
],
826+
"prop": "d",
827+
"self": 5,
828+
},
829+
Object {
830+
"name": "get",
831+
"parent": 5,
832+
"pathKey": Array [
833+
"e",
834+
],
835+
"prop": "e",
836+
"self": 6,
837+
},
838+
Object {
839+
"name": "get",
840+
"parent": 0,
841+
"pathKey": Array [
842+
"a",
843+
],
844+
"prop": "a",
845+
"self": 7,
846+
},
847+
Object {
848+
"name": "get",
849+
"parent": 7,
850+
"pathKey": Array [
851+
"b",
852+
],
853+
"prop": "b",
854+
"self": 8,
855+
},
856+
Object {
857+
"name": "get",
858+
"parent": 8,
859+
"pathKey": Array [
860+
Symbol(Symbol.toPrimitive),
861+
],
862+
"prop": Symbol(Symbol.toPrimitive),
863+
"self": 9,
864+
},
865+
Object {
866+
"name": "get",
867+
"parent": 6,
868+
"pathKey": Array [
869+
Symbol(Symbol.toPrimitive),
870+
],
871+
"prop": Symbol(Symbol.toPrimitive),
872+
"self": 10,
873+
},
874+
Object {
875+
"name": "set",
876+
"parent": 3,
877+
"pathKey": Array [
878+
"b",
879+
Symbol(set),
880+
],
881+
"prop": "b",
882+
"value": "0",
883+
},
884+
]
885+
`;
886+
887+
exports[`recursiveProxyMock supports spread as an array 1`] = `
888+
Array [
889+
Object {
890+
"name": "get",
891+
"parent": 0,
892+
"pathKey": Array [
893+
"should",
894+
],
895+
"prop": "should",
896+
"self": 1,
897+
},
898+
Object {
899+
"name": "get",
900+
"parent": 1,
901+
"pathKey": Array [
902+
"spread",
903+
],
904+
"prop": "spread",
905+
"self": 2,
906+
},
907+
Object {
908+
"name": "get",
909+
"parent": 2,
910+
"pathKey": Array [
911+
Symbol(Symbol.iterator),
912+
],
913+
"prop": Symbol(Symbol.iterator),
914+
"self": 3,
915+
},
916+
Object {
917+
"args": Array [],
918+
"name": "apply",
919+
"parent": 3,
920+
"pathKey": Array [
921+
Symbol(apply),
922+
],
923+
"self": 4,
924+
},
925+
Object {
926+
"name": "get",
927+
"parent": 4,
928+
"pathKey": Array [
929+
"next",
930+
],
931+
"prop": "next",
932+
"self": 5,
933+
},
934+
Object {
935+
"args": Array [],
936+
"name": "apply",
937+
"parent": 5,
938+
"pathKey": Array [
939+
Symbol(apply),
940+
],
941+
"self": 6,
942+
},
943+
Object {
944+
"name": "get",
945+
"parent": 6,
946+
"pathKey": Array [
947+
"done",
948+
],
949+
"prop": "done",
950+
"self": 7,
951+
},
952+
Object {
953+
"name": "set",
954+
"parent": 0,
955+
"pathKey": Array [
956+
"spread",
957+
Symbol(set),
958+
],
959+
"prop": "spread",
960+
"value": Array [],
961+
},
962+
]
963+
`;

src/recursiveProxyMock/recursiveProxyMock.test.ts

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,7 @@ describe("recursiveProxyMock", () => {
5454
const $ = mock;
5555
$("div").append("<p>Content</p>").css("color", "blue").click();
5656
}).not.toThrow();
57-
expect(
58-
hasPathBeenVisited(mock, [
59-
ProxySymbol.APPLY,
60-
"append",
61-
ProxySymbol.APPLY,
62-
"css",
63-
ProxySymbol.APPLY,
64-
"click",
65-
ProxySymbol.APPLY,
66-
])
67-
).toStrictEqual(true);
57+
expect(hasPathBeenVisited(mock, [ProxySymbol.WILDCARD, "click", ProxySymbol.APPLY])).toStrictEqual(true);
6858
});
6959

7060
test("example: complex object mock", () => {
@@ -89,6 +79,24 @@ describe("recursiveProxyMock", () => {
8979
expect(hasPathBeenVisited(resMock, ["redirect", ProxySymbol.APPLY])).toStrictEqual(true);
9080
});
9181

82+
test("supports math operations on primitive", () => {
83+
const mock = recursiveProxyMock();
84+
expect(() => {
85+
mock.test *= 10;
86+
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
87+
mock.a.b = mock.c.d.e + (2 * mock.a.b) / 2;
88+
}).not.toThrow();
89+
expect(listAllProxyOperations(mock)).toMatchSnapshot();
90+
});
91+
92+
test("supports spread as an array", () => {
93+
const mock = recursiveProxyMock();
94+
expect(() => {
95+
mock.spread = [...mock.should.spread];
96+
}).not.toThrow();
97+
expect(listAllProxyOperations(mock)).toMatchSnapshot();
98+
});
99+
92100
test("proxy trap: apply", () => {
93101
const fn = jest.fn(() => 7);
94102
const mock = recursiveProxyMock([
@@ -171,7 +179,12 @@ describe("recursiveProxyMock", () => {
171179
path: ["a", "b"],
172180
value: "potato",
173181
},
182+
{
183+
path: ["num", "val", Symbol.toPrimitive],
184+
value: 10,
185+
},
174186
]);
187+
expect(mock.num.val * 2).toStrictEqual(20);
175188
expect(mock.a.b).toStrictEqual("potato");
176189
expect(mock.b.a).not.toBeInstanceOf(String);
177190
expect(listAllProxyOperations(mock)).toMatchSnapshot();

src/recursiveProxyMock/recursiveProxyMock.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,14 @@ function recursiveProxyRecurse(overrides: ProxyOverrideConfig[], stack: ProxySta
100100
self: next,
101101
});
102102
const override = findOverrideConfig(overrides, [...currentPath, ...pathKey]);
103+
if (prop === Symbol.toPrimitive) {
104+
return (): number | string => {
105+
if (typeof override !== "undefined") {
106+
return override as number | string;
107+
}
108+
return "";
109+
};
110+
}
103111
if (typeof override !== "undefined") {
104112
return override;
105113
}

0 commit comments

Comments
 (0)