@openmrs/esm-framework 10.0.1-pre.5263 → 10.0.1-pre.5299
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/docs/functions/attach.md +1 -1
- package/docs/functions/createUseStore.md +1 -1
- package/docs/functions/defineConfigSchema.md +1 -1
- package/docs/functions/defineExtensionConfigSchema.md +1 -1
- package/docs/functions/detach.md +1 -1
- package/docs/functions/detachAll.md +1 -1
- package/docs/functions/getAssignedExtensions.md +17 -5
- package/docs/functions/getConfig.md +1 -1
- package/docs/functions/getExtensionNameFromId.md +1 -1
- package/docs/functions/getExtensionStore.md +1 -1
- package/docs/functions/provide.md +1 -1
- package/docs/functions/renderExtension.md +1 -1
- package/docs/functions/showModal.md +1 -1
- package/docs/functions/useAssignedExtensionIds.md +1 -2
- package/docs/functions/useAssignedExtensions.md +17 -2
- package/docs/functions/useExtensionSlotStore.md +1 -1
- package/docs/functions/useFhirPagination.md +1 -1
- package/docs/functions/useOpenmrsPagination.md +1 -1
- package/docs/functions/usePagination.md +5 -1
- package/docs/functions/useStore.md +4 -4
- package/docs/functions/useStoreWithActions.md +1 -1
- package/docs/interfaces/AssignedExtension.md +19 -9
- package/docs/interfaces/CancelLoading.md +2 -2
- package/docs/interfaces/ConnectedExtension.md +6 -6
- package/docs/interfaces/ExtensionMeta.md +1 -1
- package/docs/interfaces/ExtensionRegistration.md +11 -11
- package/docs/interfaces/ExtensionSlotState.md +8 -13
- package/docs/interfaces/ExtensionStore.md +2 -2
- package/docs/type-aliases/ExtensionSlotCustomState.md +11 -0
- package/package.json +20 -20
- package/src/integration-tests/extension-config-expressions.test.tsx +64 -2
- package/src/integration-tests/extension-config-propagation.test.tsx +185 -0
- package/src/integration-tests/extension-config.test.tsx +57 -2
- package/src/integration-tests/extension-recomputation.test.tsx +652 -0
- package/vitest.config.ts +22 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Interface: ExtensionStore
|
|
4
4
|
|
|
5
|
-
Defined in: [packages/framework/esm-extensions/src/store.ts:
|
|
5
|
+
Defined in: [packages/framework/esm-extensions/src/store.ts:99](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-extensions/src/store.ts#L99)
|
|
6
6
|
|
|
7
7
|
## Properties
|
|
8
8
|
|
|
@@ -10,4 +10,4 @@ Defined in: [packages/framework/esm-extensions/src/store.ts:67](https://github.c
|
|
|
10
10
|
|
|
11
11
|
> **slots**: `Record`\<`string`, [`ExtensionSlotState`](ExtensionSlotState.md)\>
|
|
12
12
|
|
|
13
|
-
Defined in: [packages/framework/esm-extensions/src/store.ts:
|
|
13
|
+
Defined in: [packages/framework/esm-extensions/src/store.ts:100](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-extensions/src/store.ts#L100)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
[O3 Framework](../API.md) / ExtensionSlotCustomState
|
|
2
|
+
|
|
3
|
+
# Type Alias: ExtensionSlotCustomState
|
|
4
|
+
|
|
5
|
+
> **ExtensionSlotCustomState** = `Record`\<`string`, `unknown`\> \| `undefined`
|
|
6
|
+
|
|
7
|
+
Defined in: [packages/framework/esm-extensions/src/store.ts:80](https://github.com/openmrs/openmrs-esm-core/blob/main/packages/framework/esm-extensions/src/store.ts#L80)
|
|
8
|
+
|
|
9
|
+
The state one rendering of a slot is displaying, which its extensions' display conditions are
|
|
10
|
+
evaluated against. Each key becomes a variable of that name in the expression, so only string
|
|
11
|
+
keys are reachable from a condition.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openmrs/esm-framework",
|
|
3
|
-
"version": "10.0.1-pre.
|
|
3
|
+
"version": "10.0.1-pre.5299",
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -60,24 +60,24 @@
|
|
|
60
60
|
"access": "public"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@openmrs/esm-api": "10.0.1-pre.
|
|
64
|
-
"@openmrs/esm-config": "10.0.1-pre.
|
|
65
|
-
"@openmrs/esm-context": "10.0.1-pre.
|
|
66
|
-
"@openmrs/esm-dynamic-loading": "10.0.1-pre.
|
|
67
|
-
"@openmrs/esm-emr-api": "10.0.1-pre.
|
|
68
|
-
"@openmrs/esm-error-handling": "10.0.1-pre.
|
|
69
|
-
"@openmrs/esm-expression-evaluator": "10.0.1-pre.
|
|
70
|
-
"@openmrs/esm-extensions": "10.0.1-pre.
|
|
71
|
-
"@openmrs/esm-feature-flags": "10.0.1-pre.
|
|
72
|
-
"@openmrs/esm-globals": "10.0.1-pre.
|
|
73
|
-
"@openmrs/esm-navigation": "10.0.1-pre.
|
|
74
|
-
"@openmrs/esm-offline": "10.0.1-pre.
|
|
75
|
-
"@openmrs/esm-react-utils": "10.0.1-pre.
|
|
76
|
-
"@openmrs/esm-routes": "10.0.1-pre.
|
|
77
|
-
"@openmrs/esm-state": "10.0.1-pre.
|
|
78
|
-
"@openmrs/esm-styleguide": "10.0.1-pre.
|
|
79
|
-
"@openmrs/esm-translations": "10.0.1-pre.
|
|
80
|
-
"@openmrs/esm-utils": "10.0.1-pre.
|
|
63
|
+
"@openmrs/esm-api": "10.0.1-pre.5299",
|
|
64
|
+
"@openmrs/esm-config": "10.0.1-pre.5299",
|
|
65
|
+
"@openmrs/esm-context": "10.0.1-pre.5299",
|
|
66
|
+
"@openmrs/esm-dynamic-loading": "10.0.1-pre.5299",
|
|
67
|
+
"@openmrs/esm-emr-api": "10.0.1-pre.5299",
|
|
68
|
+
"@openmrs/esm-error-handling": "10.0.1-pre.5299",
|
|
69
|
+
"@openmrs/esm-expression-evaluator": "10.0.1-pre.5299",
|
|
70
|
+
"@openmrs/esm-extensions": "10.0.1-pre.5299",
|
|
71
|
+
"@openmrs/esm-feature-flags": "10.0.1-pre.5299",
|
|
72
|
+
"@openmrs/esm-globals": "10.0.1-pre.5299",
|
|
73
|
+
"@openmrs/esm-navigation": "10.0.1-pre.5299",
|
|
74
|
+
"@openmrs/esm-offline": "10.0.1-pre.5299",
|
|
75
|
+
"@openmrs/esm-react-utils": "10.0.1-pre.5299",
|
|
76
|
+
"@openmrs/esm-routes": "10.0.1-pre.5299",
|
|
77
|
+
"@openmrs/esm-state": "10.0.1-pre.5299",
|
|
78
|
+
"@openmrs/esm-styleguide": "10.0.1-pre.5299",
|
|
79
|
+
"@openmrs/esm-translations": "10.0.1-pre.5299",
|
|
80
|
+
"@openmrs/esm-utils": "10.0.1-pre.5299"
|
|
81
81
|
},
|
|
82
82
|
"peerDependencies": {
|
|
83
83
|
"dayjs": "1.x",
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"swr": "2.x"
|
|
91
91
|
},
|
|
92
92
|
"devDependencies": {
|
|
93
|
-
"@openmrs/typedoc-plugin-file-categories": "10.0.1-pre.
|
|
93
|
+
"@openmrs/typedoc-plugin-file-categories": "10.0.1-pre.5299",
|
|
94
94
|
"@swc/cli": "0.8.1",
|
|
95
95
|
"@swc/core": "1.15.21",
|
|
96
96
|
"@vitest/coverage-v8": "^4.1.2",
|
|
@@ -2,10 +2,15 @@
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
4
|
import '@testing-library/jest-dom/vitest';
|
|
5
|
-
import { act, cleanup, render, screen, waitFor } from '@testing-library/react';
|
|
5
|
+
import { act, cleanup, render, screen, waitFor, within } from '@testing-library/react';
|
|
6
6
|
import { type Person } from '@openmrs/esm-api';
|
|
7
7
|
import { mockSessionStore } from '@openmrs/esm-api/mock';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
attach,
|
|
10
|
+
getExtensionRenderingsStore,
|
|
11
|
+
registerExtension,
|
|
12
|
+
updateInternalExtensionStore,
|
|
13
|
+
} from '../../../esm-extensions/src';
|
|
9
14
|
import { ExtensionSlot, getSyncLifecycle, openmrsComponentDecorator, useConfig } from '../../../esm-react-utils/src';
|
|
10
15
|
import {
|
|
11
16
|
configInternalStore,
|
|
@@ -41,6 +46,10 @@ vi.mock('@openmrs/esm-api', async () => {
|
|
|
41
46
|
*/
|
|
42
47
|
describe('Expression evaluation in extension display conditions', () => {
|
|
43
48
|
beforeEach(() => {
|
|
49
|
+
// A rendering outliving its module's schema makes the config system derive a config for an
|
|
50
|
+
// extension this test never registered.
|
|
51
|
+
// eslint-disable-next-line testing-library/no-render-in-lifecycle -- not a render; the rule matches any callee name containing "render"
|
|
52
|
+
getExtensionRenderingsStore().setState({ renderings: new Map() });
|
|
44
53
|
temporaryConfigStore.setState({ config: {} });
|
|
45
54
|
configInternalStore.setState({ providedConfigs: [], schemas: {}, moduleLoaded: {} });
|
|
46
55
|
mockSessionStore.setState({});
|
|
@@ -224,6 +233,59 @@ describe('Expression evaluation in extension display conditions', () => {
|
|
|
224
233
|
expect(slot.firstChild).toBeNull();
|
|
225
234
|
});
|
|
226
235
|
}, 10000);
|
|
236
|
+
|
|
237
|
+
it('evaluates a display condition against each rendering of a slot', async () => {
|
|
238
|
+
registerExtension({
|
|
239
|
+
name: 'Always',
|
|
240
|
+
moduleName: 'esm-bedrock',
|
|
241
|
+
load: getSyncLifecycle(() => <div>Always</div>, {
|
|
242
|
+
moduleName: 'esm-bedrock',
|
|
243
|
+
featureName: 'Bedrock',
|
|
244
|
+
disableTranslations: true,
|
|
245
|
+
}),
|
|
246
|
+
meta: {},
|
|
247
|
+
});
|
|
248
|
+
registerExtension({
|
|
249
|
+
name: 'Flagged',
|
|
250
|
+
moduleName: 'esm-bedrock',
|
|
251
|
+
load: getSyncLifecycle(() => <div>Flagged</div>, {
|
|
252
|
+
moduleName: 'esm-bedrock',
|
|
253
|
+
featureName: 'Bedrock',
|
|
254
|
+
disableTranslations: true,
|
|
255
|
+
}),
|
|
256
|
+
meta: {},
|
|
257
|
+
displayExpression: 'patient.flagged',
|
|
258
|
+
});
|
|
259
|
+
attach('rows-slot', 'Always');
|
|
260
|
+
attach('rows-slot', 'Flagged');
|
|
261
|
+
|
|
262
|
+
// A virtualized list renders the same slot once per row, each with its own patient. The slot
|
|
263
|
+
// is not "a place on the screen" here, so the condition has to be evaluated per rendering.
|
|
264
|
+
const Rows = openmrsComponentDecorator({
|
|
265
|
+
moduleName: 'esm-bedrock',
|
|
266
|
+
featureName: 'Bedrock',
|
|
267
|
+
disableTranslations: true,
|
|
268
|
+
})(() => (
|
|
269
|
+
<div>
|
|
270
|
+
<div data-testid="row-flagged">
|
|
271
|
+
<ExtensionSlot name="rows-slot" state={{ patient: { flagged: true } }} />
|
|
272
|
+
</div>
|
|
273
|
+
<div data-testid="row-plain">
|
|
274
|
+
<ExtensionSlot name="rows-slot" state={{ patient: { flagged: false } }} />
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
));
|
|
278
|
+
|
|
279
|
+
await act(async () => {
|
|
280
|
+
render(<Rows />);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
await waitFor(() => expect(within(screen.getByTestId('row-flagged')).getByText('Always')).toBeInTheDocument());
|
|
284
|
+
|
|
285
|
+
expect(within(screen.getByTestId('row-flagged')).getByText('Flagged')).toBeInTheDocument();
|
|
286
|
+
expect(within(screen.getByTestId('row-plain')).getByText('Always')).toBeInTheDocument();
|
|
287
|
+
expect(within(screen.getByTestId('row-plain')).queryByText('Flagged')).not.toBeInTheDocument();
|
|
288
|
+
}, 10000);
|
|
227
289
|
});
|
|
228
290
|
|
|
229
291
|
async function registerSimpleExtension(
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/* eslint-disable testing-library/no-unnecessary-act, testing-library/no-manual-cleanup */
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import '@testing-library/jest-dom/vitest';
|
|
5
|
+
import { act, cleanup, render, screen, waitFor } from '@testing-library/react';
|
|
6
|
+
import { mockSessionStore } from '@openmrs/esm-api/mock';
|
|
7
|
+
import {
|
|
8
|
+
attach,
|
|
9
|
+
getExtensionRenderingsStore,
|
|
10
|
+
registerExtension,
|
|
11
|
+
updateInternalExtensionStore,
|
|
12
|
+
} from '../../../esm-extensions/src';
|
|
13
|
+
import { ExtensionSlot, getSyncLifecycle, openmrsComponentDecorator } from '../../../esm-react-utils/src';
|
|
14
|
+
import {
|
|
15
|
+
configExtensionStore,
|
|
16
|
+
configInternalStore,
|
|
17
|
+
defineConfigSchema,
|
|
18
|
+
getExtensionSlotsConfigStore,
|
|
19
|
+
getExtensionsConfigStore,
|
|
20
|
+
provide,
|
|
21
|
+
registerModuleLoad,
|
|
22
|
+
resetConfigSystem,
|
|
23
|
+
temporaryConfigStore,
|
|
24
|
+
} from '../../../esm-config/src';
|
|
25
|
+
|
|
26
|
+
vi.mock('@openmrs/esm-api', async () => {
|
|
27
|
+
const original = await import('@openmrs/esm-api');
|
|
28
|
+
return { ...original, sessionStore: mockSessionStore, refetchCurrentUser: vi.fn() };
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const moduleName = 'esm-flintstone';
|
|
32
|
+
|
|
33
|
+
function reg(name: string) {
|
|
34
|
+
registerExtension({
|
|
35
|
+
name,
|
|
36
|
+
moduleName,
|
|
37
|
+
load: getSyncLifecycle(() => <div>{name}</div>, { moduleName, featureName: name }),
|
|
38
|
+
meta: {},
|
|
39
|
+
online: true,
|
|
40
|
+
offline: true,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Slots are recomputed only when an input that affects them changes, so a slot's configuration
|
|
46
|
+
* changing has to invalidate that slot. These cover the case the pre-existing config tests don't:
|
|
47
|
+
* the configuration changing *after* the slot has already rendered, which is what an implementer
|
|
48
|
+
* does from the implementer tools.
|
|
49
|
+
*/
|
|
50
|
+
describe('runtime slot config changes', () => {
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
// eslint-disable-next-line testing-library/no-render-in-lifecycle -- not a render; the rule matches any callee name containing "render"
|
|
53
|
+
getExtensionRenderingsStore().setState({ renderings: new Map() });
|
|
54
|
+
temporaryConfigStore.setState({ config: {} });
|
|
55
|
+
configInternalStore.setState({ providedConfigs: [], schemas: {}, moduleLoaded: {} });
|
|
56
|
+
mockSessionStore.setState({});
|
|
57
|
+
getExtensionSlotsConfigStore().setState({ slots: {} });
|
|
58
|
+
getExtensionsConfigStore().setState({ configs: {} });
|
|
59
|
+
configExtensionStore.setState({ mountedExtensions: [] });
|
|
60
|
+
updateInternalExtensionStore(() => ({ slots: {}, extensions: {} }));
|
|
61
|
+
resetConfigSystem();
|
|
62
|
+
defineConfigSchema(moduleName, {});
|
|
63
|
+
registerModuleLoad(moduleName);
|
|
64
|
+
cleanup();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
async function renderSlot() {
|
|
68
|
+
const App = openmrsComponentDecorator({ moduleName, featureName: 'f', disableTranslations: true })(() => (
|
|
69
|
+
<ExtensionSlot data-testid="slot" name="cfg-slot" />
|
|
70
|
+
));
|
|
71
|
+
await act(async () => {
|
|
72
|
+
render(<App />);
|
|
73
|
+
});
|
|
74
|
+
await act(async () => {
|
|
75
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function rendered() {
|
|
80
|
+
return Array.from(screen.getByTestId('slot').childNodes).map((n) => (n as HTMLElement).textContent);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Polls rather than sleeping: the cascade from a config write to the DOM runs across several
|
|
84
|
+
// store updates and a parcel mount, which is not a fixed amount of wall-clock time.
|
|
85
|
+
async function expectRendered(expected: Array<string | null>) {
|
|
86
|
+
await waitFor(() => expect(rendered()).toEqual(expected));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
it('ADD: config adding an extension after the slot is mounted shows it', async () => {
|
|
90
|
+
reg('Fred');
|
|
91
|
+
reg('Barney');
|
|
92
|
+
attach('cfg-slot', 'Fred');
|
|
93
|
+
await renderSlot();
|
|
94
|
+
await expectRendered(['Fred']);
|
|
95
|
+
|
|
96
|
+
await act(async () => {
|
|
97
|
+
provide({ [moduleName]: { extensionSlots: { 'cfg-slot': { add: ['Barney'] } } } });
|
|
98
|
+
});
|
|
99
|
+
await expectRendered(['Fred', 'Barney']);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('REMOVE: config removing an extension after the slot is mounted hides it', async () => {
|
|
103
|
+
reg('Fred');
|
|
104
|
+
reg('Wilma');
|
|
105
|
+
attach('cfg-slot', 'Fred');
|
|
106
|
+
attach('cfg-slot', 'Wilma');
|
|
107
|
+
await renderSlot();
|
|
108
|
+
await expectRendered(['Fred', 'Wilma']);
|
|
109
|
+
|
|
110
|
+
await act(async () => {
|
|
111
|
+
provide({ [moduleName]: { extensionSlots: { 'cfg-slot': { remove: ['Fred'] } } } });
|
|
112
|
+
});
|
|
113
|
+
await expectRendered(['Wilma']);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('ORDER: config reordering after the slot is mounted reorders the DOM', async () => {
|
|
117
|
+
reg('Fred');
|
|
118
|
+
reg('Wilma');
|
|
119
|
+
attach('cfg-slot', 'Fred');
|
|
120
|
+
attach('cfg-slot', 'Wilma');
|
|
121
|
+
await renderSlot();
|
|
122
|
+
await expectRendered(['Fred', 'Wilma']);
|
|
123
|
+
|
|
124
|
+
await act(async () => {
|
|
125
|
+
provide({ [moduleName]: { extensionSlots: { 'cfg-slot': { order: ['Wilma', 'Fred'] } } } });
|
|
126
|
+
});
|
|
127
|
+
await expectRendered(['Wilma', 'Fred']);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('TEMPORARY CONFIG: implementer-tools style change propagates', async () => {
|
|
131
|
+
reg('Fred');
|
|
132
|
+
reg('Barney');
|
|
133
|
+
attach('cfg-slot', 'Fred');
|
|
134
|
+
await renderSlot();
|
|
135
|
+
await expectRendered(['Fred']);
|
|
136
|
+
|
|
137
|
+
await act(async () => {
|
|
138
|
+
temporaryConfigStore.setState({
|
|
139
|
+
config: { [moduleName]: { extensionSlots: { 'cfg-slot': { add: ['Barney'] } } } },
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
await expectRendered(['Fred', 'Barney']);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('CLEARED: clearing the temporary config reverts the slot to its unconfigured state', async () => {
|
|
146
|
+
reg('Fred');
|
|
147
|
+
reg('Barney');
|
|
148
|
+
attach('cfg-slot', 'Fred');
|
|
149
|
+
await renderSlot();
|
|
150
|
+
|
|
151
|
+
await act(async () => {
|
|
152
|
+
temporaryConfigStore.setState({
|
|
153
|
+
config: { [moduleName]: { extensionSlots: { 'cfg-slot': { add: ['Barney'] } } } },
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
await expectRendered(['Fred', 'Barney']);
|
|
157
|
+
|
|
158
|
+
// What the implementer tools' "clear temporary config" does. The slot loses its config
|
|
159
|
+
// entry entirely, which has to take effect without a page reload.
|
|
160
|
+
await act(async () => {
|
|
161
|
+
temporaryConfigStore.setState({ config: {} });
|
|
162
|
+
});
|
|
163
|
+
await expectRendered(['Fred']);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('REPEATED: a second config change after the first still propagates', async () => {
|
|
167
|
+
reg('Fred');
|
|
168
|
+
reg('Barney');
|
|
169
|
+
reg('Betty');
|
|
170
|
+
attach('cfg-slot', 'Fred');
|
|
171
|
+
await renderSlot();
|
|
172
|
+
|
|
173
|
+
await act(async () => {
|
|
174
|
+
provide({ [moduleName]: { extensionSlots: { 'cfg-slot': { add: ['Barney'] } } } });
|
|
175
|
+
});
|
|
176
|
+
await expectRendered(['Fred', 'Barney']);
|
|
177
|
+
|
|
178
|
+
await act(async () => {
|
|
179
|
+
provide({ [moduleName]: { extensionSlots: { 'cfg-slot': { add: ['Barney', 'Betty'] } } } });
|
|
180
|
+
});
|
|
181
|
+
// Two config-`add`ed extensions with no explicit order come out in the order the
|
|
182
|
+
// configuration lists them.
|
|
183
|
+
await expectRendered(['Fred', 'Barney', 'Betty']);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
@@ -5,7 +5,12 @@ import '@testing-library/jest-dom/vitest';
|
|
|
5
5
|
import { act, cleanup, render, screen, waitFor } from '@testing-library/react';
|
|
6
6
|
import { type Person } from '@openmrs/esm-api';
|
|
7
7
|
import { mockSessionStore } from '@openmrs/esm-api/mock';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
attach,
|
|
10
|
+
getExtensionRenderingsStore,
|
|
11
|
+
registerExtension,
|
|
12
|
+
updateInternalExtensionStore,
|
|
13
|
+
} from '../../../esm-extensions/src';
|
|
9
14
|
import {
|
|
10
15
|
ExtensionSlot,
|
|
11
16
|
getSyncLifecycle,
|
|
@@ -36,6 +41,10 @@ vi.mock('@openmrs/esm-api', async () => {
|
|
|
36
41
|
|
|
37
42
|
describe('Interaction between configuration and extension systems', () => {
|
|
38
43
|
beforeEach(() => {
|
|
44
|
+
// A rendering outliving its module's schema makes the config system derive a config for an
|
|
45
|
+
// extension this test never registered.
|
|
46
|
+
// eslint-disable-next-line testing-library/no-render-in-lifecycle -- not a render; the rule matches any callee name containing "render"
|
|
47
|
+
getExtensionRenderingsStore().setState({ renderings: new Map() });
|
|
39
48
|
temporaryConfigStore.setState({ config: {} });
|
|
40
49
|
configInternalStore.setState({ providedConfigs: [], schemas: {}, moduleLoaded: {} });
|
|
41
50
|
mockSessionStore.setState({});
|
|
@@ -187,6 +196,52 @@ describe('Interaction between configuration and extension systems', () => {
|
|
|
187
196
|
});
|
|
188
197
|
});
|
|
189
198
|
|
|
199
|
+
it('Should give each extension instance its own config in every rendering of the slot', async () => {
|
|
200
|
+
registerSimpleExtension('obs', 'esm-widgets', true);
|
|
201
|
+
defineConfigSchema('esm-widgets', {
|
|
202
|
+
concept: { _type: Type.String, _default: '(none)' },
|
|
203
|
+
});
|
|
204
|
+
registerModuleLoad('esm-widgets');
|
|
205
|
+
|
|
206
|
+
attach('Vitals slot', 'obs#weight');
|
|
207
|
+
attach('Vitals slot', 'obs#height');
|
|
208
|
+
provide({
|
|
209
|
+
'esm-chart': {
|
|
210
|
+
extensionSlots: {
|
|
211
|
+
'Vitals slot': {
|
|
212
|
+
configure: {
|
|
213
|
+
'obs#weight': { concept: 'Weight (kg)' },
|
|
214
|
+
'obs#height': { concept: 'Height (cm)' },
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const App = openmrsComponentDecorator({
|
|
222
|
+
moduleName: 'esm-chart',
|
|
223
|
+
featureName: 'The chart',
|
|
224
|
+
disableTranslations: true,
|
|
225
|
+
})(() => (
|
|
226
|
+
<>
|
|
227
|
+
<ExtensionSlot data-testid="row-1" name="Vitals slot" />
|
|
228
|
+
<ExtensionSlot data-testid="row-2" name="Vitals slot" />
|
|
229
|
+
</>
|
|
230
|
+
));
|
|
231
|
+
|
|
232
|
+
act(() => {
|
|
233
|
+
render(<App />);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
await waitFor(() => {
|
|
237
|
+
for (const testId of ['row-1', 'row-2']) {
|
|
238
|
+
const slot = screen.getByTestId(testId);
|
|
239
|
+
expect(slot.firstChild).toHaveTextContent(/Weight \(kg\)/);
|
|
240
|
+
expect(slot.lastChild).toHaveTextContent(/Height \(cm\)/);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
190
245
|
it('Slot config should update with temporary config', async () => {
|
|
191
246
|
registerSimpleExtension('Pearl', 'esm-slaghoople');
|
|
192
247
|
attach('A slot', 'Pearl');
|
|
@@ -272,7 +327,7 @@ describe('Interaction between configuration and extension systems', () => {
|
|
|
272
327
|
return (
|
|
273
328
|
<div>
|
|
274
329
|
<ExtensionSlot data-testid="slot" name="A slot" />
|
|
275
|
-
{store.slots['A slot'].
|
|
330
|
+
{store.slots['A slot'].candidateExtensions.map((e) => (
|
|
276
331
|
<div key={e.name}>{JSON.stringify(e.config)}</div>
|
|
277
332
|
))}
|
|
278
333
|
</div>
|