@openmrs/esm-error-handling 10.0.1-pre.5542 → 10.0.1-pre.5561

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.
@@ -1 +1 @@
1
- Successfully compiled: 1 file with swc (159.52ms)
1
+ Successfully compiled: 1 file with swc (165.69ms)
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAsBA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,QAKvC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,KAExB,aAAa,OAAO,UAK7B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA8CA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,QAKvC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,KAExB,aAAa,OAAO,UAK7B"}
package/dist/index.js CHANGED
@@ -1,8 +1,11 @@
1
1
  /** @module @category Error Handling */ import { dispatchToastShown } from "@openmrs/esm-globals";
2
- window.onerror = function(error) {
3
- console.error('Unexpected error: ', error);
2
+ // Uses the standard `window.onerror` signature: the first argument is the error
3
+ // message (a string) or an Event, and the fifth is the actual Error object (when
4
+ // the browser provides it). We prefer the Error so the toast shows its message.
5
+ window.onerror = function(message, _source, _lineno, _colno, error) {
6
+ console.error('Unexpected error: ', error ?? message);
4
7
  dispatchToastShown({
5
- description: error ?? 'Oops! An unexpected error occurred.',
8
+ description: getErrorMessage(error ?? message, 'Oops! An unexpected error occurred.'),
6
9
  kind: 'error',
7
10
  title: 'Error'
8
11
  });
@@ -11,11 +14,28 @@ window.onerror = function(error) {
11
14
  window.onunhandledrejection = function(event) {
12
15
  console.error('Unhandled rejection: ', event.reason);
13
16
  dispatchToastShown({
14
- description: event.reason ?? 'Oops! An unhandled promise rejection occurred.',
17
+ // `event.reason` is whatever the promise rejected with, frequently an Error
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.'),
15
22
  kind: 'error',
16
23
  title: 'Error'
17
24
  });
18
25
  };
26
+ /**
27
+ * Resolves an arbitrary thrown/rejected value to a user-facing, non-empty
28
+ * message string suitable for use as a toast description, falling back to the
29
+ * provided message when no usable message can be derived.
30
+ */ function getErrorMessage(reason, fallback) {
31
+ if (reason instanceof Error) {
32
+ return reason.message || fallback;
33
+ }
34
+ if (typeof reason === 'string' && reason.trim().length > 0) {
35
+ return reason;
36
+ }
37
+ return fallback;
38
+ }
19
39
  /**
20
40
  * Reports an error to the global error handler. The error will be displayed
21
41
  * to the user as a toast notification. This function ensures the error is
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmrs/esm-error-handling",
3
- "version": "10.0.1-pre.5542",
3
+ "version": "10.0.1-pre.5561",
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.5542"
49
+ "@openmrs/esm-globals": "^10.0.1-pre.5561"
50
50
  },
51
51
  "devDependencies": {
52
- "@openmrs/esm-globals": "10.0.1-pre.5542",
52
+ "@openmrs/esm-globals": "10.0.1-pre.5561",
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
@@ -1,10 +1,13 @@
1
1
  /** @module @category Error Handling */
2
2
  import { dispatchToastShown } from '@openmrs/esm-globals';
3
3
 
4
- window.onerror = function (error) {
5
- console.error('Unexpected error: ', error);
4
+ // Uses the standard `window.onerror` signature: the first argument is the error
5
+ // message (a string) or an Event, and the fifth is the actual Error object (when
6
+ // the browser provides it). We prefer the Error so the toast shows its message.
7
+ window.onerror = function (message, _source, _lineno, _colno, error) {
8
+ console.error('Unexpected error: ', error ?? message);
6
9
  dispatchToastShown({
7
- description: error ?? 'Oops! An unexpected error occurred.',
10
+ description: getErrorMessage(error ?? message, 'Oops! An unexpected error occurred.'),
8
11
  kind: 'error',
9
12
  title: 'Error',
10
13
  });
@@ -14,12 +17,33 @@ window.onerror = function (error) {
14
17
  window.onunhandledrejection = function (event: PromiseRejectionEvent) {
15
18
  console.error('Unhandled rejection: ', event.reason);
16
19
  dispatchToastShown({
17
- description: event.reason ?? 'Oops! An unhandled promise rejection occurred.',
20
+ // `event.reason` is whatever the promise rejected with, frequently an Error
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.'),
18
25
  kind: 'error',
19
26
  title: 'Error',
20
27
  });
21
28
  };
22
29
 
30
+ /**
31
+ * Resolves an arbitrary thrown/rejected value to a user-facing, non-empty
32
+ * message string suitable for use as a toast description, falling back to the
33
+ * provided message when no usable message can be derived.
34
+ */
35
+ function getErrorMessage(reason: unknown, fallback: string): string {
36
+ if (reason instanceof Error) {
37
+ return reason.message || fallback;
38
+ }
39
+
40
+ if (typeof reason === 'string' && reason.trim().length > 0) {
41
+ return reason;
42
+ }
43
+
44
+ return fallback;
45
+ }
46
+
23
47
  /**
24
48
  * Reports an error to the global error handler. The error will be displayed
25
49
  * to the user as a toast notification. This function ensures the error is
@@ -1,8 +1,15 @@
1
- import { describe, it, expect, vi } from 'vitest';
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { dispatchToastShown } from '@openmrs/esm-globals';
2
3
  import { reportError } from './index';
3
4
 
5
+ vi.mock('@openmrs/esm-globals', () => ({
6
+ dispatchToastShown: vi.fn(),
7
+ }));
8
+
4
9
  vi.useFakeTimers();
5
10
 
11
+ const mockDispatchToastShown = vi.mocked(dispatchToastShown);
12
+
6
13
  describe('error handler', () => {
7
14
  it('transforms non-Error inputs into valid Error objects', () => {
8
15
  expect(() => {
@@ -26,3 +33,41 @@ describe('error handler', () => {
26
33
  }).toThrow("'undefined' was thrown as an error");
27
34
  });
28
35
  });
36
+
37
+ describe('window.onunhandledrejection', () => {
38
+ beforeEach(() => {
39
+ mockDispatchToastShown.mockClear();
40
+ vi.spyOn(console, 'error').mockImplementation(() => {});
41
+ });
42
+
43
+ // The handler only reads `event.reason`, so a minimal stand-in avoids
44
+ // constructing a real (and itself-unhandled) PromiseRejectionEvent.
45
+ const fireRejection = (reason: unknown) => window.onunhandledrejection?.({ reason } as PromiseRejectionEvent);
46
+
47
+ it('shows a toast with the Error message as a string description', () => {
48
+ fireRejection(new Error('Something broke'));
49
+
50
+ expect(mockDispatchToastShown).toHaveBeenCalledWith(
51
+ expect.objectContaining({ description: 'Something broke', kind: 'error', title: 'Error' }),
52
+ );
53
+ expect(typeof mockDispatchToastShown.mock.calls[0][0].description).toBe('string');
54
+ });
55
+
56
+ it('shows a string reason directly', () => {
57
+ fireRejection('plain string reason');
58
+
59
+ expect(mockDispatchToastShown).toHaveBeenCalledWith(
60
+ expect.objectContaining({ description: 'plain string reason' }),
61
+ );
62
+ });
63
+
64
+ it('falls back to a friendly message for empty/non-string reasons', () => {
65
+ for (const reason of [undefined, null, new Error(''), { some: 'object' }]) {
66
+ mockDispatchToastShown.mockClear();
67
+ fireRejection(reason);
68
+ expect(mockDispatchToastShown.mock.calls[0][0].description).toBe(
69
+ 'Oops! An unhandled promise rejection occurred.',
70
+ );
71
+ }
72
+ });
73
+ });