@arrai-innovations/reactive-helpers 8.0.4 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintrc.cjs CHANGED
@@ -7,10 +7,6 @@ module.exports = {
7
7
  plugins: ["jsdoc"],
8
8
  extends: ["plugin:vue/vue3-essential", "eslint:recommended", "@vue/prettier", "plugin:jsdoc/recommended-error"],
9
9
 
10
- parserOptions: {
11
- parser: "babel-eslint",
12
- },
13
-
14
10
  rules: {
15
11
  "no-console": "off", // console.error is useful.
16
12
  "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off",
@@ -8,8 +8,9 @@ diffCode=$?
8
8
  # we want to return a non-zero exit code if the files are different, to stop the commit
9
9
  if [ $diffCode != 0 ]; then
10
10
  echo "docs.md is out of date, see git diff for details"
11
- mv ./docs.new.md ./docs.md
11
+ mv -f ./docs.new.md ./docs.md
12
12
  exit $diffCode
13
13
  fi
14
- set -e
15
- rm ./docs.new.md
14
+ rm -f ./docs.new.md
15
+ git rm ./docs.new.md > /dev/null 2>&1
16
+ exit 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arrai-innovations/reactive-helpers",
3
- "version": "8.0.4",
3
+ "version": "8.1.0",
4
4
  "description": "VueJS 3 utility composition functions to help manipulate objects and lists.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -10,7 +10,7 @@
10
10
  "tests": "tests"
11
11
  },
12
12
  "scripts": {
13
- "test": "jest",
13
+ "test": "vitest",
14
14
  "coverage": "npm run coverage:clean; npm test -- --coverage=true",
15
15
  "coverage:clean": "rm -rf coverage",
16
16
  "prepare": "husky install",
@@ -28,32 +28,29 @@
28
28
  "homepage": "https://github.com/arrai-innovations/reactive-helpers#readme",
29
29
  "devDependencies": {
30
30
  "@arrai-innovations/commitlint-config": "^1.1.0",
31
- "@babel/preset-env": "^7.17.10",
32
31
  "@commitlint/cli": "^16.2.4",
33
32
  "@godaddy/dmd": "^1.0.4",
34
33
  "@trivago/prettier-plugin-sort-imports": "^4.1.1",
35
- "@types/jest": "^27.5.0",
34
+ "@vitest/coverage-v8": "^0.32.2",
36
35
  "@vue/compiler-sfc": "^3.3.4",
37
36
  "@vue/eslint-config-prettier": "^7.0.0",
38
37
  "@vue/test-utils": "^2.3.2",
39
- "@vue/vue3-jest": "^27.0.0",
40
- "babel-eslint": "^10.1.0",
41
- "babel-jest": "^27.5.1",
42
38
  "doctoc": "^2.1.0",
43
- "eslint": "^7.32.0",
44
- "eslint-plugin-jest": "^26.1.5",
39
+ "eslint": ">=8.0.0",
45
40
  "eslint-plugin-jsdoc": "^46.2.6",
41
+ "eslint-plugin-vitest": "0.2.6",
42
+ "eslint-plugin-vitest-globals": "^1.3.1",
46
43
  "eslint-plugin-vue": "^8.7.1",
47
44
  "flush-promises": "^1.0.2",
48
45
  "jsdoc-to-markdown": "^8.0.0",
49
46
  "lint-staged": "^13.2.2",
50
- "prettier": "2.6.2"
47
+ "prettier": "2.6.2",
48
+ "vitest": "^0.32.2"
51
49
  },
52
50
  "dependencies": {
53
51
  "browser-util-inspect": "^0.2.0",
54
52
  "flexsearch": "^0.7.21",
55
53
  "husky": "^7.0.4",
56
- "jest": "^27.5.1",
57
54
  "pinst": "^3.0.0",
58
55
  "vue-deepunref": "^1.0.1"
59
56
  },
@@ -1,10 +1,10 @@
1
1
  module.exports = {
2
- plugins: ["jest"],
3
- extends: ["plugin:jest/recommended"],
2
+ plugins: ["vitest"],
3
+ extends: ["eslint:recommended", "plugin:vitest/recommended", "plugin:vitest-globals/recommended"],
4
4
  env: {
5
- "jest/globals": true,
5
+ "vitest-globals/env": true,
6
6
  },
7
7
  rules: {
8
- "jest/no-conditional-expect": "off",
8
+ "vitest/no-conditional-expect": "off",
9
9
  },
10
10
  };
@@ -11,7 +11,7 @@ export class CancellableResolvable {
11
11
  constructor() {
12
12
  const newResolvable = new Resolvable();
13
13
  const cancelResolvable = new Resolvable();
14
- newResolvable.promise.cancel = jest
14
+ newResolvable.promise.cancel = vi
15
15
  .fn()
16
16
  .mockImplementationOnce(async () => cancelResolvable.promise)
17
17
  .mockRejectedValue(new Error("cancel already called"));
@@ -1,8 +1,8 @@
1
1
  export function getMockOnUnmounted() {
2
2
  const mockOnUnmounted = {};
3
- jest.mock("vue", () => ({
3
+ vi.mock("vue", () => ({
4
4
  _esModule: true,
5
- ...jest.requireActual("vue"),
5
+ ...vi.requireActual("vue"),
6
6
  onUnmounted: () => mockOnUnmounted,
7
7
  }));
8
8
  return mockOnUnmounted;
@@ -0,0 +1,150 @@
1
+ import { useCancellableIntent } from "../../../use/index.js";
2
+ import { CancellableResolvable } from "../crudPromise.js";
3
+ import flushPromises from "flush-promises";
4
+ import { nextTick, reactive, ref } from "vue";
5
+
6
+ describe("use/cancellableIntent", () => {
7
+ let mockAwaitableWithCancel, cancellableResolvable;
8
+ beforeEach(async () => {
9
+ cancellableResolvable = new CancellableResolvable();
10
+ mockAwaitableWithCancel = vi.fn().mockReturnValue(cancellableResolvable.promise);
11
+ });
12
+ afterEach(() => {
13
+ cancellableResolvable = null;
14
+ vi.resetAllMocks();
15
+ });
16
+ it("should throw an error if awaitableWithCancel is not provided", () => {
17
+ expect(() => {
18
+ useCancellableIntent({});
19
+ }).toThrow("awaitableWithCancel is required");
20
+ });
21
+
22
+ it("should throw an error if awaitableWithCancel is not a function", () => {
23
+ expect(() => {
24
+ useCancellableIntent({
25
+ awaitableWithCancel: "not a function",
26
+ });
27
+ }).toThrow("awaitableWithCancel must be a function");
28
+ });
29
+ describe("Resolution", () => {
30
+ it("should initiate the intent and resolve the promise when the awaitableWithCancel resolves", async () => {
31
+ const subscribeIntent = useCancellableIntent({
32
+ awaitableWithCancel: mockAwaitableWithCancel,
33
+ watchArguments: {
34
+ testArg: 1,
35
+ },
36
+ });
37
+ await nextTick();
38
+ await flushPromises();
39
+ expect(mockAwaitableWithCancel).toHaveBeenCalledTimes(1);
40
+ expect(subscribeIntent.state.active).toBe(true);
41
+ expect(subscribeIntent.state.resolving).toBe(true);
42
+
43
+ cancellableResolvable.resolve(true);
44
+
45
+ await nextTick();
46
+ await flushPromises();
47
+
48
+ expect(subscribeIntent.state.active).toBe(false);
49
+ expect(subscribeIntent.state.resolving).toBe(false);
50
+ });
51
+ });
52
+
53
+ describe("Cancellation", () => {
54
+ it("should cancel the previous promise when watched arguments changed", async () => {
55
+ let testArgRef = ref(1);
56
+ const subscribeIntent = useCancellableIntent({
57
+ awaitableWithCancel: mockAwaitableWithCancel,
58
+ watchArguments: reactive({
59
+ testArg: testArgRef,
60
+ }),
61
+ clearActiveOnResolved: true,
62
+ });
63
+ await nextTick();
64
+ await flushPromises();
65
+ expect(mockAwaitableWithCancel).toHaveBeenCalledTimes(1);
66
+ expect(subscribeIntent.state.active).toBe(true);
67
+ expect(subscribeIntent.state.resolving).toBe(true);
68
+ testArgRef.value = 2;
69
+
70
+ await nextTick();
71
+ await flushPromises();
72
+ expect(cancellableResolvable.promise.cancel).toHaveBeenCalledTimes(1);
73
+ expect(subscribeIntent.state.active).toBe(true);
74
+ expect(subscribeIntent.state.resolving).toBe(true);
75
+
76
+ cancellableResolvable.resolve(true);
77
+ await nextTick();
78
+ await flushPromises();
79
+ expect(subscribeIntent.state.active).toBe(false);
80
+ expect(subscribeIntent.state.resolving).toBe(false);
81
+ });
82
+ });
83
+
84
+ describe("Delay", () => {
85
+ it("should handle the delayed watch properly", async () => {
86
+ let testGuardRef = ref(true);
87
+ const subscribeIntent = useCancellableIntent({
88
+ awaitableWithCancel: mockAwaitableWithCancel,
89
+ watchArguments: {
90
+ testArg: 1,
91
+ },
92
+ guardArguments: reactive({
93
+ testGuard: testGuardRef,
94
+ }),
95
+ });
96
+
97
+ expect(mockAwaitableWithCancel).not.toHaveBeenCalled();
98
+ expect(subscribeIntent.state.activeCount).toBeUndefined();
99
+ expect(subscribeIntent.state.resolvingCount).toBeUndefined();
100
+
101
+ testGuardRef.value = false;
102
+ await nextTick();
103
+ await flushPromises();
104
+ expect(mockAwaitableWithCancel).toHaveBeenCalled();
105
+ expect(subscribeIntent.state.active).toBe(true);
106
+ expect(subscribeIntent.state.resolving).toBe(true);
107
+
108
+ cancellableResolvable.resolve(true);
109
+ await nextTick();
110
+ await flushPromises();
111
+ expect(subscribeIntent.state.active).toBe(false);
112
+ expect(subscribeIntent.state.resolving).toBe(false);
113
+ });
114
+ });
115
+ describe("Rejection", () => {
116
+ //error is not being caught
117
+ it.skip("errored", async () => {
118
+ const consoleErrorMock = vi.spyOn(console, "error").mockImplementation(() => {});
119
+ const subscribeIntent = useCancellableIntent({
120
+ awaitableWithCancel: mockAwaitableWithCancel,
121
+ watchArguments: {
122
+ testArg: 1,
123
+ },
124
+ });
125
+ await nextTick();
126
+ await flushPromises();
127
+
128
+ const mockError = new Error("rejected");
129
+ cancellableResolvable.reject(mockError);
130
+
131
+ await nextTick();
132
+ await flushPromises();
133
+
134
+ expect(cancellableResolvable.promise.cancel).toHaveBeenCalledTimes(1);
135
+ expect(subscribeIntent.state.active).toBe(true);
136
+ expect(subscribeIntent.state.resolving).toBe(true);
137
+
138
+ await cancellableResolvable.cancel.resolve(true);
139
+ await nextTick();
140
+ await flushPromises();
141
+
142
+ await expect(cancellableResolvable.promise).rejects.toThrow(mockError);
143
+ expect(consoleErrorMock).toHaveBeenCalledWith(mockError);
144
+ expect(subscribeIntent.state.active).toBe(false);
145
+ expect(subscribeIntent.state.resolving).toBe(false);
146
+ expect(subscribeIntent.state.errored).toBe(true);
147
+ expect(subscribeIntent.state.error).toBe(mockError);
148
+ });
149
+ });
150
+ });
@@ -10,8 +10,8 @@ describe("use/listCalculated", () => {
10
10
  useListCalculated = listCalculatedModule.useListCalculated;
11
11
  });
12
12
  it("should return a list of calculated items", async () => {
13
- const mainListInstance = useListInstance({});
14
- const calculatedListInstance = useListInstance({});
13
+ const mainListInstance = useListInstance({ props: {} });
14
+ const calculatedListInstance = useListInstance({ props: {} });
15
15
  mainListInstance.addListObject({
16
16
  id: "1",
17
17
  name: "main",
@@ -36,15 +36,13 @@ describe("use/listCalculated", () => {
36
36
  calculatedItems: (obj) => obj.calculated_items.map((x) => calculatedListInstance.state.objects[x]),
37
37
  calculatedItem: (obj) => calculatedListInstance.state.objects[obj.calculated_id],
38
38
  },
39
- calculatedObjectsPropertyName: "myCalculatedObjects",
40
39
  });
41
40
  await nextTick();
42
41
  // listCalculated.state.objects is doing proxy shenanigans
43
42
  // in uses handler.has
44
- expect("myCalculatedObjects" in listCalculated.state).toBe(true);
45
- expect(!!listCalculated.state.myCalculatedObjects?.[1]).toBe(true);
46
- expect("calculatedItems" in listCalculated.state.myCalculatedObjects[1]).toBe(true);
47
- expect("calculatedItem" in listCalculated.state.myCalculatedObjects[1]).toBe(true);
43
+ expect(!!listCalculated.state.calculatedObjects?.[1]).toBe(true);
44
+ expect("calculatedItems" in listCalculated.state.calculatedObjects[1]).toBe(true);
45
+ expect("calculatedItem" in listCalculated.state.calculatedObjects[1]).toBe(true);
48
46
  // expect uses enumeration, which uses handler.ownKeys and handler.getOwnPropertyDescriptor
49
47
  expect(deepUnref(listCalculated.state.objects)).toEqual({
50
48
  1: {
@@ -54,7 +52,7 @@ describe("use/listCalculated", () => {
54
52
  calculated_items: ["2", "3"],
55
53
  },
56
54
  });
57
- expect(deepUnref(listCalculated.state.myCalculatedObjects)).toEqual({
55
+ expect(deepUnref(listCalculated.state.calculatedObjects)).toEqual({
58
56
  1: {
59
57
  calculatedItems: [
60
58
  {
@@ -18,7 +18,7 @@ describe("use/listFilter", () => {
18
18
  });
19
19
 
20
20
  it("should match an allowed values list", async () => {
21
- const list = useListInstance({});
21
+ const list = useListInstance({ props: {} });
22
22
  const filter = useListFilter({
23
23
  parentState: list.state,
24
24
  allowedValues: reactive({
@@ -52,7 +52,7 @@ describe("use/listFilter", () => {
52
52
  });
53
53
  });
54
54
  it("should match an allowed filter function", async () => {
55
- const list = useListInstance({});
55
+ const list = useListInstance({ props: {} });
56
56
  const filter = useListFilter({
57
57
  parentState: list.state,
58
58
  allowedFilter: (object) => object.id == 1 || object.id == 3,
@@ -84,7 +84,7 @@ describe("use/listFilter", () => {
84
84
  });
85
85
  it("should match by search term", async () => {
86
86
  const textSearchValue = ref("one");
87
- const list = useListInstance({});
87
+ const list = useListInstance({ props: {} });
88
88
  const filter = useListFilter({
89
89
  parentState: list.state,
90
90
  useTextSearch: true,
@@ -151,7 +151,7 @@ describe("use/listFilter", () => {
151
151
  });
152
152
  });
153
153
  it("should match an excluded filter function", async () => {
154
- const list = useListInstance({});
154
+ const list = useListInstance({ props: {} });
155
155
  const filter = useListFilter({
156
156
  parentState: list.state,
157
157
  excludedFilter: (object) => object.id == 2 || object.id == 4,
@@ -182,7 +182,7 @@ describe("use/listFilter", () => {
182
182
  });
183
183
  });
184
184
  it("should exclude an excludedValues parameter", async () => {
185
- const list = useListInstance({});
185
+ const list = useListInstance({ props: {} });
186
186
  const filter = useListFilter({
187
187
  parentState: list.state,
188
188
  excludedValues: reactive({
@@ -216,7 +216,7 @@ describe("use/listFilter", () => {
216
216
  });
217
217
  });
218
218
  it("no args: returns objects unfiltered", async () => {
219
- const listInstance = useListInstance({});
219
+ const listInstance = useListInstance({ props: {} });
220
220
  const listItems = [
221
221
  { id: 4, name: "four", has_things: true },
222
222
  { id: 2, name: "two", has_things: true },
@@ -235,10 +235,10 @@ describe("use/listFilter", () => {
235
235
  });
236
236
  describe("useListFilter operates on parentState modified by useListSort", () => {
237
237
  it("computes state.order and state.objects in order", async () => {
238
- jest.resetAllMocks();
238
+ vi.resetAllMocks();
239
239
  const orderByRules = [{ key: "name", desc: true, localeCompare: false }];
240
240
  const sortThrottleWait = 0;
241
- const listInstance = useListInstance({});
241
+ const listInstance = useListInstance({ props: {} });
242
242
  const listItems = [
243
243
  { id: 4, name: "four", has_things: true },
244
244
  { id: 2, name: "two", has_things: true },
@@ -266,20 +266,24 @@ describe("use/listFilter", () => {
266
266
  });
267
267
  describe("useListFilters accepts args and parentInstances", () => {
268
268
  it("returns filtered objects", async () => {
269
- jest.resetAllMocks();
269
+ vi.resetAllMocks();
270
270
  const fields = ["id", "__str__", "name"];
271
271
  const listInstanceA = useListInstance({
272
- crudArgs: { stream: "test_streamA" },
273
- listArgs: { user: 1 },
274
- retrieveArgs: {
275
- fields,
272
+ props: {
273
+ crudArgs: { stream: "test_streamA" },
274
+ listArgs: { user: 1 },
275
+ retrieveArgs: {
276
+ fields,
277
+ },
276
278
  },
277
279
  });
278
280
  const listInstanceB = useListInstance({
279
- crudArgs: { stream: "test_streamB" },
280
- listArgs: { user: 2 },
281
- retrieveArgs: {
282
- fields,
281
+ props: {
282
+ crudArgs: { stream: "test_streamB" },
283
+ listArgs: { user: 2 },
284
+ retrieveArgs: {
285
+ fields,
286
+ },
283
287
  },
284
288
  });
285
289
  const listFilterA = useListFilter({