@finos/legend-application-studio 28.21.31 → 28.21.32
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/lib/__lib__/LegendStudioApplicationNavigationContext.d.ts +2 -1
- package/lib/__lib__/LegendStudioApplicationNavigationContext.d.ts.map +1 -1
- package/lib/__lib__/LegendStudioApplicationNavigationContext.js +1 -0
- package/lib/__lib__/LegendStudioApplicationNavigationContext.js.map +1 -1
- package/lib/components/ElementIconUtils.d.ts.map +1 -1
- package/lib/components/ElementIconUtils.js +2 -0
- package/lib/components/ElementIconUtils.js.map +1 -1
- package/lib/components/editor/availability-editor.css +1 -0
- package/lib/components/editor/availability-editor.css.map +1 -0
- package/lib/components/editor/editor-group/EditorGroup.d.ts.map +1 -1
- package/lib/components/editor/editor-group/EditorGroup.js +5 -0
- package/lib/components/editor/editor-group/EditorGroup.js.map +1 -1
- package/lib/components/editor/editor-group/availability/AvailabilityEditor.d.ts +19 -0
- package/lib/components/editor/editor-group/availability/AvailabilityEditor.d.ts.map +1 -0
- package/lib/components/editor/editor-group/availability/AvailabilityEditor.js +50 -0
- package/lib/components/editor/editor-group/availability/AvailabilityEditor.js.map +1 -0
- package/lib/components/editor/editor-group/availability/testable/AvailabilityTestableEditor.d.ts +22 -0
- package/lib/components/editor/editor-group/availability/testable/AvailabilityTestableEditor.d.ts.map +1 -0
- package/lib/components/editor/editor-group/availability/testable/AvailabilityTestableEditor.js +205 -0
- package/lib/components/editor/editor-group/availability/testable/AvailabilityTestableEditor.js.map +1 -0
- package/lib/components/editor/editor-group/testable/TestableSharedComponents.d.ts +5 -0
- package/lib/components/editor/editor-group/testable/TestableSharedComponents.d.ts.map +1 -1
- package/lib/components/editor/editor-group/testable/TestableSharedComponents.js +1 -1
- package/lib/components/editor/editor-group/testable/TestableSharedComponents.js.map +1 -1
- package/lib/components/editor/panel-group/DevToolPanel.d.ts.map +1 -1
- package/lib/components/editor/panel-group/DevToolPanel.js +27 -24
- package/lib/components/editor/panel-group/DevToolPanel.js.map +1 -1
- package/lib/components/extensions/Core_LegendStudioApplicationPlugin.d.ts.map +1 -1
- package/lib/components/extensions/Core_LegendStudioApplicationPlugin.js +1 -0
- package/lib/components/extensions/Core_LegendStudioApplicationPlugin.js.map +1 -1
- package/lib/index.css +2 -2
- package/lib/index.css.map +1 -1
- package/lib/package.json +1 -1
- package/lib/stores/editor/EditorGraphState.d.ts.map +1 -1
- package/lib/stores/editor/EditorGraphState.js +4 -1
- package/lib/stores/editor/EditorGraphState.js.map +1 -1
- package/lib/stores/editor/EditorTabManagerState.d.ts.map +1 -1
- package/lib/stores/editor/EditorTabManagerState.js +5 -1
- package/lib/stores/editor/EditorTabManagerState.js.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/availability/AvailabilityEditorState.d.ts +32 -0
- package/lib/stores/editor/editor-state/element-editor-state/availability/AvailabilityEditorState.d.ts.map +1 -0
- package/lib/stores/editor/editor-state/element-editor-state/availability/AvailabilityEditorState.js +46 -0
- package/lib/stores/editor/editor-state/element-editor-state/availability/AvailabilityEditorState.js.map +1 -0
- package/lib/stores/editor/editor-state/element-editor-state/availability/testable/AvailabilityTestableState.d.ts +111 -0
- package/lib/stores/editor/editor-state/element-editor-state/availability/testable/AvailabilityTestableState.d.ts.map +1 -0
- package/lib/stores/editor/editor-state/element-editor-state/availability/testable/AvailabilityTestableState.js +425 -0
- package/lib/stores/editor/editor-state/element-editor-state/availability/testable/AvailabilityTestableState.js.map +1 -0
- package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.d.ts +3 -0
- package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.d.ts.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.js +8 -2
- package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.js.map +1 -1
- package/lib/stores/editor/utils/ModelClassifierUtils.d.ts +2 -1
- package/lib/stores/editor/utils/ModelClassifierUtils.d.ts.map +1 -1
- package/lib/stores/editor/utils/ModelClassifierUtils.js +1 -0
- package/lib/stores/editor/utils/ModelClassifierUtils.js.map +1 -1
- package/package.json +12 -12
- package/src/__lib__/LegendStudioApplicationNavigationContext.ts +1 -0
- package/src/components/ElementIconUtils.tsx +2 -0
- package/src/components/editor/editor-group/EditorGroup.tsx +4 -0
- package/src/components/editor/editor-group/availability/AvailabilityEditor.tsx +114 -0
- package/src/components/editor/editor-group/availability/testable/AvailabilityTestableEditor.tsx +829 -0
- package/src/components/editor/editor-group/testable/TestableSharedComponents.tsx +1 -1
- package/src/components/editor/panel-group/DevToolPanel.tsx +61 -60
- package/src/components/extensions/Core_LegendStudioApplicationPlugin.tsx +1 -0
- package/src/stores/editor/EditorGraphState.ts +3 -0
- package/src/stores/editor/EditorTabManagerState.ts +4 -0
- package/src/stores/editor/editor-state/element-editor-state/availability/AvailabilityEditorState.ts +56 -0
- package/src/stores/editor/editor-state/element-editor-state/availability/testable/AvailabilityTestableState.ts +631 -0
- package/src/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.ts +13 -3
- package/src/stores/editor/utils/ModelClassifierUtils.ts +1 -0
- package/tsconfig.json +4 -0
package/src/components/editor/editor-group/availability/testable/AvailabilityTestableEditor.tsx
ADDED
|
@@ -0,0 +1,829 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026-present, Goldman Sachs
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { observer } from 'mobx-react-lite';
|
|
18
|
+
import { flowResult } from 'mobx';
|
|
19
|
+
import { forwardRef, useRef, useState } from 'react';
|
|
20
|
+
import {
|
|
21
|
+
BlankPanelPlaceholder,
|
|
22
|
+
clsx,
|
|
23
|
+
ContextMenu,
|
|
24
|
+
CustomSelectorInput,
|
|
25
|
+
Dialog,
|
|
26
|
+
MenuContent,
|
|
27
|
+
MenuContentItem,
|
|
28
|
+
Modal,
|
|
29
|
+
ModalBody,
|
|
30
|
+
ModalFooter,
|
|
31
|
+
ModalFooterButton,
|
|
32
|
+
ModalHeader,
|
|
33
|
+
ModalTitle,
|
|
34
|
+
Panel,
|
|
35
|
+
PanelContent,
|
|
36
|
+
PanelFormTextField,
|
|
37
|
+
PanelHeader,
|
|
38
|
+
PanelHeaderActionItem,
|
|
39
|
+
PanelHeaderActions,
|
|
40
|
+
PlayIcon,
|
|
41
|
+
PlusIcon,
|
|
42
|
+
ResizablePanel,
|
|
43
|
+
ResizablePanelGroup,
|
|
44
|
+
ResizablePanelSplitter,
|
|
45
|
+
ResizablePanelSplitterLine,
|
|
46
|
+
RunAllIcon,
|
|
47
|
+
RunErrorsIcon,
|
|
48
|
+
} from '@finos/legend-art';
|
|
49
|
+
import { CodeEditor } from '@finos/legend-lego/code-editor';
|
|
50
|
+
import { CODE_EDITOR_LANGUAGE } from '@finos/legend-code-editor';
|
|
51
|
+
import {
|
|
52
|
+
type AvailabilityBarrierTest,
|
|
53
|
+
type AvailabilityTestSuite,
|
|
54
|
+
} from '@finos/legend-graph';
|
|
55
|
+
import {
|
|
56
|
+
TESTABLE_RESULT,
|
|
57
|
+
getTestableResultFromTestResult,
|
|
58
|
+
} from '../../../../../stores/editor/sidebar-state/testable/GlobalTestRunnerState.js';
|
|
59
|
+
import { getTestableResultIcon } from '../../../side-bar/testable/GlobalTestRunner.js';
|
|
60
|
+
import {
|
|
61
|
+
AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT,
|
|
62
|
+
type AvailabilityTestState,
|
|
63
|
+
type AvailabilityTestSuiteState,
|
|
64
|
+
type AvailabilityTestableState,
|
|
65
|
+
} from '../../../../../stores/editor/editor-state/element-editor-state/availability/testable/AvailabilityTestableState.js';
|
|
66
|
+
import {
|
|
67
|
+
atomicTest_setId,
|
|
68
|
+
testAssertion_setId,
|
|
69
|
+
testSuite_setId,
|
|
70
|
+
} from '../../../../../stores/graph-modifier/Testable_GraphModifierHelper.js';
|
|
71
|
+
import {
|
|
72
|
+
RenameModal,
|
|
73
|
+
TestAssertionResultViewer,
|
|
74
|
+
} from '../../testable/TestableSharedComponents.js';
|
|
75
|
+
import { TEST_ASSERTION_TAB } from '../../../../../stores/editor/editor-state/element-editor-state/testable/TestAssertionState.js';
|
|
76
|
+
import { RelationElementEditor } from '../../data-editor/RelationElementsDataEditor.js';
|
|
77
|
+
import { validateTestableId } from '../../../../../stores/editor/utils/TestableUtils.js';
|
|
78
|
+
|
|
79
|
+
// ─── Format helpers ─────────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
interface FormatOption {
|
|
82
|
+
label: string;
|
|
83
|
+
value: AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const FORMAT_OPTIONS: FormatOption[] = Object.values(
|
|
87
|
+
AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT,
|
|
88
|
+
).map((format) => ({ label: format, value: format }));
|
|
89
|
+
|
|
90
|
+
const FormatSelector = observer(
|
|
91
|
+
(props: {
|
|
92
|
+
value: AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT;
|
|
93
|
+
onChange: (v: AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT) => void;
|
|
94
|
+
disabled?: boolean;
|
|
95
|
+
darkMode?: boolean;
|
|
96
|
+
}) => {
|
|
97
|
+
const { value, onChange, disabled, darkMode } = props;
|
|
98
|
+
const selectedOption =
|
|
99
|
+
FORMAT_OPTIONS.find((option) => option.value === value) ?? null;
|
|
100
|
+
return (
|
|
101
|
+
<div className="availability-test-editor__format-selector">
|
|
102
|
+
<CustomSelectorInput
|
|
103
|
+
options={FORMAT_OPTIONS}
|
|
104
|
+
onChange={(option: FormatOption | null): void => {
|
|
105
|
+
if (option) {
|
|
106
|
+
onChange(option.value);
|
|
107
|
+
}
|
|
108
|
+
}}
|
|
109
|
+
value={selectedOption}
|
|
110
|
+
placeholder="Select watermark format"
|
|
111
|
+
isClearable={false}
|
|
112
|
+
darkMode={darkMode ?? true}
|
|
113
|
+
disabled={disabled}
|
|
114
|
+
/>
|
|
115
|
+
</div>
|
|
116
|
+
);
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
// ─── Create Suite Modal ─────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
const CreateSuiteModal = observer(
|
|
123
|
+
(props: {
|
|
124
|
+
testableState: AvailabilityTestableState;
|
|
125
|
+
onClose: () => void;
|
|
126
|
+
}) => {
|
|
127
|
+
const { testableState, onClose } = props;
|
|
128
|
+
const editorStore = testableState.editorState.editorStore;
|
|
129
|
+
const applicationStore = editorStore.applicationStore;
|
|
130
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
131
|
+
|
|
132
|
+
const existingSuiteIds = testableState.availability.tests.map(
|
|
133
|
+
(suite) => suite.id,
|
|
134
|
+
);
|
|
135
|
+
const [suiteName, setSuiteName] = useState<string | undefined>(undefined);
|
|
136
|
+
const [testName, setTestName] = useState<string | undefined>(undefined);
|
|
137
|
+
const [format, setFormat] =
|
|
138
|
+
useState<AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT>(
|
|
139
|
+
AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT.DEFAULT,
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const suiteError = validateTestableId(suiteName, existingSuiteIds);
|
|
143
|
+
const testError = validateTestableId(testName, undefined);
|
|
144
|
+
const isValid = Boolean(suiteName && !suiteError && testName && !testError);
|
|
145
|
+
|
|
146
|
+
const create = (): void => {
|
|
147
|
+
if (!suiteName || !testName) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
testableState.addSuite(format, suiteName, testName);
|
|
152
|
+
onClose();
|
|
153
|
+
} catch (err) {
|
|
154
|
+
applicationStore.notificationService.notifyError(
|
|
155
|
+
err instanceof Error ? err.message : String(err),
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<Dialog
|
|
162
|
+
open={true}
|
|
163
|
+
onClose={onClose}
|
|
164
|
+
classes={{ container: 'search-modal__container' }}
|
|
165
|
+
slotProps={{
|
|
166
|
+
transition: { onEnter: () => inputRef.current?.focus() },
|
|
167
|
+
paper: { classes: { root: 'search-modal__inner-container' } },
|
|
168
|
+
}}
|
|
169
|
+
>
|
|
170
|
+
<Modal
|
|
171
|
+
darkMode={
|
|
172
|
+
!applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled
|
|
173
|
+
}
|
|
174
|
+
>
|
|
175
|
+
<ModalHeader>
|
|
176
|
+
<ModalTitle title="Create Test Suite" />
|
|
177
|
+
</ModalHeader>
|
|
178
|
+
<ModalBody>
|
|
179
|
+
<PanelFormTextField
|
|
180
|
+
ref={inputRef}
|
|
181
|
+
name="Suite Name"
|
|
182
|
+
prompt="Unique identifier for the test suite"
|
|
183
|
+
placeholder="e.g. suite_1"
|
|
184
|
+
value={suiteName}
|
|
185
|
+
update={(v): void => setSuiteName(v ?? '')}
|
|
186
|
+
errorMessage={suiteError}
|
|
187
|
+
/>
|
|
188
|
+
<PanelFormTextField
|
|
189
|
+
name="Test Name"
|
|
190
|
+
prompt="Name for the first test in this suite"
|
|
191
|
+
placeholder="e.g. test_1"
|
|
192
|
+
value={testName}
|
|
193
|
+
update={(v): void => setTestName(v ?? '')}
|
|
194
|
+
errorMessage={testError}
|
|
195
|
+
/>
|
|
196
|
+
<div className="panel__content__form__section">
|
|
197
|
+
<div className="panel__content__form__section__header__label">
|
|
198
|
+
Watermark Serialization Format
|
|
199
|
+
</div>
|
|
200
|
+
<div className="panel__content__form__section__header__prompt">
|
|
201
|
+
This format is fixed for the life of the test.
|
|
202
|
+
</div>
|
|
203
|
+
<FormatSelector
|
|
204
|
+
value={format}
|
|
205
|
+
onChange={setFormat}
|
|
206
|
+
darkMode={
|
|
207
|
+
!applicationStore.layoutService
|
|
208
|
+
.TEMPORARY__isLightColorThemeEnabled
|
|
209
|
+
}
|
|
210
|
+
/>
|
|
211
|
+
</div>
|
|
212
|
+
</ModalBody>
|
|
213
|
+
<ModalFooter>
|
|
214
|
+
<ModalFooterButton
|
|
215
|
+
disabled={!isValid}
|
|
216
|
+
title={!isValid ? 'Fill in all required fields' : 'Create Suite'}
|
|
217
|
+
onClick={create}
|
|
218
|
+
text="Create"
|
|
219
|
+
/>
|
|
220
|
+
<ModalFooterButton
|
|
221
|
+
onClick={onClose}
|
|
222
|
+
text="Close"
|
|
223
|
+
type="secondary"
|
|
224
|
+
/>
|
|
225
|
+
</ModalFooter>
|
|
226
|
+
</Modal>
|
|
227
|
+
</Dialog>
|
|
228
|
+
);
|
|
229
|
+
},
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
// ─── Create Test Modal ──────────────────────────────────────────────────────
|
|
233
|
+
|
|
234
|
+
const CreateTestModal = observer(
|
|
235
|
+
(props: { suiteState: AvailabilityTestSuiteState; onClose: () => void }) => {
|
|
236
|
+
const { suiteState, onClose } = props;
|
|
237
|
+
const applicationStore = suiteState.editorStore.applicationStore;
|
|
238
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
239
|
+
|
|
240
|
+
const existingIds = suiteState.suite.tests.map((test) => test.id);
|
|
241
|
+
const [testName, setTestName] = useState<string | undefined>(undefined);
|
|
242
|
+
const [format, setFormat] =
|
|
243
|
+
useState<AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT>(
|
|
244
|
+
AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT.DEFAULT,
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
const testError = validateTestableId(testName, existingIds);
|
|
248
|
+
const isValid = Boolean(testName && !testError);
|
|
249
|
+
|
|
250
|
+
const create = (): void => {
|
|
251
|
+
if (!testName) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
suiteState.addTest(format, testName);
|
|
256
|
+
onClose();
|
|
257
|
+
} catch (err) {
|
|
258
|
+
applicationStore.notificationService.notifyError(
|
|
259
|
+
err instanceof Error ? err.message : String(err),
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
return (
|
|
265
|
+
<Dialog
|
|
266
|
+
open={true}
|
|
267
|
+
onClose={onClose}
|
|
268
|
+
classes={{ container: 'search-modal__container' }}
|
|
269
|
+
slotProps={{
|
|
270
|
+
transition: { onEnter: () => inputRef.current?.focus() },
|
|
271
|
+
paper: { classes: { root: 'search-modal__inner-container' } },
|
|
272
|
+
}}
|
|
273
|
+
>
|
|
274
|
+
<Modal
|
|
275
|
+
darkMode={
|
|
276
|
+
!applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled
|
|
277
|
+
}
|
|
278
|
+
>
|
|
279
|
+
<ModalHeader>
|
|
280
|
+
<ModalTitle title={`Add Test to "${suiteState.suite.id}"`} />
|
|
281
|
+
</ModalHeader>
|
|
282
|
+
<ModalBody>
|
|
283
|
+
<PanelFormTextField
|
|
284
|
+
ref={inputRef}
|
|
285
|
+
name="Test Name"
|
|
286
|
+
prompt="Unique identifier for the test"
|
|
287
|
+
placeholder="e.g. test_1"
|
|
288
|
+
value={testName}
|
|
289
|
+
update={(v): void => setTestName(v ?? '')}
|
|
290
|
+
errorMessage={testError}
|
|
291
|
+
/>
|
|
292
|
+
<div className="panel__content__form__section">
|
|
293
|
+
<div className="panel__content__form__section__header__label">
|
|
294
|
+
Watermark Serialization Format
|
|
295
|
+
</div>
|
|
296
|
+
<div className="panel__content__form__section__header__prompt">
|
|
297
|
+
This format is fixed for the life of the test.
|
|
298
|
+
</div>
|
|
299
|
+
<FormatSelector
|
|
300
|
+
value={format}
|
|
301
|
+
onChange={setFormat}
|
|
302
|
+
darkMode={
|
|
303
|
+
!applicationStore.layoutService
|
|
304
|
+
.TEMPORARY__isLightColorThemeEnabled
|
|
305
|
+
}
|
|
306
|
+
/>
|
|
307
|
+
</div>
|
|
308
|
+
</ModalBody>
|
|
309
|
+
<ModalFooter>
|
|
310
|
+
<ModalFooterButton
|
|
311
|
+
disabled={!isValid}
|
|
312
|
+
title={!isValid ? 'Fill in all required fields' : 'Create Test'}
|
|
313
|
+
onClick={create}
|
|
314
|
+
text="Create"
|
|
315
|
+
/>
|
|
316
|
+
<ModalFooterButton
|
|
317
|
+
onClick={onClose}
|
|
318
|
+
text="Close"
|
|
319
|
+
type="secondary"
|
|
320
|
+
/>
|
|
321
|
+
</ModalFooter>
|
|
322
|
+
</Modal>
|
|
323
|
+
</Dialog>
|
|
324
|
+
);
|
|
325
|
+
},
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
// ─── Availability expected-JSON editor ──────────────────────────────────────
|
|
329
|
+
|
|
330
|
+
const ExpectedJsonEditor = observer(
|
|
331
|
+
(props: { testState: AvailabilityTestState }) => {
|
|
332
|
+
const { testState } = props;
|
|
333
|
+
const isReadOnly = testState.isReadOnly;
|
|
334
|
+
const canAdd = testState.parsedExpected !== undefined;
|
|
335
|
+
const [editorRefreshNonce, setEditorRefreshNonce] = useState(0);
|
|
336
|
+
|
|
337
|
+
const handleAdd = (): void => {
|
|
338
|
+
testState.addExpectedEntry();
|
|
339
|
+
// Force a lightweight rerender/remount so Monaco reflects external
|
|
340
|
+
// state updates immediately (without tab switching).
|
|
341
|
+
setEditorRefreshNonce((val) => val + 1);
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
return (
|
|
345
|
+
<div className="availability-assertion-editor__expected">
|
|
346
|
+
<div className="availability-assertion-editor__expected__body availability-assertion-editor__expected__body--code">
|
|
347
|
+
<CodeEditor
|
|
348
|
+
inputValue={testState.expectedValue}
|
|
349
|
+
language={CODE_EDITOR_LANGUAGE.JSON}
|
|
350
|
+
updateInput={(val: string): void => {
|
|
351
|
+
testState.setExpectedValue(val);
|
|
352
|
+
}}
|
|
353
|
+
hideGutter={true}
|
|
354
|
+
rightActions={
|
|
355
|
+
<button
|
|
356
|
+
tabIndex={-1}
|
|
357
|
+
className="code-editor__header__action"
|
|
358
|
+
disabled={isReadOnly || !canAdd}
|
|
359
|
+
onClick={handleAdd}
|
|
360
|
+
title={
|
|
361
|
+
canAdd
|
|
362
|
+
? 'Add entry'
|
|
363
|
+
: 'Fix invalid JSON first, then add entry'
|
|
364
|
+
}
|
|
365
|
+
>
|
|
366
|
+
<PlusIcon />
|
|
367
|
+
</button>
|
|
368
|
+
}
|
|
369
|
+
key={`availability-expected-json-${String(editorRefreshNonce)}`}
|
|
370
|
+
/>
|
|
371
|
+
</div>
|
|
372
|
+
</div>
|
|
373
|
+
);
|
|
374
|
+
},
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
const AvailabilityAssertionEditor = observer(
|
|
378
|
+
(props: { testState: AvailabilityTestState }) => {
|
|
379
|
+
const { testState } = props;
|
|
380
|
+
const assertionEditorState = testState.assertionEditorStates[0];
|
|
381
|
+
const selectedTab =
|
|
382
|
+
assertionEditorState?.selectedTab ?? TEST_ASSERTION_TAB.EXPECTED;
|
|
383
|
+
const changeTab = (tab: TEST_ASSERTION_TAB): void =>
|
|
384
|
+
assertionEditorState?.setSelectedTab(tab);
|
|
385
|
+
|
|
386
|
+
return (
|
|
387
|
+
<div className="availability-assertion-editor panel">
|
|
388
|
+
<PanelHeader>
|
|
389
|
+
<div className="availability-assertion-editor__title">
|
|
390
|
+
<div className="availability-assertion-editor__title__value">
|
|
391
|
+
{testState.test.id}
|
|
392
|
+
</div>
|
|
393
|
+
</div>
|
|
394
|
+
<div className="availability-assertion-editor__meta">
|
|
395
|
+
<div className="availability-assertion-editor__meta__label">
|
|
396
|
+
Format
|
|
397
|
+
</div>
|
|
398
|
+
<div className="availability-assertion-editor__meta__value">
|
|
399
|
+
{testState.format}
|
|
400
|
+
</div>
|
|
401
|
+
</div>
|
|
402
|
+
</PanelHeader>
|
|
403
|
+
<div className="testable-test-assertion-editor__header">
|
|
404
|
+
<div className="testable-test-assertion-editor__header__tabs">
|
|
405
|
+
{Object.values(TEST_ASSERTION_TAB).map((tab) => (
|
|
406
|
+
<div
|
|
407
|
+
key={tab}
|
|
408
|
+
onClick={(): void => changeTab(tab)}
|
|
409
|
+
className={clsx('testable-test-assertion-editor__header__tab', {
|
|
410
|
+
'testable-test-assertion-editor__header__tab--active':
|
|
411
|
+
tab === selectedTab,
|
|
412
|
+
})}
|
|
413
|
+
>
|
|
414
|
+
{tab === TEST_ASSERTION_TAB.EXPECTED ? 'Expected' : 'Result'}
|
|
415
|
+
</div>
|
|
416
|
+
))}
|
|
417
|
+
</div>
|
|
418
|
+
</div>
|
|
419
|
+
<PanelContent>
|
|
420
|
+
<div className="availability-assertion-editor__body">
|
|
421
|
+
{selectedTab === TEST_ASSERTION_TAB.EXPECTED && (
|
|
422
|
+
<ExpectedJsonEditor testState={testState} />
|
|
423
|
+
)}
|
|
424
|
+
{selectedTab === TEST_ASSERTION_TAB.RESULT &&
|
|
425
|
+
assertionEditorState && (
|
|
426
|
+
<TestAssertionResultViewer
|
|
427
|
+
testAssertionEditorState={assertionEditorState}
|
|
428
|
+
/>
|
|
429
|
+
)}
|
|
430
|
+
</div>
|
|
431
|
+
</PanelContent>
|
|
432
|
+
</div>
|
|
433
|
+
);
|
|
434
|
+
},
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
// ─── Test list item ─────────────────────────────────────────────────────────
|
|
438
|
+
|
|
439
|
+
const AvailabilityTestContextMenu = observer(
|
|
440
|
+
forwardRef<
|
|
441
|
+
HTMLDivElement,
|
|
442
|
+
{
|
|
443
|
+
suiteState: AvailabilityTestSuiteState;
|
|
444
|
+
test: AvailabilityBarrierTest;
|
|
445
|
+
}
|
|
446
|
+
>(function AvailabilityTestContextMenu(props, ref) {
|
|
447
|
+
const { suiteState, test } = props;
|
|
448
|
+
return (
|
|
449
|
+
<MenuContent ref={ref}>
|
|
450
|
+
<MenuContentItem onClick={(): void => suiteState.setTestToRename(test)}>
|
|
451
|
+
Rename
|
|
452
|
+
</MenuContentItem>
|
|
453
|
+
<MenuContentItem onClick={(): void => suiteState.deleteTest(test)}>
|
|
454
|
+
Delete
|
|
455
|
+
</MenuContentItem>
|
|
456
|
+
</MenuContent>
|
|
457
|
+
);
|
|
458
|
+
}),
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
const AvailabilityTestItem = observer(
|
|
462
|
+
(props: {
|
|
463
|
+
suiteState: AvailabilityTestSuiteState;
|
|
464
|
+
testState: AvailabilityTestState;
|
|
465
|
+
}) => {
|
|
466
|
+
const { suiteState, testState } = props;
|
|
467
|
+
const isActive = suiteState.selectTestState === testState;
|
|
468
|
+
const testResult = getTestableResultFromTestResult(
|
|
469
|
+
testState.testResultState.result,
|
|
470
|
+
);
|
|
471
|
+
const icon = getTestableResultIcon(testResult);
|
|
472
|
+
|
|
473
|
+
return (
|
|
474
|
+
<ContextMenu
|
|
475
|
+
className={clsx('testable-test-explorer__item', {
|
|
476
|
+
'testable-test-explorer__item--active': isActive,
|
|
477
|
+
})}
|
|
478
|
+
content={
|
|
479
|
+
<AvailabilityTestContextMenu
|
|
480
|
+
suiteState={suiteState}
|
|
481
|
+
test={testState.test}
|
|
482
|
+
/>
|
|
483
|
+
}
|
|
484
|
+
>
|
|
485
|
+
<div
|
|
486
|
+
className="testable-test-explorer__item__label"
|
|
487
|
+
onClick={(): void => suiteState.changeTest(testState.test)}
|
|
488
|
+
tabIndex={-1}
|
|
489
|
+
>
|
|
490
|
+
<div className="testable-test-explorer__item__label__icon">
|
|
491
|
+
{icon}
|
|
492
|
+
</div>
|
|
493
|
+
<div className="testable-test-explorer__item__label__text">
|
|
494
|
+
{testState.test.id}
|
|
495
|
+
</div>
|
|
496
|
+
<div className="mapping-test-explorer__item__actions">
|
|
497
|
+
<button
|
|
498
|
+
className="mapping-test-explorer__item__action mapping-test-explorer__run-test-btn"
|
|
499
|
+
onClick={(event): void => {
|
|
500
|
+
event.stopPropagation();
|
|
501
|
+
flowResult(testState.runTest()).catch(
|
|
502
|
+
suiteState.editorStore.applicationStore.alertUnhandledError,
|
|
503
|
+
);
|
|
504
|
+
}}
|
|
505
|
+
tabIndex={-1}
|
|
506
|
+
title="Run Test"
|
|
507
|
+
>
|
|
508
|
+
<PlayIcon />
|
|
509
|
+
</button>
|
|
510
|
+
</div>
|
|
511
|
+
</div>
|
|
512
|
+
</ContextMenu>
|
|
513
|
+
);
|
|
514
|
+
},
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
// ─── Tests panel (right side) ───────────────────────────────────────────────
|
|
518
|
+
|
|
519
|
+
const AvailabilityTestsEditor = observer(
|
|
520
|
+
(props: {
|
|
521
|
+
suiteState: AvailabilityTestSuiteState;
|
|
522
|
+
testableState: AvailabilityTestableState;
|
|
523
|
+
}) => {
|
|
524
|
+
const { suiteState, testableState } = props;
|
|
525
|
+
const selectedTestState = suiteState.selectTestState;
|
|
526
|
+
const isReadOnly = testableState.editorState.isReadOnly;
|
|
527
|
+
|
|
528
|
+
return (
|
|
529
|
+
<div className="panel service-test-editor">
|
|
530
|
+
<ResizablePanelGroup orientation="vertical">
|
|
531
|
+
<ResizablePanel minSize={100} size={220}>
|
|
532
|
+
<div className="binding-editor__header">
|
|
533
|
+
<div className="binding-editor__header__title">
|
|
534
|
+
<div className="panel__header__title__content">Tests</div>
|
|
535
|
+
</div>
|
|
536
|
+
<div className="panel__header__actions">
|
|
537
|
+
<button
|
|
538
|
+
className="panel__header__action"
|
|
539
|
+
tabIndex={-1}
|
|
540
|
+
onClick={(): void => {
|
|
541
|
+
flowResult(suiteState.runSuite()).catch(
|
|
542
|
+
suiteState.editorStore.applicationStore
|
|
543
|
+
.alertUnhandledError,
|
|
544
|
+
);
|
|
545
|
+
}}
|
|
546
|
+
disabled={suiteState.suite.tests.length === 0}
|
|
547
|
+
title="Run all tests in this suite"
|
|
548
|
+
>
|
|
549
|
+
<RunAllIcon />
|
|
550
|
+
</button>
|
|
551
|
+
<button
|
|
552
|
+
className="panel__header__action"
|
|
553
|
+
tabIndex={-1}
|
|
554
|
+
onClick={(): void => {
|
|
555
|
+
flowResult(suiteState.runFailingTests()).catch(
|
|
556
|
+
suiteState.editorStore.applicationStore
|
|
557
|
+
.alertUnhandledError,
|
|
558
|
+
);
|
|
559
|
+
}}
|
|
560
|
+
disabled={suiteState.suite.tests.length === 0}
|
|
561
|
+
title="Run failing tests"
|
|
562
|
+
>
|
|
563
|
+
<RunErrorsIcon />
|
|
564
|
+
</button>
|
|
565
|
+
{!isReadOnly && (
|
|
566
|
+
<button
|
|
567
|
+
className="panel__header__action"
|
|
568
|
+
tabIndex={-1}
|
|
569
|
+
onClick={(): void =>
|
|
570
|
+
testableState.setShowCreateTestModal(true)
|
|
571
|
+
}
|
|
572
|
+
title="Add test to this suite"
|
|
573
|
+
>
|
|
574
|
+
<PlusIcon />
|
|
575
|
+
</button>
|
|
576
|
+
)}
|
|
577
|
+
</div>
|
|
578
|
+
</div>
|
|
579
|
+
<div>
|
|
580
|
+
{suiteState.testStates.map((testState) => (
|
|
581
|
+
<AvailabilityTestItem
|
|
582
|
+
key={testState.test.id}
|
|
583
|
+
suiteState={suiteState}
|
|
584
|
+
testState={testState}
|
|
585
|
+
/>
|
|
586
|
+
))}
|
|
587
|
+
</div>
|
|
588
|
+
</ResizablePanel>
|
|
589
|
+
<ResizablePanelSplitter>
|
|
590
|
+
<ResizablePanelSplitterLine color="var(--color-dark-grey-200)" />
|
|
591
|
+
</ResizablePanelSplitter>
|
|
592
|
+
<ResizablePanel minSize={56}>
|
|
593
|
+
{selectedTestState ? (
|
|
594
|
+
<AvailabilityAssertionEditor testState={selectedTestState} />
|
|
595
|
+
) : (
|
|
596
|
+
<BlankPanelPlaceholder
|
|
597
|
+
text="Select a test"
|
|
598
|
+
tooltipText="Select a test from the list above"
|
|
599
|
+
/>
|
|
600
|
+
)}
|
|
601
|
+
</ResizablePanel>
|
|
602
|
+
</ResizablePanelGroup>
|
|
603
|
+
</div>
|
|
604
|
+
);
|
|
605
|
+
},
|
|
606
|
+
);
|
|
607
|
+
|
|
608
|
+
// ─── Suite editor (horizontal split) ────────────────────────────────────────
|
|
609
|
+
|
|
610
|
+
const AvailabilitySuiteEditor = observer(
|
|
611
|
+
(props: {
|
|
612
|
+
suiteState: AvailabilityTestSuiteState;
|
|
613
|
+
testableState: AvailabilityTestableState;
|
|
614
|
+
}) => {
|
|
615
|
+
const { suiteState, testableState } = props;
|
|
616
|
+
const isReadOnly = testableState.editorState.isReadOnly;
|
|
617
|
+
|
|
618
|
+
return (
|
|
619
|
+
<div className="service-test-suite-editor">
|
|
620
|
+
<ResizablePanelGroup orientation="horizontal">
|
|
621
|
+
<ResizablePanel size={520} minSize={28}>
|
|
622
|
+
<div className="panel service-test-data-editor">
|
|
623
|
+
<div className="service-test-data-editor__data">
|
|
624
|
+
<RelationElementEditor
|
|
625
|
+
relationElementState={suiteState.testDataState}
|
|
626
|
+
isReadOnly={isReadOnly}
|
|
627
|
+
hideColumnDefinitions={true}
|
|
628
|
+
/>
|
|
629
|
+
</div>
|
|
630
|
+
</div>
|
|
631
|
+
</ResizablePanel>
|
|
632
|
+
<ResizablePanelSplitter>
|
|
633
|
+
<ResizablePanelSplitterLine color="var(--color-dark-grey-200)" />
|
|
634
|
+
</ResizablePanelSplitter>
|
|
635
|
+
<ResizablePanel minSize={56}>
|
|
636
|
+
<AvailabilityTestsEditor
|
|
637
|
+
suiteState={suiteState}
|
|
638
|
+
testableState={testableState}
|
|
639
|
+
/>
|
|
640
|
+
</ResizablePanel>
|
|
641
|
+
</ResizablePanelGroup>
|
|
642
|
+
</div>
|
|
643
|
+
);
|
|
644
|
+
},
|
|
645
|
+
);
|
|
646
|
+
|
|
647
|
+
// ─── Suite tab context menu ─────────────────────────────────────────────────
|
|
648
|
+
|
|
649
|
+
const AvailabilitySuiteContextMenu = observer(
|
|
650
|
+
forwardRef<
|
|
651
|
+
HTMLDivElement,
|
|
652
|
+
{
|
|
653
|
+
suite: AvailabilityTestSuite;
|
|
654
|
+
testableState: AvailabilityTestableState;
|
|
655
|
+
}
|
|
656
|
+
>(function AvailabilitySuiteContextMenu(props, ref) {
|
|
657
|
+
const { suite, testableState } = props;
|
|
658
|
+
return (
|
|
659
|
+
<MenuContent ref={ref}>
|
|
660
|
+
<MenuContentItem
|
|
661
|
+
onClick={(): void => testableState.setSuiteToRename(suite)}
|
|
662
|
+
>
|
|
663
|
+
Rename
|
|
664
|
+
</MenuContentItem>
|
|
665
|
+
<MenuContentItem onClick={(): void => testableState.deleteSuite(suite)}>
|
|
666
|
+
Delete
|
|
667
|
+
</MenuContentItem>
|
|
668
|
+
</MenuContent>
|
|
669
|
+
);
|
|
670
|
+
}),
|
|
671
|
+
);
|
|
672
|
+
|
|
673
|
+
// ─── Top-level testable editor ──────────────────────────────────────────────
|
|
674
|
+
|
|
675
|
+
export const AvailabilityTestableEditor = observer(
|
|
676
|
+
(props: { testableState: AvailabilityTestableState }) => {
|
|
677
|
+
const { testableState } = props;
|
|
678
|
+
const availability = testableState.availability;
|
|
679
|
+
const selectedSuiteState = testableState.selectedSuiteState;
|
|
680
|
+
const isReadOnly = testableState.editorState.isReadOnly;
|
|
681
|
+
|
|
682
|
+
const renameSuite = (val: string): void => {
|
|
683
|
+
if (testableState.suiteToRename) {
|
|
684
|
+
testSuite_setId(testableState.suiteToRename, val);
|
|
685
|
+
}
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
const renameTest = (val: string): void => {
|
|
689
|
+
const testToRename = testableState.selectedSuiteState?.testToRename;
|
|
690
|
+
if (testToRename) {
|
|
691
|
+
atomicTest_setId(testToRename, val);
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
|
|
695
|
+
const renameAssertion = (val: string): void => {
|
|
696
|
+
const assertionToRename =
|
|
697
|
+
testableState.selectedSuiteState?.selectTestState?.assertionToRename;
|
|
698
|
+
if (assertionToRename) {
|
|
699
|
+
testAssertion_setId(assertionToRename, val);
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
return (
|
|
704
|
+
<Panel className="service-test-suite-editor">
|
|
705
|
+
<PanelHeader>
|
|
706
|
+
{availability.tests.length ? (
|
|
707
|
+
<PanelHeader className="service-test-suite-editor__header service-test-suite-editor__header--with-tabs">
|
|
708
|
+
<div className="uml-element-editor__tabs">
|
|
709
|
+
{availability.tests.map((suite) => {
|
|
710
|
+
const isActive = selectedSuiteState?.suite === suite;
|
|
711
|
+
const suiteResult = selectedSuiteState
|
|
712
|
+
? isActive
|
|
713
|
+
? selectedSuiteState.result
|
|
714
|
+
: TESTABLE_RESULT.DID_NOT_RUN
|
|
715
|
+
: TESTABLE_RESULT.DID_NOT_RUN;
|
|
716
|
+
return (
|
|
717
|
+
<div
|
|
718
|
+
key={suite.id}
|
|
719
|
+
onClick={(): void => testableState.changeSuite(suite)}
|
|
720
|
+
className={clsx('service-test-suite-editor__tab', {
|
|
721
|
+
'service-test-suite-editor__tab--active': isActive,
|
|
722
|
+
})}
|
|
723
|
+
>
|
|
724
|
+
<ContextMenu
|
|
725
|
+
className="mapping-editor__header__tab__content"
|
|
726
|
+
content={
|
|
727
|
+
<AvailabilitySuiteContextMenu
|
|
728
|
+
suite={suite}
|
|
729
|
+
testableState={testableState}
|
|
730
|
+
/>
|
|
731
|
+
}
|
|
732
|
+
>
|
|
733
|
+
<div className="testable-test-explorer__item__result">
|
|
734
|
+
{getTestableResultIcon(suiteResult)}
|
|
735
|
+
</div>
|
|
736
|
+
{suite.id}
|
|
737
|
+
</ContextMenu>
|
|
738
|
+
</div>
|
|
739
|
+
);
|
|
740
|
+
})}
|
|
741
|
+
</div>
|
|
742
|
+
</PanelHeader>
|
|
743
|
+
) : (
|
|
744
|
+
<div></div>
|
|
745
|
+
)}
|
|
746
|
+
|
|
747
|
+
<PanelHeaderActions>
|
|
748
|
+
{!isReadOnly && (
|
|
749
|
+
<PanelHeaderActionItem
|
|
750
|
+
onClick={(): void =>
|
|
751
|
+
testableState.setShowCreateSuiteModal(true)
|
|
752
|
+
}
|
|
753
|
+
title="Add Suite"
|
|
754
|
+
>
|
|
755
|
+
<PlusIcon />
|
|
756
|
+
</PanelHeaderActionItem>
|
|
757
|
+
)}
|
|
758
|
+
</PanelHeaderActions>
|
|
759
|
+
</PanelHeader>
|
|
760
|
+
|
|
761
|
+
<Panel className="service-test-suite-editor">
|
|
762
|
+
{selectedSuiteState ? (
|
|
763
|
+
<AvailabilitySuiteEditor
|
|
764
|
+
suiteState={selectedSuiteState}
|
|
765
|
+
testableState={testableState}
|
|
766
|
+
/>
|
|
767
|
+
) : (
|
|
768
|
+
<BlankPanelPlaceholder
|
|
769
|
+
text="Add Test Suite"
|
|
770
|
+
onClick={(): void => testableState.setShowCreateSuiteModal(true)}
|
|
771
|
+
clickActionType="add"
|
|
772
|
+
tooltipText="Click to add availability test suite"
|
|
773
|
+
/>
|
|
774
|
+
)}
|
|
775
|
+
|
|
776
|
+
{testableState.showCreateSuiteModal && (
|
|
777
|
+
<CreateSuiteModal
|
|
778
|
+
testableState={testableState}
|
|
779
|
+
onClose={(): void => testableState.setShowCreateSuiteModal(false)}
|
|
780
|
+
/>
|
|
781
|
+
)}
|
|
782
|
+
|
|
783
|
+
{testableState.showCreateTestModal && selectedSuiteState && (
|
|
784
|
+
<CreateTestModal
|
|
785
|
+
suiteState={selectedSuiteState}
|
|
786
|
+
onClose={(): void => testableState.setShowCreateTestModal(false)}
|
|
787
|
+
/>
|
|
788
|
+
)}
|
|
789
|
+
|
|
790
|
+
{testableState.suiteToRename && (
|
|
791
|
+
<RenameModal
|
|
792
|
+
val={testableState.suiteToRename.id}
|
|
793
|
+
isReadOnly={isReadOnly}
|
|
794
|
+
showModal={true}
|
|
795
|
+
closeModal={(): void => testableState.setSuiteToRename(undefined)}
|
|
796
|
+
setValue={renameSuite}
|
|
797
|
+
/>
|
|
798
|
+
)}
|
|
799
|
+
|
|
800
|
+
{selectedSuiteState?.testToRename && (
|
|
801
|
+
<RenameModal
|
|
802
|
+
val={selectedSuiteState.testToRename.id}
|
|
803
|
+
isReadOnly={isReadOnly}
|
|
804
|
+
showModal={true}
|
|
805
|
+
closeModal={(): void =>
|
|
806
|
+
selectedSuiteState.setTestToRename(undefined)
|
|
807
|
+
}
|
|
808
|
+
setValue={renameTest}
|
|
809
|
+
/>
|
|
810
|
+
)}
|
|
811
|
+
|
|
812
|
+
{selectedSuiteState?.selectTestState?.assertionToRename && (
|
|
813
|
+
<RenameModal
|
|
814
|
+
val={selectedSuiteState.selectTestState.assertionToRename.id}
|
|
815
|
+
isReadOnly={isReadOnly}
|
|
816
|
+
showModal={true}
|
|
817
|
+
closeModal={(): void =>
|
|
818
|
+
selectedSuiteState.selectTestState?.setAssertionToRename(
|
|
819
|
+
undefined,
|
|
820
|
+
)
|
|
821
|
+
}
|
|
822
|
+
setValue={renameAssertion}
|
|
823
|
+
/>
|
|
824
|
+
)}
|
|
825
|
+
</Panel>
|
|
826
|
+
</Panel>
|
|
827
|
+
);
|
|
828
|
+
},
|
|
829
|
+
);
|