@openmrs/esm-styleguide 10.0.1-pre.4943 → 10.0.1-pre.4966
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/workspaces2/active-workspace-window.component.d.ts.map +1 -1
- package/dist/workspaces2/active-workspace-window.component.js +23 -5
- package/package.json +25 -25
- package/src/workspaces2/active-workspace-window.component.tsx +20 -5
- package/src/workspaces2/active-workspace-window.test.tsx +99 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
[0] Successfully compiled: 123 files, copied 164 files with swc (
|
|
1
|
+
[0] Successfully compiled: 123 files, copied 164 files with swc (211.09ms)
|
|
2
2
|
[0] swc --strip-leading-paths --copy-files src -d dist && svgo -rf dist --quiet exited with code 0
|
|
3
3
|
[1] tsc --project tsconfig.build.json exited with code 0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"active-workspace-window.component.d.ts","sourceRoot":"","sources":["../../src/workspaces2/active-workspace-window.component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAK5D,OAAO,EAAE,KAAK,YAAY,EAAyC,MAAM,yBAAyB,CAAC;AAOnG,UAAU,oBAAoB;IAC5B,YAAY,EAAE,YAAY,CAAC;IAC3B,cAAc,EAAE,OAAO,CAAC;CACzB;AACD;;GAEG;AACH,QAAA,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,
|
|
1
|
+
{"version":3,"file":"active-workspace-window.component.d.ts","sourceRoot":"","sources":["../../src/workspaces2/active-workspace-window.component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAK5D,OAAO,EAAE,KAAK,YAAY,EAAyC,MAAM,yBAAyB,CAAC;AAOnG,UAAU,oBAAoB;IAC5B,YAAY,EAAE,YAAY,CAAC;IAC3B,cAAc,EAAE,OAAO,CAAC;CACzB;AACD;;GAEG;AACH,QAAA,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA4CzD,CAAC;AAyIF,eAAe,qBAAqB,CAAC"}
|
|
@@ -13,13 +13,31 @@ import styles from "./workspace2.module.scss";
|
|
|
13
13
|
* Renders an opened workspace window.
|
|
14
14
|
*/ const ActiveWorkspaceWindow = ({ openedWindow, showActionMenu })=>{
|
|
15
15
|
const { openedWorkspaces } = openedWindow;
|
|
16
|
-
|
|
16
|
+
// Keyed by opened workspace uuid rather than stack index. The lifecycles load
|
|
17
|
+
// asynchronously, so when the stack changes (a workspace at some position is
|
|
18
|
+
// replaced by another, for example closing a child workspace and immediately
|
|
19
|
+
// opening a different one), an index-based lookup can hand the new workspace
|
|
20
|
+
// the previous workspace's component until the reload resolves. The parcel
|
|
21
|
+
// mounts whatever config it first receives, so the wrong component would then
|
|
22
|
+
// render with the new workspace's props.
|
|
23
|
+
const [lifeCyclesByUuid, setLifeCyclesByUuid] = useState({});
|
|
17
24
|
const { registeredWorkspacesByName } = workspace2Store.getState();
|
|
18
25
|
useEffect(()=>{
|
|
19
|
-
|
|
26
|
+
let cancelled = false;
|
|
27
|
+
Promise.all(openedWorkspaces.map(async (openedWorkspace)=>{
|
|
20
28
|
const { moduleName, component } = registeredWorkspacesByName[openedWorkspace.workspaceName];
|
|
21
|
-
return
|
|
22
|
-
|
|
29
|
+
return [
|
|
30
|
+
openedWorkspace.uuid,
|
|
31
|
+
await loadLifeCycles(moduleName, component)
|
|
32
|
+
];
|
|
33
|
+
})).then((entries)=>{
|
|
34
|
+
if (!cancelled) {
|
|
35
|
+
setLifeCyclesByUuid(Object.fromEntries(entries));
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
return ()=>{
|
|
39
|
+
cancelled = true;
|
|
40
|
+
};
|
|
23
41
|
}, [
|
|
24
42
|
openedWorkspaces
|
|
25
43
|
]);
|
|
@@ -27,7 +45,7 @@ import styles from "./workspace2.module.scss";
|
|
|
27
45
|
children: openedWorkspaces.map((openedWorkspace, i)=>/*#__PURE__*/ _jsx(ActiveWorkspace, {
|
|
28
46
|
openedWorkspace: openedWorkspace,
|
|
29
47
|
openedWindow: openedWindow,
|
|
30
|
-
lifeCycle:
|
|
48
|
+
lifeCycle: lifeCyclesByUuid[openedWorkspace.uuid],
|
|
31
49
|
isRootWorkspace: i === 0,
|
|
32
50
|
isLeafWorkspace: i === openedWorkspaces.length - 1,
|
|
33
51
|
showActionMenu: showActionMenu
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openmrs/esm-styleguide",
|
|
3
|
-
"version": "10.0.1-pre.
|
|
3
|
+
"version": "10.0.1-pre.4966",
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
5
|
"description": "The styleguide for OpenMRS SPA",
|
|
6
6
|
"module": "dist/internal.js",
|
|
@@ -77,18 +77,18 @@
|
|
|
77
77
|
"react-aria-components": "^1.7.1"
|
|
78
78
|
},
|
|
79
79
|
"peerDependencies": {
|
|
80
|
-
"@openmrs/esm-api": "^10.0.1-pre.
|
|
81
|
-
"@openmrs/esm-config": "^10.0.1-pre.
|
|
82
|
-
"@openmrs/esm-emr-api": "^10.0.1-pre.
|
|
83
|
-
"@openmrs/esm-error-handling": "^10.0.1-pre.
|
|
84
|
-
"@openmrs/esm-extensions": "^10.0.1-pre.
|
|
85
|
-
"@openmrs/esm-globals": "^10.0.1-pre.
|
|
86
|
-
"@openmrs/esm-navigation": "^10.0.1-pre.
|
|
87
|
-
"@openmrs/esm-react-utils": "^10.0.1-pre.
|
|
88
|
-
"@openmrs/esm-routes": "^10.0.1-pre.
|
|
89
|
-
"@openmrs/esm-state": "^10.0.1-pre.
|
|
90
|
-
"@openmrs/esm-translations": "^10.0.1-pre.
|
|
91
|
-
"@openmrs/esm-utils": "^10.0.1-pre.
|
|
80
|
+
"@openmrs/esm-api": "^10.0.1-pre.4966",
|
|
81
|
+
"@openmrs/esm-config": "^10.0.1-pre.4966",
|
|
82
|
+
"@openmrs/esm-emr-api": "^10.0.1-pre.4966",
|
|
83
|
+
"@openmrs/esm-error-handling": "^10.0.1-pre.4966",
|
|
84
|
+
"@openmrs/esm-extensions": "^10.0.1-pre.4966",
|
|
85
|
+
"@openmrs/esm-globals": "^10.0.1-pre.4966",
|
|
86
|
+
"@openmrs/esm-navigation": "^10.0.1-pre.4966",
|
|
87
|
+
"@openmrs/esm-react-utils": "^10.0.1-pre.4966",
|
|
88
|
+
"@openmrs/esm-routes": "^10.0.1-pre.4966",
|
|
89
|
+
"@openmrs/esm-state": "^10.0.1-pre.4966",
|
|
90
|
+
"@openmrs/esm-translations": "^10.0.1-pre.4966",
|
|
91
|
+
"@openmrs/esm-utils": "^10.0.1-pre.4966",
|
|
92
92
|
"dayjs": "1.x",
|
|
93
93
|
"i18next": "25.x",
|
|
94
94
|
"react": "18.x",
|
|
@@ -98,18 +98,18 @@
|
|
|
98
98
|
"swr": "2.x"
|
|
99
99
|
},
|
|
100
100
|
"devDependencies": {
|
|
101
|
-
"@openmrs/esm-api": "10.0.1-pre.
|
|
102
|
-
"@openmrs/esm-config": "10.0.1-pre.
|
|
103
|
-
"@openmrs/esm-emr-api": "10.0.1-pre.
|
|
104
|
-
"@openmrs/esm-error-handling": "10.0.1-pre.
|
|
105
|
-
"@openmrs/esm-extensions": "10.0.1-pre.
|
|
106
|
-
"@openmrs/esm-globals": "10.0.1-pre.
|
|
107
|
-
"@openmrs/esm-navigation": "10.0.1-pre.
|
|
108
|
-
"@openmrs/esm-react-utils": "10.0.1-pre.
|
|
109
|
-
"@openmrs/esm-routes": "10.0.1-pre.
|
|
110
|
-
"@openmrs/esm-state": "10.0.1-pre.
|
|
111
|
-
"@openmrs/esm-translations": "10.0.1-pre.
|
|
112
|
-
"@openmrs/esm-utils": "10.0.1-pre.
|
|
101
|
+
"@openmrs/esm-api": "10.0.1-pre.4966",
|
|
102
|
+
"@openmrs/esm-config": "10.0.1-pre.4966",
|
|
103
|
+
"@openmrs/esm-emr-api": "10.0.1-pre.4966",
|
|
104
|
+
"@openmrs/esm-error-handling": "10.0.1-pre.4966",
|
|
105
|
+
"@openmrs/esm-extensions": "10.0.1-pre.4966",
|
|
106
|
+
"@openmrs/esm-globals": "10.0.1-pre.4966",
|
|
107
|
+
"@openmrs/esm-navigation": "10.0.1-pre.4966",
|
|
108
|
+
"@openmrs/esm-react-utils": "10.0.1-pre.4966",
|
|
109
|
+
"@openmrs/esm-routes": "10.0.1-pre.4966",
|
|
110
|
+
"@openmrs/esm-state": "10.0.1-pre.4966",
|
|
111
|
+
"@openmrs/esm-translations": "10.0.1-pre.4966",
|
|
112
|
+
"@openmrs/esm-utils": "10.0.1-pre.4966",
|
|
113
113
|
"@swc/cli": "0.8.1",
|
|
114
114
|
"@swc/core": "1.15.21",
|
|
115
115
|
"@types/geopattern": "^1.2.9",
|
|
@@ -19,16 +19,31 @@ interface WorkspaceWindowProps {
|
|
|
19
19
|
*/
|
|
20
20
|
const ActiveWorkspaceWindow: React.FC<WorkspaceWindowProps> = ({ openedWindow, showActionMenu }) => {
|
|
21
21
|
const { openedWorkspaces } = openedWindow;
|
|
22
|
-
|
|
22
|
+
// Keyed by opened workspace uuid rather than stack index. The lifecycles load
|
|
23
|
+
// asynchronously, so when the stack changes (a workspace at some position is
|
|
24
|
+
// replaced by another, for example closing a child workspace and immediately
|
|
25
|
+
// opening a different one), an index-based lookup can hand the new workspace
|
|
26
|
+
// the previous workspace's component until the reload resolves. The parcel
|
|
27
|
+
// mounts whatever config it first receives, so the wrong component would then
|
|
28
|
+
// render with the new workspace's props.
|
|
29
|
+
const [lifeCyclesByUuid, setLifeCyclesByUuid] = useState<Record<string, ParcelConfig>>({});
|
|
23
30
|
const { registeredWorkspacesByName } = workspace2Store.getState();
|
|
24
31
|
|
|
25
32
|
useEffect(() => {
|
|
33
|
+
let cancelled = false;
|
|
26
34
|
Promise.all(
|
|
27
|
-
openedWorkspaces.map((openedWorkspace) => {
|
|
35
|
+
openedWorkspaces.map(async (openedWorkspace): Promise<[string, ParcelConfig]> => {
|
|
28
36
|
const { moduleName, component } = registeredWorkspacesByName[openedWorkspace.workspaceName];
|
|
29
|
-
return loadLifeCycles(moduleName, component);
|
|
37
|
+
return [openedWorkspace.uuid, await loadLifeCycles(moduleName, component)];
|
|
30
38
|
}),
|
|
31
|
-
).then(
|
|
39
|
+
).then((entries) => {
|
|
40
|
+
if (!cancelled) {
|
|
41
|
+
setLifeCyclesByUuid(Object.fromEntries(entries));
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
return () => {
|
|
45
|
+
cancelled = true;
|
|
46
|
+
};
|
|
32
47
|
}, [openedWorkspaces]);
|
|
33
48
|
|
|
34
49
|
return (
|
|
@@ -38,7 +53,7 @@ const ActiveWorkspaceWindow: React.FC<WorkspaceWindowProps> = ({ openedWindow, s
|
|
|
38
53
|
key={openedWorkspace.uuid}
|
|
39
54
|
openedWorkspace={openedWorkspace}
|
|
40
55
|
openedWindow={openedWindow}
|
|
41
|
-
lifeCycle={
|
|
56
|
+
lifeCycle={lifeCyclesByUuid[openedWorkspace.uuid]}
|
|
42
57
|
isRootWorkspace={i === 0}
|
|
43
58
|
isLeafWorkspace={i === openedWorkspaces.length - 1}
|
|
44
59
|
showActionMenu={showActionMenu}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { act, render, screen } from '@testing-library/react';
|
|
4
|
+
import { workspace2Store, type OpenedWindow, type OpenedWorkspace } from '@openmrs/esm-extensions';
|
|
5
|
+
import { loadLifeCycles } from '@openmrs/esm-routes';
|
|
6
|
+
import ActiveWorkspaceWindow from './active-workspace-window.component';
|
|
7
|
+
|
|
8
|
+
vi.mock('@openmrs/esm-routes', () => ({
|
|
9
|
+
loadLifeCycles: vi.fn(),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
vi.mock('single-spa-react/parcel', () => ({
|
|
13
|
+
default: ({ config }: { config: { name: string } }) => <div data-testid="parcel">{config.name}</div>,
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
const mockLoadLifeCycles = vi.mocked(loadLifeCycles);
|
|
17
|
+
|
|
18
|
+
function makeOpenedWorkspace(workspaceName: string, uuid: string): OpenedWorkspace {
|
|
19
|
+
return { workspaceName, uuid, props: {}, hasUnsavedChanges: false } as OpenedWorkspace;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function makeOpenedWindow(openedWorkspaces: Array<OpenedWorkspace>): OpenedWindow {
|
|
23
|
+
return {
|
|
24
|
+
windowName: 'test-window',
|
|
25
|
+
openedWorkspaces,
|
|
26
|
+
props: {},
|
|
27
|
+
maximized: false,
|
|
28
|
+
} as OpenedWindow;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('ActiveWorkspaceWindow', () => {
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
workspace2Store.setState({
|
|
34
|
+
registeredWorkspacesByName: {
|
|
35
|
+
'form-workspace': {
|
|
36
|
+
name: 'form-workspace',
|
|
37
|
+
component: 'form',
|
|
38
|
+
window: 'test-window',
|
|
39
|
+
moduleName: 'test-module',
|
|
40
|
+
},
|
|
41
|
+
'admit-workspace': {
|
|
42
|
+
name: 'admit-workspace',
|
|
43
|
+
component: 'admit',
|
|
44
|
+
window: 'test-window',
|
|
45
|
+
moduleName: 'test-module',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
registeredWindowsByName: {
|
|
49
|
+
'test-window': { name: 'test-window', group: 'test-group' },
|
|
50
|
+
},
|
|
51
|
+
workspaceTitleByWorkspaceName: {},
|
|
52
|
+
openedGroup: null,
|
|
53
|
+
} as never);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
afterEach(() => {
|
|
57
|
+
vi.clearAllMocks();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('does not render a replaced workspace with the previous workspace lifecycle at the same stack position', async () => {
|
|
61
|
+
// The form lifecycle resolves immediately. The admit lifecycle resolves only
|
|
62
|
+
// when we release it, keeping the reload from the stack change pending, which
|
|
63
|
+
// is the window where an index-based lookup served the stale component.
|
|
64
|
+
let resolveAdmit: (config: { name: string }) => void;
|
|
65
|
+
mockLoadLifeCycles.mockImplementation((moduleName, component) => {
|
|
66
|
+
if (component === 'form') {
|
|
67
|
+
return Promise.resolve({ name: 'form-lifecycle' } as never);
|
|
68
|
+
}
|
|
69
|
+
return new Promise((resolve) => {
|
|
70
|
+
resolveAdmit = resolve as never;
|
|
71
|
+
}) as never;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const formWorkspace = makeOpenedWorkspace('form-workspace', 'uuid-form');
|
|
75
|
+
const { rerender } = render(
|
|
76
|
+
<ActiveWorkspaceWindow openedWindow={makeOpenedWindow([formWorkspace])} showActionMenu={false} />,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
await act(async () => {
|
|
80
|
+
await Promise.resolve();
|
|
81
|
+
});
|
|
82
|
+
expect(screen.getByTestId('parcel')).toHaveTextContent('form-lifecycle');
|
|
83
|
+
|
|
84
|
+
// Replace the workspace at index 0, as happens when a workspace is closed and
|
|
85
|
+
// another is opened in quick succession.
|
|
86
|
+
const admitWorkspace = makeOpenedWorkspace('admit-workspace', 'uuid-admit');
|
|
87
|
+
rerender(<ActiveWorkspaceWindow openedWindow={makeOpenedWindow([admitWorkspace])} showActionMenu={false} />);
|
|
88
|
+
|
|
89
|
+
// While the admit lifecycle is still loading, the stale form lifecycle must not
|
|
90
|
+
// be mounted for the admit workspace.
|
|
91
|
+
expect(screen.queryByTestId('parcel')).not.toBeInTheDocument();
|
|
92
|
+
|
|
93
|
+
await act(async () => {
|
|
94
|
+
resolveAdmit({ name: 'admit-lifecycle' });
|
|
95
|
+
await Promise.resolve();
|
|
96
|
+
});
|
|
97
|
+
expect(screen.getByTestId('parcel')).toHaveTextContent('admit-lifecycle');
|
|
98
|
+
});
|
|
99
|
+
});
|