@openmrs/esm-error-handling 10.0.1-pre.5664 → 10.0.1-pre.5673
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/.turbo/turbo-build.log +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -17
- package/package.json +3 -3
- package/src/index.ts +8 -19
- package/src/openmrs-esm-error-handling.test.ts +54 -29
package/.turbo/turbo-build.log
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Successfully compiled: 1 file with swc (
|
|
1
|
+
Successfully compiled: 1 file with swc (174.56ms)
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAmCA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,QAKvC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,KAExB,aAAa,OAAO,UAK7B"}
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
window.onerror = function(message, _source, _lineno, _colno, error) {
|
|
6
6
|
console.error('Unexpected error: ', error ?? message);
|
|
7
7
|
dispatchToastShown({
|
|
8
|
-
description:
|
|
8
|
+
description: toToastDescription(error, 'Oops! An unexpected error occurred.'),
|
|
9
9
|
kind: 'error',
|
|
10
10
|
title: 'Error'
|
|
11
11
|
});
|
|
@@ -14,27 +14,18 @@ window.onerror = function(message, _source, _lineno, _colno, error) {
|
|
|
14
14
|
window.onunhandledrejection = function(event) {
|
|
15
15
|
console.error('Unhandled rejection: ', event.reason);
|
|
16
16
|
dispatchToastShown({
|
|
17
|
-
|
|
18
|
-
// object. `showToast` expects `description` to be a renderable, non-empty
|
|
19
|
-
// value, so resolve it to a string message rather than passing the raw
|
|
20
|
-
// rejection (which renders as "[object Object]" and fails the non-empty check).
|
|
21
|
-
description: getErrorMessage(event.reason, 'Oops! An unhandled promise rejection occurred.'),
|
|
17
|
+
description: toToastDescription(event.reason, 'Oops! An unhandled promise rejection occurred.'),
|
|
22
18
|
kind: 'error',
|
|
23
19
|
title: 'Error'
|
|
24
20
|
});
|
|
25
21
|
};
|
|
26
22
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*/ function
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
if (typeof reason === 'string' && reason.trim().length > 0) {
|
|
35
|
-
return reason;
|
|
36
|
-
}
|
|
37
|
-
return fallback;
|
|
23
|
+
* Tries to turn whatever reached the global error handler into
|
|
24
|
+
* a sensible string, returning the caller's fallback if a non-empty
|
|
25
|
+
* string could not be constructed.
|
|
26
|
+
*/ function toToastDescription(thing, fallback) {
|
|
27
|
+
const { message } = ensureErrorObject(thing);
|
|
28
|
+
return typeof message === 'string' && message ? message : fallback;
|
|
38
29
|
}
|
|
39
30
|
/**
|
|
40
31
|
* Reports an error to the global error handler. The error will be displayed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openmrs/esm-error-handling",
|
|
3
|
-
"version": "10.0.1-pre.
|
|
3
|
+
"version": "10.0.1-pre.5673",
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
5
|
"description": "An ES module to help with error handling",
|
|
6
6
|
"type": "module",
|
|
@@ -46,10 +46,10 @@
|
|
|
46
46
|
"access": "public"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
|
-
"@openmrs/esm-globals": "^10.0.1-pre.
|
|
49
|
+
"@openmrs/esm-globals": "^10.0.1-pre.5673"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@openmrs/esm-globals": "10.0.1-pre.
|
|
52
|
+
"@openmrs/esm-globals": "10.0.1-pre.5673",
|
|
53
53
|
"@swc/cli": "0.8.1",
|
|
54
54
|
"@swc/core": "1.15.21",
|
|
55
55
|
"@vitest/coverage-v8": "^4.1.2",
|
package/src/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { dispatchToastShown } from '@openmrs/esm-globals';
|
|
|
7
7
|
window.onerror = function (message, _source, _lineno, _colno, error) {
|
|
8
8
|
console.error('Unexpected error: ', error ?? message);
|
|
9
9
|
dispatchToastShown({
|
|
10
|
-
description:
|
|
10
|
+
description: toToastDescription(error, 'Oops! An unexpected error occurred.'),
|
|
11
11
|
kind: 'error',
|
|
12
12
|
title: 'Error',
|
|
13
13
|
});
|
|
@@ -17,31 +17,20 @@ window.onerror = function (message, _source, _lineno, _colno, error) {
|
|
|
17
17
|
window.onunhandledrejection = function (event: PromiseRejectionEvent) {
|
|
18
18
|
console.error('Unhandled rejection: ', event.reason);
|
|
19
19
|
dispatchToastShown({
|
|
20
|
-
|
|
21
|
-
// object. `showToast` expects `description` to be a renderable, non-empty
|
|
22
|
-
// value, so resolve it to a string message rather than passing the raw
|
|
23
|
-
// rejection (which renders as "[object Object]" and fails the non-empty check).
|
|
24
|
-
description: getErrorMessage(event.reason, 'Oops! An unhandled promise rejection occurred.'),
|
|
20
|
+
description: toToastDescription(event.reason, 'Oops! An unhandled promise rejection occurred.'),
|
|
25
21
|
kind: 'error',
|
|
26
22
|
title: 'Error',
|
|
27
23
|
});
|
|
28
24
|
};
|
|
29
25
|
|
|
30
26
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
27
|
+
* Tries to turn whatever reached the global error handler into
|
|
28
|
+
* a sensible string, returning the caller's fallback if a non-empty
|
|
29
|
+
* string could not be constructed.
|
|
34
30
|
*/
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (typeof reason === 'string' && reason.trim().length > 0) {
|
|
41
|
-
return reason;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return fallback;
|
|
31
|
+
function toToastDescription(thing: unknown, fallback: string) {
|
|
32
|
+
const { message } = ensureErrorObject(thing);
|
|
33
|
+
return typeof message === 'string' && message ? message : fallback;
|
|
45
34
|
}
|
|
46
35
|
|
|
47
36
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, it, expect, vi
|
|
1
|
+
import { beforeEach, describe, it, expect, vi } from 'vitest';
|
|
2
2
|
import { dispatchToastShown } from '@openmrs/esm-globals';
|
|
3
3
|
import { reportError } from './index';
|
|
4
4
|
|
|
@@ -10,6 +10,33 @@ vi.useFakeTimers();
|
|
|
10
10
|
|
|
11
11
|
const mockDispatchToastShown = vi.mocked(dispatchToastShown);
|
|
12
12
|
|
|
13
|
+
function errorWithObjectMessage() {
|
|
14
|
+
const error = new Error('replaced below');
|
|
15
|
+
(error as unknown as { message: unknown }).message = { nested: 'value' };
|
|
16
|
+
return error;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The shapes a thrown value can take when it reaches the global handlers, as the `error`
|
|
25
|
+
* argument of `window.onerror` or as `event.reason` in `window.onunhandledrejection`.
|
|
26
|
+
* `null` in `expected` means the handler's fallback text is expected.
|
|
27
|
+
*/
|
|
28
|
+
const errorShapes: Array<{ label: string; reason: unknown; expected: string | null }> = [
|
|
29
|
+
{ label: 'an Error instance', reason: new Error('something exploded'), expected: 'something exploded' },
|
|
30
|
+
{ label: 'an Error with an empty message', reason: new Error(''), expected: null },
|
|
31
|
+
{ label: 'a string', reason: 'something failed', expected: 'something failed' },
|
|
32
|
+
{ label: 'a plain object', reason: { foo: 'bar' }, expected: 'Object thrown as error: {"foo":"bar"}' },
|
|
33
|
+
{ label: 'null', reason: null, expected: "'null' was thrown as an error" },
|
|
34
|
+
{ label: 'undefined', reason: undefined, expected: "'undefined' was thrown as an error" },
|
|
35
|
+
// `Error#message` is writable, so an Error can reach the handlers carrying a non-string message.
|
|
36
|
+
// `ensureErrorObject()` passes such an Error through untouched, so the guard has to be downstream.
|
|
37
|
+
{ label: 'an Error whose message is not a string', reason: errorWithObjectMessage(), expected: null },
|
|
38
|
+
];
|
|
39
|
+
|
|
13
40
|
describe('error handler', () => {
|
|
14
41
|
it('transforms non-Error inputs into valid Error objects', () => {
|
|
15
42
|
expect(() => {
|
|
@@ -34,40 +61,38 @@ describe('error handler', () => {
|
|
|
34
61
|
});
|
|
35
62
|
});
|
|
36
63
|
|
|
37
|
-
describe('
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
64
|
+
describe('Global error handler', () => {
|
|
65
|
+
// Browsers call `window.onerror` with the message first and the thrown value fifth.
|
|
66
|
+
function triggerOnError(error: unknown) {
|
|
67
|
+
window.onerror?.('Uncaught error', undefined, undefined, undefined, error as Error);
|
|
68
|
+
}
|
|
42
69
|
|
|
43
|
-
|
|
44
|
-
// constructing a real (and itself-unhandled) PromiseRejectionEvent.
|
|
45
|
-
const fireRejection = (reason: unknown) => window.onunhandledrejection?.({ reason } as PromiseRejectionEvent);
|
|
70
|
+
const fallback = 'Oops! An unexpected error occurred.';
|
|
46
71
|
|
|
47
|
-
it('shows a
|
|
48
|
-
|
|
72
|
+
it.each(errorShapes)('shows a string description for $label', ({ reason, expected }) => {
|
|
73
|
+
triggerOnError(reason);
|
|
49
74
|
|
|
50
|
-
expect(mockDispatchToastShown).
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
expect(typeof
|
|
75
|
+
expect(mockDispatchToastShown).toHaveBeenCalledOnce();
|
|
76
|
+
const { description } = mockDispatchToastShown.mock.calls[0][0];
|
|
77
|
+
// The regression itself: an object here is unrenderable as a React child, so the toast never appears.
|
|
78
|
+
expect(typeof description).toBe('string');
|
|
79
|
+
expect(description).toBe(expected ?? fallback);
|
|
54
80
|
});
|
|
81
|
+
});
|
|
55
82
|
|
|
56
|
-
|
|
57
|
-
|
|
83
|
+
describe('Global rejection handler', () => {
|
|
84
|
+
function triggerOnUnhandledRejection(reason: unknown) {
|
|
85
|
+
(window.onunhandledrejection as (event: PromiseRejectionEvent) => void)({ reason } as PromiseRejectionEvent);
|
|
86
|
+
}
|
|
58
87
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
88
|
+
const fallback = 'Oops! An unhandled promise rejection occurred.';
|
|
89
|
+
|
|
90
|
+
it.each(errorShapes)('shows a string description for $label', ({ reason, expected }) => {
|
|
91
|
+
triggerOnUnhandledRejection(reason);
|
|
63
92
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
expect(mockDispatchToastShown.mock.calls[0][0].description).toBe(
|
|
69
|
-
'Oops! An unhandled promise rejection occurred.',
|
|
70
|
-
);
|
|
71
|
-
}
|
|
93
|
+
expect(mockDispatchToastShown).toHaveBeenCalledOnce();
|
|
94
|
+
const { description } = mockDispatchToastShown.mock.calls[0][0];
|
|
95
|
+
expect(typeof description).toBe('string');
|
|
96
|
+
expect(description).toBe(expected ?? fallback);
|
|
72
97
|
});
|
|
73
98
|
});
|