@player-ui/player 0.15.3 → 0.15.4--canary.881.37421
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/dist/Player.native.js +3259 -2768
- package/dist/Player.native.js.map +1 -1
- package/dist/cjs/index.cjs +2553 -2114
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/index.legacy-esm.js +2535 -2103
- package/dist/index.mjs +2535 -2103
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/data.test.ts +0 -13
- package/src/__tests__/view.test.ts +34 -1
- package/src/controllers/data/controller.ts +1 -1
- package/src/controllers/data/utils.ts +5 -26
- package/src/controllers/error/__tests__/controller.test.ts +359 -0
- package/src/controllers/error/__tests__/middleware.test.ts +237 -0
- package/src/controllers/error/__tests__/navigation.test.ts +190 -0
- package/src/controllers/error/controller.ts +257 -0
- package/src/controllers/error/index.ts +3 -0
- package/src/controllers/error/middleware.ts +106 -0
- package/src/controllers/error/types.ts +42 -0
- package/src/controllers/error/utils/__tests__/isErrorWithMetadata.test.ts +114 -0
- package/src/controllers/error/utils/__tests__/makeJsonStringifyReplacer.test.ts +24 -0
- package/src/controllers/error/utils/index.ts +2 -0
- package/src/controllers/error/utils/isErrorWithMetadata.ts +28 -0
- package/src/controllers/error/utils/makeJsonStringifyReplacer.ts +17 -0
- package/src/controllers/flow/__tests__/flow.test.ts +268 -0
- package/src/controllers/flow/flow.ts +96 -4
- package/src/controllers/index.ts +1 -0
- package/src/controllers/view/controller.ts +22 -3
- package/src/data/model.ts +6 -0
- package/src/expressions/types.ts +8 -4
- package/src/player.ts +20 -1
- package/src/types.ts +6 -0
- package/src/validator/types.ts +2 -1
- package/src/view/parser/types.ts +6 -3
- package/src/view/plugins/__tests__/template.test.ts +7 -2
- package/src/view/resolver/ResolverError.ts +25 -0
- package/src/view/resolver/__tests__/index.test.ts +53 -1
- package/src/view/resolver/index.ts +68 -37
- package/src/view/resolver/types.ts +13 -0
- package/src/view/resolver/utils.ts +1 -1
- package/types/controllers/data/utils.d.ts +3 -7
- package/types/controllers/error/controller.d.ts +82 -0
- package/types/controllers/error/index.d.ts +4 -0
- package/types/controllers/error/middleware.d.ts +23 -0
- package/types/controllers/error/types.d.ts +35 -0
- package/types/controllers/error/utils/index.d.ts +3 -0
- package/types/controllers/error/utils/isErrorWithMetadata.d.ts +3 -0
- package/types/controllers/error/utils/makeJsonStringifyReplacer.d.ts +5 -0
- package/types/controllers/flow/flow.d.ts +17 -0
- package/types/controllers/index.d.ts +1 -0
- package/types/controllers/view/controller.d.ts +4 -0
- package/types/data/model.d.ts +5 -0
- package/types/types.d.ts +5 -1
- package/types/view/resolver/ResolverError.d.ts +13 -0
- package/types/view/resolver/index.d.ts +2 -1
- package/types/view/resolver/types.d.ts +11 -0
package/package.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"types"
|
|
7
7
|
],
|
|
8
8
|
"name": "@player-ui/player",
|
|
9
|
-
"version": "0.15.
|
|
9
|
+
"version": "0.15.4--canary.881.37421",
|
|
10
10
|
"main": "dist/cjs/index.cjs",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@player-ui/partial-match-registry": "0.15.
|
|
13
|
-
"@player-ui/make-flow": "0.15.
|
|
14
|
-
"@player-ui/types": "0.15.
|
|
12
|
+
"@player-ui/partial-match-registry": "0.15.4--canary.881.37421",
|
|
13
|
+
"@player-ui/make-flow": "0.15.4--canary.881.37421",
|
|
14
|
+
"@player-ui/types": "0.15.4--canary.881.37421",
|
|
15
15
|
"@types/dlv": "^1.1.4",
|
|
16
16
|
"dequal": "^2.0.2",
|
|
17
17
|
"dlv": "^1.1.3",
|
|
@@ -482,17 +482,4 @@ describe("Read Only Data Controller", () => {
|
|
|
482
482
|
it("Reads data", () => {
|
|
483
483
|
expect(readOnlyController.get("some.data")).toStrictEqual(true);
|
|
484
484
|
});
|
|
485
|
-
|
|
486
|
-
it("Logs error on set", () => {
|
|
487
|
-
expect(readOnlyController.set([["some.data", false]])).toStrictEqual([]);
|
|
488
|
-
expect(logger.error).toBeCalledWith(
|
|
489
|
-
"Error: Tried to set in a read only instance of the DataController",
|
|
490
|
-
);
|
|
491
|
-
});
|
|
492
|
-
it("Logs error on delete", () => {
|
|
493
|
-
readOnlyController.delete("some.data");
|
|
494
|
-
expect(logger.error).toBeCalledWith(
|
|
495
|
-
"Error: Tried to delete in a read only instance of the DataController",
|
|
496
|
-
);
|
|
497
|
-
});
|
|
498
485
|
});
|
|
@@ -3,7 +3,7 @@ import type { Flow, NavigationFlowViewState } from "@player-ui/types";
|
|
|
3
3
|
import type { FlowController } from "../controllers";
|
|
4
4
|
import TrackBindingPlugin from "./helpers/binding.plugin";
|
|
5
5
|
import type { InProgressState } from "../types";
|
|
6
|
-
import { Player } from "..";
|
|
6
|
+
import { Player, PlayerPlugin } from "..";
|
|
7
7
|
import { ActionExpPlugin } from "./helpers/action-exp.plugin";
|
|
8
8
|
|
|
9
9
|
const minimal: Flow = {
|
|
@@ -713,3 +713,36 @@ describe("view update scheduling", () => {
|
|
|
713
713
|
});
|
|
714
714
|
});
|
|
715
715
|
});
|
|
716
|
+
|
|
717
|
+
describe("view error capturing", () => {
|
|
718
|
+
test("should capture errors caused during view resolution and send them to the errorController", async () => {
|
|
719
|
+
const errorControllerSpy = vitest.fn(() => undefined);
|
|
720
|
+
const viewFailurePlugin: PlayerPlugin = {
|
|
721
|
+
name: "ViewFailurePlugin",
|
|
722
|
+
apply: (player) => {
|
|
723
|
+
// Force resolution failures to capture in the view controller
|
|
724
|
+
player.hooks.view.tap("fail", (view) => {
|
|
725
|
+
view.hooks.resolver.tap("fail", (resolver) => {
|
|
726
|
+
resolver.hooks.beforeResolve.tap("fail", () => {
|
|
727
|
+
throw new Error("ERROR!");
|
|
728
|
+
});
|
|
729
|
+
});
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
player.hooks.errorController.tap("fail", (controller) => {
|
|
733
|
+
controller.hooks.onError.tap("fail", errorControllerSpy);
|
|
734
|
+
});
|
|
735
|
+
},
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
const player = new Player({ plugins: [viewFailurePlugin] });
|
|
739
|
+
player.start(minimal).catch(() => {});
|
|
740
|
+
await vitest.waitFor(() => {
|
|
741
|
+
expect(errorControllerSpy).toHaveBeenCalledWith(
|
|
742
|
+
expect.objectContaining({
|
|
743
|
+
cause: new Error("ERROR!"),
|
|
744
|
+
}),
|
|
745
|
+
);
|
|
746
|
+
});
|
|
747
|
+
});
|
|
748
|
+
});
|
|
@@ -1,39 +1,18 @@
|
|
|
1
1
|
import type { DataController } from ".";
|
|
2
|
-
import type { Logger } from "../../logger";
|
|
3
2
|
import type { BindingLike } from "../../binding";
|
|
4
|
-
import type {
|
|
5
|
-
DataModelWithParser,
|
|
6
|
-
DataModelOptions,
|
|
7
|
-
Updates,
|
|
8
|
-
} from "../../data";
|
|
3
|
+
import type { DataModelWithParser, DataModelOptions } from "../../data";
|
|
9
4
|
|
|
10
5
|
/** Wrapper for the Data Controller Class that prevents writes */
|
|
11
|
-
export class ReadOnlyDataController
|
|
6
|
+
export class ReadOnlyDataController
|
|
7
|
+
implements Pick<DataModelWithParser<DataModelOptions>, "get">
|
|
8
|
+
{
|
|
12
9
|
private controller: DataController;
|
|
13
|
-
private logger?: Logger;
|
|
14
10
|
|
|
15
|
-
constructor(controller: DataController
|
|
11
|
+
constructor(controller: DataController) {
|
|
16
12
|
this.controller = controller;
|
|
17
|
-
this.logger = logger;
|
|
18
13
|
}
|
|
19
14
|
|
|
20
15
|
get(binding: BindingLike, options?: DataModelOptions | undefined) {
|
|
21
16
|
return this.controller.get(binding, options);
|
|
22
17
|
}
|
|
23
|
-
|
|
24
|
-
set(
|
|
25
|
-
transaction: [BindingLike, any][],
|
|
26
|
-
options?: DataModelOptions | undefined,
|
|
27
|
-
): Updates {
|
|
28
|
-
this.logger?.error(
|
|
29
|
-
"Error: Tried to set in a read only instance of the DataController",
|
|
30
|
-
);
|
|
31
|
-
return [];
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
delete(binding: BindingLike, options?: DataModelOptions | undefined): void {
|
|
35
|
-
this.logger?.error(
|
|
36
|
-
"Error: Tried to delete in a read only instance of the DataController",
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
18
|
}
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import { describe, it, beforeEach, expect, vitest } from "vitest";
|
|
2
|
+
import { ErrorController } from "../controller";
|
|
3
|
+
import {
|
|
4
|
+
ErrorMetadata,
|
|
5
|
+
ErrorSeverity,
|
|
6
|
+
ErrorTypes,
|
|
7
|
+
PlayerErrorMetadata,
|
|
8
|
+
} from "../types";
|
|
9
|
+
import type { DataController } from "../../data/controller";
|
|
10
|
+
import type { FlowController } from "../../flow/controller";
|
|
11
|
+
import type { Logger } from "../../../logger";
|
|
12
|
+
|
|
13
|
+
/** Test class to create an error with any additional properties */
|
|
14
|
+
class ErrorWithProps extends Error implements PlayerErrorMetadata {
|
|
15
|
+
constructor(
|
|
16
|
+
message: string,
|
|
17
|
+
public type: string,
|
|
18
|
+
public severity?: ErrorSeverity,
|
|
19
|
+
public metadata?: ErrorMetadata,
|
|
20
|
+
) {
|
|
21
|
+
super(message);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe("ErrorController", () => {
|
|
26
|
+
let errorController: ErrorController;
|
|
27
|
+
let mockDataController: DataController;
|
|
28
|
+
let mockFlowController: FlowController;
|
|
29
|
+
let mockLogger: Logger;
|
|
30
|
+
let mockFail: ReturnType<typeof vitest.fn>;
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
mockDataController = {
|
|
34
|
+
set: vitest.fn(),
|
|
35
|
+
get: vitest.fn(),
|
|
36
|
+
delete: vitest.fn(),
|
|
37
|
+
} as any;
|
|
38
|
+
|
|
39
|
+
mockFlowController = {
|
|
40
|
+
current: undefined,
|
|
41
|
+
} as any;
|
|
42
|
+
|
|
43
|
+
mockLogger = {
|
|
44
|
+
trace: vitest.fn(),
|
|
45
|
+
debug: vitest.fn(),
|
|
46
|
+
info: vitest.fn(),
|
|
47
|
+
warn: vitest.fn(),
|
|
48
|
+
error: vitest.fn(),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
mockFail = vitest.fn();
|
|
52
|
+
|
|
53
|
+
errorController = new ErrorController({
|
|
54
|
+
logger: mockLogger,
|
|
55
|
+
flow: mockFlowController,
|
|
56
|
+
fail: mockFail,
|
|
57
|
+
model: mockDataController,
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("captureError", () => {
|
|
62
|
+
it("should capture error with metadata", () => {
|
|
63
|
+
const error = new ErrorWithProps(
|
|
64
|
+
"Test error",
|
|
65
|
+
ErrorTypes.EXPRESSION,
|
|
66
|
+
ErrorSeverity.ERROR,
|
|
67
|
+
{ state: "VIEW_Test" },
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const result = errorController.captureError(error);
|
|
71
|
+
|
|
72
|
+
expect(result).toBe(false);
|
|
73
|
+
const playerError = errorController.getCurrentError();
|
|
74
|
+
expect(playerError).toBe(error);
|
|
75
|
+
expect(playerError?.type).toBe(ErrorTypes.EXPRESSION);
|
|
76
|
+
expect(playerError?.severity).toBe(ErrorSeverity.ERROR);
|
|
77
|
+
expect(playerError?.metadata?.state).toBe("VIEW_Test");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("should add error to history", () => {
|
|
81
|
+
const error1 = new ErrorWithProps("Error 1", "test-error-1");
|
|
82
|
+
const error2 = new ErrorWithProps("Error 2", "test-error-2");
|
|
83
|
+
|
|
84
|
+
errorController.captureError(error1);
|
|
85
|
+
errorController.captureError(error2);
|
|
86
|
+
|
|
87
|
+
const history = errorController.getErrors();
|
|
88
|
+
expect(history).toHaveLength(2);
|
|
89
|
+
expect(history[0]).toBe(error1);
|
|
90
|
+
expect(history[1]).toBe(error2);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("should set as current error", () => {
|
|
94
|
+
const error = new ErrorWithProps("Test error", "test-error");
|
|
95
|
+
errorController.captureError(error);
|
|
96
|
+
|
|
97
|
+
const currentError = errorController.getCurrentError();
|
|
98
|
+
expect(currentError).toBe(error);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should write to data model", () => {
|
|
102
|
+
const error = new ErrorWithProps("Test error", ErrorTypes.EXPRESSION);
|
|
103
|
+
errorController.captureError(error);
|
|
104
|
+
|
|
105
|
+
expect(mockDataController.set).toHaveBeenCalledWith(
|
|
106
|
+
[
|
|
107
|
+
[
|
|
108
|
+
"errorState",
|
|
109
|
+
expect.objectContaining({
|
|
110
|
+
message: "Test error",
|
|
111
|
+
name: "Error",
|
|
112
|
+
errorType: ErrorTypes.EXPRESSION,
|
|
113
|
+
}),
|
|
114
|
+
],
|
|
115
|
+
],
|
|
116
|
+
expect.objectContaining({
|
|
117
|
+
writeSymbol: expect.any(Symbol),
|
|
118
|
+
}),
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("getCurrentError", () => {
|
|
124
|
+
it("should return undefined when no errors", () => {
|
|
125
|
+
expect(errorController.getCurrentError()).toBeUndefined();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("should return current error", () => {
|
|
129
|
+
const error = new ErrorWithProps("Test error", "test-error");
|
|
130
|
+
errorController.captureError(error);
|
|
131
|
+
|
|
132
|
+
expect(errorController.getCurrentError()).toBe(error);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe("getErrors", () => {
|
|
137
|
+
it("should return empty array when no errors", () => {
|
|
138
|
+
expect(errorController.getErrors()).toEqual([]);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("should return error history", () => {
|
|
142
|
+
const error1 = new ErrorWithProps("Error 1", "error-1");
|
|
143
|
+
const error2 = new ErrorWithProps("Error 2", "error-2");
|
|
144
|
+
|
|
145
|
+
errorController.captureError(error1);
|
|
146
|
+
errorController.captureError(error2);
|
|
147
|
+
|
|
148
|
+
const errors = errorController.getErrors();
|
|
149
|
+
expect(errors).toHaveLength(2);
|
|
150
|
+
expect(errors[0]).toBe(error1);
|
|
151
|
+
expect(errors[1]).toBe(error2);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe("clearErrors", () => {
|
|
156
|
+
it("should clear all errors and history", () => {
|
|
157
|
+
errorController.captureError(new ErrorWithProps("Error 1", "error-1"));
|
|
158
|
+
errorController.captureError(new ErrorWithProps("Error 2", "error-2"));
|
|
159
|
+
|
|
160
|
+
errorController.clearErrors();
|
|
161
|
+
|
|
162
|
+
expect(errorController.getCurrentError()).toBeUndefined();
|
|
163
|
+
expect(errorController.getErrors()).toEqual([]);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("should delete errorState from data model", () => {
|
|
167
|
+
errorController.captureError(
|
|
168
|
+
new ErrorWithProps("Test error", "test-error"),
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
// Reset mock to track only clearErrors call
|
|
172
|
+
vitest.mocked(mockDataController.set).mockClear();
|
|
173
|
+
|
|
174
|
+
errorController.clearErrors();
|
|
175
|
+
|
|
176
|
+
expect(mockDataController.delete).toHaveBeenCalledWith(
|
|
177
|
+
"errorState",
|
|
178
|
+
expect.objectContaining({
|
|
179
|
+
writeSymbol: expect.any(Symbol),
|
|
180
|
+
}),
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("should handle missing data controller gracefully", () => {
|
|
185
|
+
const controller = new ErrorController({
|
|
186
|
+
logger: mockLogger,
|
|
187
|
+
flow: mockFlowController,
|
|
188
|
+
fail: mockFail,
|
|
189
|
+
});
|
|
190
|
+
controller.captureError(new ErrorWithProps("Test error", "test-error"));
|
|
191
|
+
|
|
192
|
+
expect(() => controller.clearErrors()).not.toThrow();
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe("clearCurrentError", () => {
|
|
197
|
+
it("should clear current error but preserve history", () => {
|
|
198
|
+
const error1 = new ErrorWithProps("Error 1", "error-1");
|
|
199
|
+
const error2 = new ErrorWithProps("Error 2", "error-2");
|
|
200
|
+
|
|
201
|
+
errorController.captureError(error1);
|
|
202
|
+
errorController.captureError(error2);
|
|
203
|
+
|
|
204
|
+
errorController.clearCurrentError();
|
|
205
|
+
|
|
206
|
+
expect(errorController.getCurrentError()).toBeUndefined();
|
|
207
|
+
expect(errorController.getErrors()).toHaveLength(2);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("should delete errorState from data model", () => {
|
|
211
|
+
errorController.captureError(
|
|
212
|
+
new ErrorWithProps("Test error", "test-error"),
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
// Reset mock to track only clearCurrentError call
|
|
216
|
+
vitest.mocked(mockDataController.set).mockClear();
|
|
217
|
+
|
|
218
|
+
errorController.clearCurrentError();
|
|
219
|
+
|
|
220
|
+
expect(mockDataController.delete).toHaveBeenCalledWith(
|
|
221
|
+
"errorState",
|
|
222
|
+
expect.objectContaining({
|
|
223
|
+
writeSymbol: expect.any(Symbol),
|
|
224
|
+
}),
|
|
225
|
+
);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("should handle missing data controller gracefully", () => {
|
|
229
|
+
const controller = new ErrorController({
|
|
230
|
+
logger: mockLogger,
|
|
231
|
+
flow: mockFlowController,
|
|
232
|
+
fail: mockFail,
|
|
233
|
+
});
|
|
234
|
+
controller.captureError(new ErrorWithProps("Test error", "test-error"));
|
|
235
|
+
|
|
236
|
+
expect(() => controller.clearCurrentError()).not.toThrow();
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe("onError hook", () => {
|
|
241
|
+
it("should allow plugins to observe errors", () => {
|
|
242
|
+
const onErrorSpy = vitest.fn();
|
|
243
|
+
errorController.hooks.onError.tap("test", onErrorSpy);
|
|
244
|
+
|
|
245
|
+
const error = new ErrorWithProps("Test error", "test-error");
|
|
246
|
+
errorController.captureError(error);
|
|
247
|
+
|
|
248
|
+
expect(onErrorSpy).toHaveBeenCalledTimes(1);
|
|
249
|
+
expect(onErrorSpy).toHaveBeenCalledWith(error);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("should allow plugins to bail by returning true, stopping execution and preventing data model update", () => {
|
|
253
|
+
const observer1 = vitest.fn(() => undefined);
|
|
254
|
+
const skipPlugin = vitest.fn(() => true);
|
|
255
|
+
const observer2 = vitest.fn(() => undefined);
|
|
256
|
+
|
|
257
|
+
errorController.hooks.onError.tap("observer1", observer1);
|
|
258
|
+
errorController.hooks.onError.tap("skip-plugin", skipPlugin);
|
|
259
|
+
errorController.hooks.onError.tap("observer2", observer2);
|
|
260
|
+
|
|
261
|
+
const error = new ErrorWithProps("Test error", "test-error");
|
|
262
|
+
const result = errorController.captureError(error);
|
|
263
|
+
|
|
264
|
+
expect(observer1).toHaveBeenCalledTimes(1);
|
|
265
|
+
expect(skipPlugin).toHaveBeenCalledWith(error);
|
|
266
|
+
expect(observer2).not.toHaveBeenCalled(); // Execution stops after bail
|
|
267
|
+
// Data model should not be updated when skipped
|
|
268
|
+
expect(mockDataController.set).not.toHaveBeenCalled();
|
|
269
|
+
expect(result).toBe(true);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("should continue to next plugin when undefined is returned", () => {
|
|
273
|
+
const observer1 = vitest.fn(() => undefined);
|
|
274
|
+
const observer2 = vitest.fn(() => undefined);
|
|
275
|
+
errorController.hooks.onError.tap("observer1", observer1);
|
|
276
|
+
errorController.hooks.onError.tap("observer2", observer2);
|
|
277
|
+
|
|
278
|
+
const error = new ErrorWithProps("Test error", "test-error");
|
|
279
|
+
errorController.captureError(error);
|
|
280
|
+
|
|
281
|
+
expect(observer1).toHaveBeenCalledTimes(1);
|
|
282
|
+
expect(observer2).toHaveBeenCalledTimes(1);
|
|
283
|
+
// Data model should be updated when not skipped
|
|
284
|
+
expect(mockDataController.set).toHaveBeenCalled();
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
describe("custom error types", () => {
|
|
289
|
+
it("should allow custom plugin error types", () => {
|
|
290
|
+
const error = new ErrorWithProps(
|
|
291
|
+
"Custom plugin error",
|
|
292
|
+
"my-custom-plugin",
|
|
293
|
+
ErrorSeverity.WARNING,
|
|
294
|
+
);
|
|
295
|
+
const result = errorController.captureError(error);
|
|
296
|
+
|
|
297
|
+
expect(result).toBe(false);
|
|
298
|
+
const playerError = errorController.getCurrentError();
|
|
299
|
+
expect(playerError?.type).toBe("my-custom-plugin");
|
|
300
|
+
expect(playerError?.severity).toBe(ErrorSeverity.WARNING);
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
describe("integration with middleware", () => {
|
|
305
|
+
it("should use middleware to protect errorState from external deletes", () => {
|
|
306
|
+
const middleware = errorController.getDataMiddleware();
|
|
307
|
+
|
|
308
|
+
// Middleware should block deletes by default
|
|
309
|
+
expect(middleware.name).toBe("error-state-middleware");
|
|
310
|
+
|
|
311
|
+
// Capture an error (sets errorState)
|
|
312
|
+
errorController.captureError(
|
|
313
|
+
new ErrorWithProps("Test error", ErrorTypes.VIEW),
|
|
314
|
+
);
|
|
315
|
+
expect(mockDataController.set).toHaveBeenCalled();
|
|
316
|
+
|
|
317
|
+
// Clear error should delete via middleware with writeSymbol
|
|
318
|
+
vitest.mocked(mockDataController.set).mockClear();
|
|
319
|
+
errorController.clearCurrentError();
|
|
320
|
+
expect(mockDataController.delete).toHaveBeenCalledWith(
|
|
321
|
+
"errorState",
|
|
322
|
+
expect.objectContaining({
|
|
323
|
+
writeSymbol: expect.any(Symbol),
|
|
324
|
+
}),
|
|
325
|
+
);
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
describe("Fallback - Error without PlayerErrorMetadata interface", () => {
|
|
330
|
+
it("should not write errors that do not implement PlayerErrorMetadata", () => {
|
|
331
|
+
const middleware = errorController.getDataMiddleware();
|
|
332
|
+
|
|
333
|
+
// Middleware should block deletes by default
|
|
334
|
+
expect(middleware.name).toBe("error-state-middleware");
|
|
335
|
+
|
|
336
|
+
// Capture an error without metadata - no error in data controller
|
|
337
|
+
errorController.captureError(new Error("Test error"));
|
|
338
|
+
expect(mockDataController.set).not.toHaveBeenCalled();
|
|
339
|
+
|
|
340
|
+
// Clear error shouldn't about current state. Should always try to clear.
|
|
341
|
+
vitest.mocked(mockDataController.set).mockClear();
|
|
342
|
+
errorController.clearCurrentError();
|
|
343
|
+
expect(mockDataController.delete).toHaveBeenCalledWith(
|
|
344
|
+
"errorState",
|
|
345
|
+
expect.objectContaining({
|
|
346
|
+
writeSymbol: expect.any(Symbol),
|
|
347
|
+
}),
|
|
348
|
+
);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it("should not call the onError hook when the error is unrecognized.", () => {
|
|
352
|
+
const onErrorSpy = vitest.fn();
|
|
353
|
+
errorController.hooks.onError.tap("test", onErrorSpy);
|
|
354
|
+
errorController.captureError(new Error("Test error"));
|
|
355
|
+
|
|
356
|
+
expect(onErrorSpy).not.toHaveBeenCalled();
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
});
|