@qoretechnologies/reqraft 0.10.53 → 0.10.54-pr.115.g9bc96ed
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/.claude/CLAUDE.md +25 -0
- package/.tasks/INDEX.md +1 -0
- package/.tasks/STORY_NETWORK_ISOLATION.md +98 -0
- package/README.md +1 -1
- package/design/FORM_ENGINE_EXPRESSIONS.md +110 -0
- package/design/FORM_ENGINE_FIELD_MIGRATION.md +56 -1
- package/design/RENDER_EXPRESSION_TRANSPORT.md +76 -10
- package/design/SMART_EDITOR_UX.md +21 -0
- package/dist/components/dpqlEditor/DpqlEditor.d.ts.map +1 -1
- package/dist/components/dpqlEditor/DpqlEditor.js +3 -3
- package/dist/components/dpqlEditor/DpqlEditor.js.map +1 -1
- package/dist/components/dpqlEditor/dpqlHelpers.d.ts +6 -0
- package/dist/components/dpqlEditor/dpqlHelpers.d.ts.map +1 -1
- package/dist/components/dpqlEditor/dpqlHelpers.js +21 -2
- package/dist/components/dpqlEditor/dpqlHelpers.js.map +1 -1
- package/dist/components/dpqlEditor/dpqlProbe.d.ts +37 -0
- package/dist/components/dpqlEditor/dpqlProbe.d.ts.map +1 -0
- package/dist/components/dpqlEditor/dpqlProbe.js +203 -0
- package/dist/components/dpqlEditor/dpqlProbe.js.map +1 -0
- package/dist/components/dpqlEditor/dpqlTags.d.ts +8 -0
- package/dist/components/dpqlEditor/dpqlTags.d.ts.map +1 -1
- package/dist/components/dpqlEditor/dpqlTags.js +5 -1
- package/dist/components/dpqlEditor/dpqlTags.js.map +1 -1
- package/dist/components/dpqlEditor/index.d.ts +4 -0
- package/dist/components/dpqlEditor/index.d.ts.map +1 -1
- package/dist/components/dpqlEditor/index.js +5 -1
- package/dist/components/dpqlEditor/index.js.map +1 -1
- package/dist/components/dpqlEditor/types.d.ts +42 -2
- package/dist/components/dpqlEditor/types.d.ts.map +1 -1
- package/dist/components/dpqlEditor/types.js +0 -4
- package/dist/components/dpqlEditor/types.js.map +1 -1
- package/dist/components/dpqlEditor/useDpqlProbe.d.ts +4 -0
- package/dist/components/dpqlEditor/useDpqlProbe.d.ts.map +1 -0
- package/dist/components/dpqlEditor/useDpqlProbe.js +29 -0
- package/dist/components/dpqlEditor/useDpqlProbe.js.map +1 -0
- package/dist/components/dpqlEditor/useDpqlSession.d.ts +5 -2
- package/dist/components/dpqlEditor/useDpqlSession.d.ts.map +1 -1
- package/dist/components/dpqlEditor/useDpqlSession.js +8 -2
- package/dist/components/dpqlEditor/useDpqlSession.js.map +1 -1
- package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
- package/dist/components/form/engine/CompactRow.js +192 -45
- package/dist/components/form/engine/CompactRow.js.map +1 -1
- package/dist/components/form/engine/CompactToolbar.d.ts.map +1 -1
- package/dist/components/form/engine/CompactToolbar.js +5 -2
- package/dist/components/form/engine/CompactToolbar.js.map +1 -1
- package/dist/components/form/engine/FormEngine.d.ts +160 -12
- package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
- package/dist/components/form/engine/FormEngine.js +376 -75
- package/dist/components/form/engine/FormEngine.js.map +1 -1
- package/dist/components/form/engine/FormFieldsSkeleton.d.ts +48 -0
- package/dist/components/form/engine/FormFieldsSkeleton.d.ts.map +1 -0
- package/dist/components/form/engine/FormFieldsSkeleton.js +38 -0
- package/dist/components/form/engine/FormFieldsSkeleton.js.map +1 -0
- package/dist/components/form/engine/OptionFieldMessages.d.ts +17 -2
- package/dist/components/form/engine/OptionFieldMessages.d.ts.map +1 -1
- package/dist/components/form/engine/OptionFieldMessages.js +61 -11
- package/dist/components/form/engine/OptionFieldMessages.js.map +1 -1
- package/dist/components/form/engine/_structuredData/SchemaDataView.d.ts.map +1 -1
- package/dist/components/form/engine/_structuredData/SchemaDataView.js +7 -1
- package/dist/components/form/engine/_structuredData/SchemaDataView.js.map +1 -1
- package/dist/components/form/engine/compactToolbarContext.d.ts +1 -0
- package/dist/components/form/engine/compactToolbarContext.d.ts.map +1 -1
- package/dist/components/form/engine/readFirst.d.ts +42 -1
- package/dist/components/form/engine/readFirst.d.ts.map +1 -1
- package/dist/components/form/engine/readFirst.js +74 -7
- package/dist/components/form/engine/readFirst.js.map +1 -1
- package/dist/components/form/engine/rendererTypes.d.ts +13 -0
- package/dist/components/form/engine/rendererTypes.d.ts.map +1 -1
- package/dist/components/form/engine/rendererTypes.js.map +1 -1
- package/dist/components/form/engine/rowMenuContext.d.ts +78 -0
- package/dist/components/form/engine/rowMenuContext.d.ts.map +1 -0
- package/dist/components/form/engine/rowMenuContext.js +103 -0
- package/dist/components/form/engine/rowMenuContext.js.map +1 -0
- package/dist/components/form/engine/typeChoices.d.ts +37 -0
- package/dist/components/form/engine/typeChoices.d.ts.map +1 -0
- package/dist/components/form/engine/typeChoices.js +25 -0
- package/dist/components/form/engine/typeChoices.js.map +1 -0
- package/dist/components/form/expressions/DpqlRendering.d.ts +26 -0
- package/dist/components/form/expressions/DpqlRendering.d.ts.map +1 -0
- package/dist/components/form/expressions/DpqlRendering.js +53 -0
- package/dist/components/form/expressions/DpqlRendering.js.map +1 -0
- package/dist/components/form/expressions/ExpressionField.d.ts +7 -1
- package/dist/components/form/expressions/ExpressionField.d.ts.map +1 -1
- package/dist/components/form/expressions/ExpressionField.js +202 -45
- package/dist/components/form/expressions/ExpressionField.js.map +1 -1
- package/dist/components/form/expressions/argumentPresence.d.ts +97 -0
- package/dist/components/form/expressions/argumentPresence.d.ts.map +1 -0
- package/dist/components/form/expressions/argumentPresence.js +203 -0
- package/dist/components/form/expressions/argumentPresence.js.map +1 -0
- package/dist/components/form/expressions/builder/argumentLabel.d.ts.map +1 -1
- package/dist/components/form/expressions/builder/argumentLabel.js +9 -4
- package/dist/components/form/expressions/builder/argumentLabel.js.map +1 -1
- package/dist/components/form/expressions/builder/index.d.ts +15 -2
- package/dist/components/form/expressions/builder/index.d.ts.map +1 -1
- package/dist/components/form/expressions/builder/index.js +20 -58
- package/dist/components/form/expressions/builder/index.js.map +1 -1
- package/dist/components/form/expressions/builder/renderTemplate.d.ts +4 -1
- package/dist/components/form/expressions/builder/renderTemplate.d.ts.map +1 -1
- package/dist/components/form/expressions/builder/renderTemplate.js +10 -67
- package/dist/components/form/expressions/builder/renderTemplate.js.map +1 -1
- package/dist/components/form/expressions/dpqlMockLanguage.d.ts +76 -0
- package/dist/components/form/expressions/dpqlMockLanguage.d.ts.map +1 -0
- package/dist/components/form/expressions/dpqlMockLanguage.js +525 -0
- package/dist/components/form/expressions/dpqlMockLanguage.js.map +1 -0
- package/dist/components/form/expressions/dpqlMockLsp.d.ts +31 -2
- package/dist/components/form/expressions/dpqlMockLsp.d.ts.map +1 -1
- package/dist/components/form/expressions/dpqlMockLsp.js +86 -80
- package/dist/components/form/expressions/dpqlMockLsp.js.map +1 -1
- package/dist/components/form/expressions/mockExpressions.d.ts.map +1 -1
- package/dist/components/form/expressions/mockExpressions.js +8 -4
- package/dist/components/form/expressions/mockExpressions.js.map +1 -1
- package/dist/components/form/expressions/renderExpressionToText.d.ts +1 -1
- package/dist/components/form/expressions/renderExpressionToText.d.ts.map +1 -1
- package/dist/components/form/expressions/renderExpressionToText.js +95 -19
- package/dist/components/form/expressions/renderExpressionToText.js.map +1 -1
- package/dist/components/form/expressions/types.d.ts +7 -0
- package/dist/components/form/expressions/types.d.ts.map +1 -1
- package/dist/components/form/expressions/useRenderExpression.d.ts +7 -16
- package/dist/components/form/expressions/useRenderExpression.d.ts.map +1 -1
- package/dist/components/form/expressions/useRenderExpression.js +50 -126
- package/dist/components/form/expressions/useRenderExpression.js.map +1 -1
- package/dist/components/form/fields/Field.d.ts.map +1 -1
- package/dist/components/form/fields/Field.js +3 -1
- package/dist/components/form/fields/Field.js.map +1 -1
- package/dist/components/form/fields/allowed-values/AllowedValues.d.ts +11 -1
- package/dist/components/form/fields/allowed-values/AllowedValues.d.ts.map +1 -1
- package/dist/components/form/fields/allowed-values/AllowedValues.js +14 -2
- package/dist/components/form/fields/allowed-values/AllowedValues.js.map +1 -1
- package/dist/components/form/fields/auto/AutoFormField.d.ts.map +1 -1
- package/dist/components/form/fields/auto/AutoFormField.js +32 -8
- package/dist/components/form/fields/auto/AutoFormField.js.map +1 -1
- package/dist/components/form/fields/date/Date.d.ts +1 -1
- package/dist/components/form/fields/date/Date.d.ts.map +1 -1
- package/dist/components/form/fields/date/Date.js +16 -2
- package/dist/components/form/fields/date/Date.js.map +1 -1
- package/dist/components/form/fields/rich-text/RichText.d.ts +21 -1
- package/dist/components/form/fields/rich-text/RichText.d.ts.map +1 -1
- package/dist/components/form/fields/rich-text/RichText.js +140 -8
- package/dist/components/form/fields/rich-text/RichText.js.map +1 -1
- package/dist/components/form/fields/template/ReadOnlyTemplateTag.d.ts +2 -1
- package/dist/components/form/fields/template/ReadOnlyTemplateTag.d.ts.map +1 -1
- package/dist/components/form/fields/template/ReadOnlyTemplateTag.js +3 -2
- package/dist/components/form/fields/template/ReadOnlyTemplateTag.js.map +1 -1
- package/dist/components/form/fields/template/TemplateField.d.ts +31 -1
- package/dist/components/form/fields/template/TemplateField.d.ts.map +1 -1
- package/dist/components/form/fields/template/TemplateField.js +528 -50
- package/dist/components/form/fields/template/TemplateField.js.map +1 -1
- package/dist/components/form/index.d.ts +1 -0
- package/dist/components/form/index.d.ts.map +1 -1
- package/dist/components/form/index.js +1 -0
- package/dist/components/form/index.js.map +1 -1
- package/dist/components/smartEditor/SmartEditor.d.ts.map +1 -1
- package/dist/components/smartEditor/SmartEditor.js +29 -8
- package/dist/components/smartEditor/SmartEditor.js.map +1 -1
- package/dist/components/smartEditor/__fixtures__/mockLspServer.d.ts +8 -0
- package/dist/components/smartEditor/__fixtures__/mockLspServer.d.ts.map +1 -1
- package/dist/components/smartEditor/__fixtures__/mockLspServer.js +53 -10
- package/dist/components/smartEditor/__fixtures__/mockLspServer.js.map +1 -1
- package/dist/components/smartEditor/useLspSession.d.ts +5 -1
- package/dist/components/smartEditor/useLspSession.d.ts.map +1 -1
- package/dist/components/smartEditor/useLspSession.js +16 -2
- package/dist/components/smartEditor/useLspSession.js.map +1 -1
- package/dist/helpers/dpqlDetection.d.ts +58 -0
- package/dist/helpers/dpqlDetection.d.ts.map +1 -0
- package/dist/helpers/dpqlDetection.js +164 -0
- package/dist/helpers/dpqlDetection.js.map +1 -0
- package/dist/helpers/optionUiTypes.d.ts +3 -3
- package/dist/helpers/options.d.ts +37 -0
- package/dist/helpers/options.d.ts.map +1 -1
- package/dist/helpers/options.js +78 -1
- package/dist/helpers/options.js.map +1 -1
- package/dist/helpers/templateItems.d.ts +47 -0
- package/dist/helpers/templateItems.d.ts.map +1 -0
- package/dist/helpers/templateItems.js +88 -0
- package/dist/helpers/templateItems.js.map +1 -0
- package/dist/helpers/templateText.d.ts +37 -0
- package/dist/helpers/templateText.d.ts.map +1 -0
- package/dist/helpers/templateText.js +64 -0
- package/dist/helpers/templateText.js.map +1 -0
- package/dist/helpers/templates.d.ts +19 -11
- package/dist/helpers/templates.d.ts.map +1 -1
- package/dist/helpers/templates.js +46 -29
- package/dist/helpers/templates.js.map +1 -1
- package/dist/helpers/validations.d.ts.map +1 -1
- package/dist/helpers/validations.js +56 -9
- package/dist/helpers/validations.js.map +1 -1
- package/dist/hooks/useArgSchema.d.ts +2 -1
- package/dist/hooks/useArgSchema.d.ts.map +1 -1
- package/dist/hooks/useFetch/useFetch.d.ts +3 -1
- package/dist/hooks/useFetch/useFetch.d.ts.map +1 -1
- package/dist/hooks/useFetch/useFetch.js +47 -9
- package/dist/hooks/useFetch/useFetch.js.map +1 -1
- package/dist/hooks/useTemplates.d.ts +20 -0
- package/dist/hooks/useTemplates.d.ts.map +1 -1
- package/dist/hooks/useTemplates.js +34 -2
- package/dist/hooks/useTemplates.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/stories/storyNetwork.d.ts +36 -0
- package/dist/stories/storyNetwork.d.ts.map +1 -0
- package/dist/stories/storyNetwork.js +82 -0
- package/dist/stories/storyNetwork.js.map +1 -0
- package/dist/utils/fetch.d.ts +151 -1
- package/dist/utils/fetch.d.ts.map +1 -1
- package/dist/utils/fetch.js +256 -44
- package/dist/utils/fetch.js.map +1 -1
- package/dist/utils/lspClient.d.ts +6 -0
- package/dist/utils/lspClient.d.ts.map +1 -1
- package/dist/utils/lspClient.js +62 -11
- package/dist/utils/lspClient.js.map +1 -1
- package/dist/utils/websocket.d.ts +6 -0
- package/dist/utils/websocket.d.ts.map +1 -1
- package/dist/utils/websocket.js +15 -7
- package/dist/utils/websocket.js.map +1 -1
- package/package.json +2 -2
- package/src/components/dpqlEditor/DpqlEditor.stories.tsx +3 -119
- package/src/components/dpqlEditor/DpqlEditor.tsx +5 -2
- package/src/components/dpqlEditor/dpqlHelpers.ts +20 -1
- package/src/components/dpqlEditor/dpqlProbe.ts +165 -0
- package/src/components/dpqlEditor/dpqlTags.ts +13 -0
- package/src/components/dpqlEditor/index.ts +4 -0
- package/src/components/dpqlEditor/types.ts +38 -2
- package/src/components/dpqlEditor/useDpqlProbe.ts +33 -0
- package/src/components/dpqlEditor/useDpqlSession.ts +20 -4
- package/src/components/form/engine/CompactRow.tsx +240 -51
- package/src/components/form/engine/CompactToolbar.tsx +10 -3
- package/src/components/form/engine/FormEngine.stories.tsx +423 -120
- package/src/components/form/engine/FormEngine.tsx +504 -87
- package/src/components/form/engine/FormFieldsSkeleton.tsx +76 -0
- package/src/components/form/engine/OptionFieldMessages.tsx +89 -11
- package/src/components/form/engine/_structuredData/SchemaDataView.tsx +11 -0
- package/src/components/form/engine/compactToolbarContext.ts +1 -1
- package/src/components/form/engine/readFirst.ts +86 -7
- package/src/components/form/engine/rendererTypes.ts +12 -0
- package/src/components/form/engine/rowMenuContext.ts +159 -0
- package/src/components/form/engine/typeChoices.ts +55 -0
- package/src/components/form/expressions/DpqlRendering.tsx +85 -0
- package/src/components/form/expressions/ExpressionField.stories.tsx +826 -48
- package/src/components/form/expressions/ExpressionField.tsx +265 -54
- package/src/components/form/expressions/argumentPresence.ts +200 -0
- package/src/components/form/expressions/builder/ExpressionBuilder.stories.tsx +20 -4
- package/src/components/form/expressions/builder/argumentLabel.tsx +10 -4
- package/src/components/form/expressions/builder/index.tsx +39 -77
- package/src/components/form/expressions/builder/renderTemplate.tsx +23 -56
- package/src/components/form/expressions/dpqlMockLanguage.ts +565 -0
- package/src/components/form/expressions/dpqlMockLsp.ts +109 -83
- package/src/components/form/expressions/mockExpressions.ts +8 -4
- package/src/components/form/expressions/renderExpressionToText.ts +112 -22
- package/src/components/form/expressions/types.ts +7 -0
- package/src/components/form/expressions/useRenderExpression.ts +43 -127
- package/src/components/form/fields/Field.tsx +3 -2
- package/src/components/form/fields/allowed-values/AllowedValues.tsx +13 -2
- package/src/components/form/fields/auto/AutoFormField.stories.tsx +27 -20
- package/src/components/form/fields/auto/AutoFormField.tsx +33 -9
- package/src/components/form/fields/date/Date.tsx +17 -1
- package/src/components/form/fields/rich-text/RichText.tsx +199 -22
- package/src/components/form/fields/template/ReadOnlyTemplateTag.tsx +4 -2
- package/src/components/form/fields/template/TemplateField.stories.tsx +31 -9
- package/src/components/form/fields/template/TemplateField.tsx +714 -122
- package/src/components/form/index.tsx +1 -0
- package/src/components/log/Log.stories.tsx +2 -1
- package/src/components/smartEditor/SmartEditor.tsx +41 -9
- package/src/components/smartEditor/__fixtures__/mockLspServer.ts +52 -3
- package/src/components/smartEditor/useLspSession.ts +21 -3
- package/src/helpers/dpqlDetection.ts +209 -0
- package/src/helpers/options.ts +70 -0
- package/src/helpers/templateItems.ts +81 -0
- package/src/helpers/templateText.ts +92 -0
- package/src/helpers/templates.ts +47 -37
- package/src/helpers/validations.ts +59 -9
- package/src/hooks/useFetch/useFetch.stories.tsx +6 -5
- package/src/hooks/useFetch/useFetch.tsx +53 -7
- package/src/hooks/useStorage/useStorage.stories.tsx +2 -1
- package/src/hooks/useTemplates.ts +41 -0
- package/src/hooks/useWebSocket/useWebsocket.stories.tsx +2 -1
- package/src/index.tsx +14 -1
- package/src/stores/currentUser/currentUser.stories.tsx +2 -1
- package/src/stories/storyNetwork.ts +92 -0
- package/src/utils/fetch.ts +311 -21
- package/src/utils/lspClient.ts +68 -11
- package/src/utils/websocket.ts +20 -9
|
@@ -5,7 +5,6 @@ import { Meta, StoryObj } from '@storybook/react-vite';
|
|
|
5
5
|
import { ChangeEvent, useEffect, useMemo, useState } from 'react';
|
|
6
6
|
import { expect, fireEvent, fn, userEvent, waitFor, within } from 'storybook/test';
|
|
7
7
|
import { validateField } from '../../../helpers/validations';
|
|
8
|
-
import { defaultQorusTypes } from '../../../hooks/useQorusTypes';
|
|
9
8
|
import {
|
|
10
9
|
_testsChangeRichText,
|
|
11
10
|
_testsChangeStringField,
|
|
@@ -19,7 +18,6 @@ import {
|
|
|
19
18
|
_testsWaitForTextToNotExist,
|
|
20
19
|
sleep,
|
|
21
20
|
} from '../../../stories/Tests/utils';
|
|
22
|
-
import { startDpqlMockLsp } from '../expressions/dpqlMockLsp';
|
|
23
21
|
import { mockExpressions } from '../expressions/mockExpressions';
|
|
24
22
|
import { mockPopulatedDefinition } from '../fields/schema-definition/mockDefinition';
|
|
25
23
|
import { defaultMarkdownRenderer } from '../fields/markdown/MarkdownView';
|
|
@@ -92,29 +90,9 @@ const meta: Meta<typeof FormEngine> = {
|
|
|
92
90
|
chromatic: {
|
|
93
91
|
viewports: [2560],
|
|
94
92
|
},
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
// request fires anyway — which made `Option With Any Type` pass locally (401 → built-in
|
|
99
|
-
// list, so "Boolean" exists) and fail in CI, where the token is valid and the server's
|
|
100
|
-
// list decides the labels. Pin the catalogue so the type names the plays click are ours.
|
|
101
|
-
mockData: [
|
|
102
|
-
{
|
|
103
|
-
// `query()` builds `${instance}api/latest/${url}`, and the hook's url is
|
|
104
|
-
// `/system/qorus-type-info` — hence the doubled slash. Both spellings are listed
|
|
105
|
-
// so the mock keeps matching if that leading slash is ever dropped.
|
|
106
|
-
url: 'https://hq.qoretechnologies.com:8092/api/latest//system/qorus-type-info',
|
|
107
|
-
method: 'GET',
|
|
108
|
-
status: 200,
|
|
109
|
-
response: defaultQorusTypes,
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
url: 'https://hq.qoretechnologies.com:8092/api/latest/system/qorus-type-info',
|
|
113
|
-
method: 'GET',
|
|
114
|
-
status: 200,
|
|
115
|
-
response: defaultQorusTypes,
|
|
116
|
-
},
|
|
117
|
-
],
|
|
93
|
+
// The type catalogue the plays click through (`Option With Any Type` picks
|
|
94
|
+
// "Boolean") is pinned for every story by the global mocks in
|
|
95
|
+
// `src/stories/storyNetwork.ts`.
|
|
118
96
|
},
|
|
119
97
|
render: ({ value, onChange, ...rest }: IFormEngineProps) => {
|
|
120
98
|
const [val, setValue] = useState(value);
|
|
@@ -191,11 +169,17 @@ export const Basic: Story = {
|
|
|
191
169
|
value: basicFormValue,
|
|
192
170
|
},
|
|
193
171
|
play: async ({ canvasElement, args }) => {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
await waitFor(
|
|
197
|
-
|
|
198
|
-
|
|
172
|
+
// The template option's `$local:test` reads as the name the form's
|
|
173
|
+
// templates give it, as a chip in its editor.
|
|
174
|
+
await waitFor(
|
|
175
|
+
() =>
|
|
176
|
+
expect(
|
|
177
|
+
Array.from(canvasElement.querySelectorAll('[contenteditable="true"] .reqore-tag')).some((chip) =>
|
|
178
|
+
chip.textContent?.includes('Test (local)')
|
|
179
|
+
)
|
|
180
|
+
).toBe(true),
|
|
181
|
+
{ timeout: 10000 }
|
|
182
|
+
);
|
|
199
183
|
await waitFor(
|
|
200
184
|
() =>
|
|
201
185
|
expect(
|
|
@@ -510,11 +494,11 @@ export const OptionDependsOnOptionOrAnotherOption: Story = {
|
|
|
510
494
|
});
|
|
511
495
|
|
|
512
496
|
await _testsWaitForText(
|
|
513
|
-
'This field is disabled because some dependencies are not fulfilled: "Required Option 2"
|
|
497
|
+
'This field is disabled because some dependencies are not fulfilled: ("Required Option 2" or "Required Option 5")'
|
|
514
498
|
);
|
|
515
499
|
await _testsChangeRichText('I have value', 5);
|
|
516
500
|
await _testsWaitForTextToNotExist(
|
|
517
|
-
'This field is disabled because some dependencies are not fulfilled: "Required Option 2"
|
|
501
|
+
'This field is disabled because some dependencies are not fulfilled: ("Required Option 2" or "Required Option 5")'
|
|
518
502
|
);
|
|
519
503
|
},
|
|
520
504
|
};
|
|
@@ -4364,7 +4348,7 @@ const FieldTypeCatalogGroups: Record<string, IFormEngineGroup> = {
|
|
|
4364
4348
|
* summary of the `{exp,args}` AST (`renderExpressionToText`); drilling in opens
|
|
4365
4349
|
* the full `ExpressionField` in the card — the Visual builder (catalogue offline
|
|
4366
4350
|
* via `mockExpressions`) plus the Text/DPQL editor, whose seed + Explain are
|
|
4367
|
-
* served by the
|
|
4351
|
+
* served by the story language server every story gets. No instance required.
|
|
4368
4352
|
*/
|
|
4369
4353
|
export const CompactExpressions: Story = {
|
|
4370
4354
|
// chromatic off: ends with the live ExpressionField editor (Text mode) open.
|
|
@@ -4404,65 +4388,61 @@ export const CompactExpressions: Story = {
|
|
|
4404
4388
|
} as unknown as IOptions,
|
|
4405
4389
|
},
|
|
4406
4390
|
play: async () => {
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
const chip = Array.from(document.querySelectorAll('.reqore-tag')).find((tag) =>
|
|
4414
|
-
tag.textContent?.includes('$local:name')
|
|
4415
|
-
);
|
|
4416
|
-
expect(chip, 'the reference in the summary renders as a chip').toBeTruthy();
|
|
4417
|
-
});
|
|
4418
|
-
await _testsWaitForText('" == "John"');
|
|
4419
|
-
|
|
4420
|
-
// Drill in → the card hosts the ExpressionField (Visual builder).
|
|
4421
|
-
await fireEvent.click(
|
|
4422
|
-
document.querySelector('.readfirst-row[data-field="condition"]') as HTMLElement
|
|
4423
|
-
);
|
|
4424
|
-
await waitFor(
|
|
4425
|
-
() =>
|
|
4426
|
-
expect(
|
|
4427
|
-
document.querySelector('.options-readfirst-card[data-field="condition"] .expression')
|
|
4428
|
-
).toBeInTheDocument(),
|
|
4429
|
-
{ timeout: 10000 }
|
|
4391
|
+
// Read-first: the summary of the AST reads as DPQL, through the same
|
|
4392
|
+
// rendering the Explain panel and Preview use — the template reference a
|
|
4393
|
+
// chip rather than its raw `$local:name` token, the literal as the text it is.
|
|
4394
|
+
await waitFor(() => {
|
|
4395
|
+
const summary = document.querySelector(
|
|
4396
|
+
'.readfirst-row[data-field="condition"] .dpql-rendering'
|
|
4430
4397
|
);
|
|
4431
|
-
|
|
4432
|
-
|
|
4398
|
+
expect(summary, 'the collapsed row renders its summary as DPQL').toBeTruthy();
|
|
4399
|
+
expect(summary?.textContent).toContain('local: name');
|
|
4400
|
+
expect(summary?.textContent).toContain('== "John"');
|
|
4401
|
+
});
|
|
4433
4402
|
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
()
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
).toBeInTheDocument(),
|
|
4448
|
-
{ timeout: 10000 }
|
|
4449
|
-
);
|
|
4403
|
+
// Drill in → the card hosts the ExpressionField (Visual builder).
|
|
4404
|
+
await fireEvent.click(
|
|
4405
|
+
document.querySelector('.readfirst-row[data-field="condition"]') as HTMLElement
|
|
4406
|
+
);
|
|
4407
|
+
await waitFor(
|
|
4408
|
+
() =>
|
|
4409
|
+
expect(
|
|
4410
|
+
document.querySelector('.options-readfirst-card[data-field="condition"] .expression')
|
|
4411
|
+
).toBeInTheDocument(),
|
|
4412
|
+
{ timeout: 10000 }
|
|
4413
|
+
);
|
|
4414
|
+
// The builder resolves the operator from the offline catalogue.
|
|
4415
|
+
await _testsWaitForText('Logical Equals');
|
|
4450
4416
|
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4417
|
+
// Text/DPQL tab — seeded from the AST via the mock LSP (dpql/serialize).
|
|
4418
|
+
const textBtn = Array.from(
|
|
4419
|
+
document.querySelectorAll(
|
|
4420
|
+
'.options-readfirst-card[data-field="condition"] .expression-field button'
|
|
4421
|
+
)
|
|
4422
|
+
).find((b) => b.textContent?.trim() === 'Text') as HTMLElement;
|
|
4423
|
+
await fireEvent.click(textBtn);
|
|
4424
|
+
await waitFor(
|
|
4425
|
+
() =>
|
|
4426
|
+
expect(
|
|
4427
|
+
document.querySelector(
|
|
4428
|
+
'.options-readfirst-card[data-field="condition"] [data-testid="expression-preview"]'
|
|
4429
|
+
)
|
|
4430
|
+
).toBeInTheDocument(),
|
|
4431
|
+
{ timeout: 10000 }
|
|
4432
|
+
);
|
|
4433
|
+
|
|
4434
|
+
// The "Parsed" line is the single live rendering of the AST (over the
|
|
4435
|
+
// mock dpql/renderExpression) — it reflects the seeded expression. The
|
|
4436
|
+
// separate Text-mode "Explain" button was dropped; Parsed is canonical.
|
|
4437
|
+
await waitFor(
|
|
4438
|
+
() =>
|
|
4439
|
+
expect(
|
|
4440
|
+
document.querySelector(
|
|
4441
|
+
'.options-readfirst-card[data-field="condition"] [data-testid="expression-preview"]'
|
|
4442
|
+
)?.textContent
|
|
4443
|
+
).toContain('John'),
|
|
4444
|
+
{ timeout: 10000 }
|
|
4445
|
+
);
|
|
4466
4446
|
},
|
|
4467
4447
|
};
|
|
4468
4448
|
|
|
@@ -5242,15 +5222,18 @@ export const CompactFieldTypes: Story = {
|
|
|
5242
5222
|
// than a raw hash key-count.
|
|
5243
5223
|
await _testsWaitForText('512MiB');
|
|
5244
5224
|
await _testsWaitForText(/example_customer_addresses/);
|
|
5245
|
-
// Expression field → read-first shows the offline DPQL summary of the AST
|
|
5246
|
-
// with the template reference inside it chipped rather than
|
|
5225
|
+
// Expression field → read-first shows the offline DPQL summary of the AST
|
|
5226
|
+
// as DPQL, with the template reference inside it chipped rather than
|
|
5227
|
+
// printed raw.
|
|
5247
5228
|
await waitFor(() => {
|
|
5248
|
-
const
|
|
5249
|
-
|
|
5229
|
+
const summary = document.querySelector('.readfirst-row[data-field="expr"] .dpql-rendering');
|
|
5230
|
+
const chip = Array.from(summary?.querySelectorAll('.reqore-tag') ?? []).find((tag) =>
|
|
5231
|
+
tag.textContent?.includes('name')
|
|
5250
5232
|
);
|
|
5251
5233
|
expect(chip, 'the reference in the summary renders as a chip').toBeTruthy();
|
|
5234
|
+
expect(summary?.textContent).toContain('== "John"');
|
|
5235
|
+
expect(summary?.textContent).not.toContain('$local:name');
|
|
5252
5236
|
});
|
|
5253
|
-
await _testsWaitForText('" == "John"');
|
|
5254
5237
|
await expect(document.querySelectorAll('.options-readfirst-card')).toHaveLength(0);
|
|
5255
5238
|
|
|
5256
5239
|
// A hash renders its read-first preview (the structured tree by default)
|
|
@@ -7179,10 +7162,10 @@ export const CompactListOfHashReadsInSchemaWordsMobile: Story = {
|
|
|
7179
7162
|
};
|
|
7180
7163
|
|
|
7181
7164
|
/**
|
|
7182
|
-
* The read-first summary of an EXPRESSION: it
|
|
7183
|
-
*
|
|
7184
|
-
*
|
|
7185
|
-
*
|
|
7165
|
+
* The read-first summary of an EXPRESSION: it reads as DPQL, and a template
|
|
7166
|
+
* reference inside it used to print as its own raw token — the same reference
|
|
7167
|
+
* the editor shows as a named chip. This is the Save Reply row of the Discord
|
|
7168
|
+
* assistant template, whose value is
|
|
7186
7169
|
* `trim($data:{dc_ai_reply.choices[0].message.content})`.
|
|
7187
7170
|
*/
|
|
7188
7171
|
export const CompactExpressionTemplateChips: Story = {
|
|
@@ -7190,7 +7173,7 @@ export const CompactExpressionTemplateChips: Story = {
|
|
|
7190
7173
|
docs: {
|
|
7191
7174
|
description: {
|
|
7192
7175
|
story:
|
|
7193
|
-
'Renders a compact read-first row whose value is an expression wrapping a template reference — the
|
|
7176
|
+
'Renders a compact read-first row whose value is an expression wrapping a template reference — the summary reads as DPQL and the reference inside it is a chip named from the form\'s template catalogue, trim(Choices[0].message.content), rather than its raw $data token or its path.',
|
|
7194
7177
|
},
|
|
7195
7178
|
},
|
|
7196
7179
|
chromatic: { disable: true },
|
|
@@ -7241,20 +7224,20 @@ export const CompactExpressionTemplateChips: Story = {
|
|
|
7241
7224
|
onChange={fn()}
|
|
7242
7225
|
/>
|
|
7243
7226
|
),
|
|
7244
|
-
play: async () => {
|
|
7245
|
-
// The summary keeps its literal text…
|
|
7246
|
-
await _testsWaitForText('trim("', undefined, 1);
|
|
7247
|
-
// …and the reference inside it is a named chip, not the raw token.
|
|
7227
|
+
play: async ({ canvasElement }) => {
|
|
7248
7228
|
await waitFor(() => {
|
|
7249
|
-
const
|
|
7229
|
+
const summary = canvasElement.querySelector('.readfirst-row .dpql-rendering');
|
|
7230
|
+
// The summary keeps its literal text…
|
|
7231
|
+
expect(summary?.textContent, 'the row summarises the expression as DPQL').toContain('trim(');
|
|
7232
|
+
// …and the reference inside it is a chip named from the catalogue.
|
|
7233
|
+
const chip = Array.from(summary?.querySelectorAll('.reqore-tag') ?? []).find((tag) =>
|
|
7250
7234
|
tag.textContent?.includes('Choices[0].message.content')
|
|
7251
7235
|
);
|
|
7252
7236
|
expect(chip, 'the reference renders as a named chip in the summary').toBeTruthy();
|
|
7237
|
+
// Named, so neither the raw token nor the path it resolves.
|
|
7238
|
+
expect(summary?.textContent).not.toContain('$data:{');
|
|
7239
|
+
expect(summary?.textContent).not.toContain('dc_ai_reply');
|
|
7253
7240
|
});
|
|
7254
|
-
const raw = Array.from(document.querySelectorAll('.readfirst-row')).filter((row) =>
|
|
7255
|
-
row.textContent?.includes('$data:{')
|
|
7256
|
-
);
|
|
7257
|
-
expect(raw, 'the row prints no raw token').toHaveLength(0);
|
|
7258
7241
|
},
|
|
7259
7242
|
};
|
|
7260
7243
|
|
|
@@ -7269,7 +7252,7 @@ export const CompactExpressionWithoutCatalogue: Story = {
|
|
|
7269
7252
|
docs: {
|
|
7270
7253
|
description: {
|
|
7271
7254
|
story:
|
|
7272
|
-
'Renders a compact read-first row whose value is an expression wrapping a template reference, with no templates supplied — the reference still renders as a chip carrying its own path (dc_ai_reply.choices[0].message.content) rather than the raw $data token.',
|
|
7255
|
+
'Renders a compact read-first row whose value is an expression wrapping a template reference, with no templates supplied — the summary reads as DPQL and the reference still renders as a chip carrying its own path (data: dc_ai_reply.choices[0].message.content) rather than the raw $data token.',
|
|
7273
7256
|
},
|
|
7274
7257
|
},
|
|
7275
7258
|
chromatic: { disable: true },
|
|
@@ -7304,18 +7287,16 @@ export const CompactExpressionWithoutCatalogue: Story = {
|
|
|
7304
7287
|
onChange={fn()}
|
|
7305
7288
|
/>
|
|
7306
7289
|
),
|
|
7307
|
-
play: async () => {
|
|
7308
|
-
await _testsWaitForText('trim("', undefined, 1);
|
|
7290
|
+
play: async ({ canvasElement }) => {
|
|
7309
7291
|
await waitFor(() => {
|
|
7310
|
-
const
|
|
7292
|
+
const summary = canvasElement.querySelector('.readfirst-row .dpql-rendering');
|
|
7293
|
+
expect(summary?.textContent, 'the row summarises the expression as DPQL').toContain('trim(');
|
|
7294
|
+
const chip = Array.from(summary?.querySelectorAll('.reqore-tag') ?? []).find((tag) =>
|
|
7311
7295
|
tag.textContent?.includes('dc_ai_reply.choices[0].message.content')
|
|
7312
7296
|
);
|
|
7313
7297
|
expect(chip, 'the reference chips even with no catalogue').toBeTruthy();
|
|
7298
|
+
expect(summary?.textContent, 'the row prints no raw token').not.toContain('$data:{');
|
|
7314
7299
|
});
|
|
7315
|
-
const raw = Array.from(document.querySelectorAll('.readfirst-row')).filter((row) =>
|
|
7316
|
-
row.textContent?.includes('$data:{')
|
|
7317
|
-
);
|
|
7318
|
-
expect(raw, 'the row prints no raw token').toHaveLength(0);
|
|
7319
7300
|
},
|
|
7320
7301
|
};
|
|
7321
7302
|
|
|
@@ -7469,12 +7450,12 @@ export const CompactToolbarTrimmed: Story = {
|
|
|
7469
7450
|
},
|
|
7470
7451
|
};
|
|
7471
7452
|
|
|
7472
|
-
export const
|
|
7453
|
+
export const CompactLoneOptionalStaysOpenByDefault: Story = {
|
|
7473
7454
|
parameters: {
|
|
7474
7455
|
docs: {
|
|
7475
7456
|
description: {
|
|
7476
7457
|
story:
|
|
7477
|
-
|
|
7458
|
+
'Renders an all-optional compact form with NO `compactCollapsedGroups` — the Optional box is the whole form, so it stays open. Collapsing the only box would render a titled card containing one collapsed strip and nothing else, which reads as broken rather than as tidy. This is the default half of the contract; its pair below is what an explicit collapse list changes.',
|
|
7478
7459
|
},
|
|
7479
7460
|
},
|
|
7480
7461
|
},
|
|
@@ -7486,16 +7467,42 @@ export const CompactCollapseListLeavesLoneOptionalOpen: Story = {
|
|
|
7486
7467
|
beta: { type: 'string', ui_type: 'string', display_name: 'Beta' },
|
|
7487
7468
|
} as unknown as IOptionsSchema,
|
|
7488
7469
|
value: {} as IOptions,
|
|
7489
|
-
compactCollapsedGroups: ['optional'],
|
|
7490
7470
|
},
|
|
7491
7471
|
play: async () => {
|
|
7492
|
-
//
|
|
7472
|
+
// No collapse list was passed, so the invariant holds and the rows are on
|
|
7473
|
+
// screen with no click.
|
|
7493
7474
|
await expectBoxOpen('Optional');
|
|
7494
7475
|
await _testsWaitForText('Alpha');
|
|
7495
7476
|
await _testsWaitForText('Beta');
|
|
7496
7477
|
},
|
|
7497
7478
|
};
|
|
7498
7479
|
|
|
7480
|
+
export const CompactCollapseListCollapsesLoneOptional: Story = {
|
|
7481
|
+
parameters: {
|
|
7482
|
+
docs: {
|
|
7483
|
+
description: {
|
|
7484
|
+
story:
|
|
7485
|
+
"Renders the same all-optional form with an explicit `compactCollapsedGroups={['optional']}` — and this time the Optional box IS collapsed. Passing the prop waives the \"never collapse the sole box\" invariant: it is a default, not a law. A host that supplies its own heading and explanation around the form — a run-options panel headed \"Change a setting for this run\", say — has already told the reader what the card is, and has asked for the settings to start folded away. The default (the story above) is unchanged for every caller that says nothing.",
|
|
7486
|
+
},
|
|
7487
|
+
},
|
|
7488
|
+
},
|
|
7489
|
+
args: {
|
|
7490
|
+
compact: true,
|
|
7491
|
+
minColumnWidth: '300px',
|
|
7492
|
+
options: {
|
|
7493
|
+
alpha: { type: 'string', ui_type: 'string', display_name: 'Alpha' },
|
|
7494
|
+
beta: { type: 'string', ui_type: 'string', display_name: 'Beta' },
|
|
7495
|
+
} as unknown as IOptionsSchema,
|
|
7496
|
+
value: {} as IOptions,
|
|
7497
|
+
compactCollapsedGroups: ['optional'],
|
|
7498
|
+
},
|
|
7499
|
+
play: async () => {
|
|
7500
|
+
// The caller asked for it folded, so it is folded even though it is the
|
|
7501
|
+
// only box: `ReqorePanel` unmounts collapsed content, so the rows are gone.
|
|
7502
|
+
await expectBoxCollapsed('Optional');
|
|
7503
|
+
},
|
|
7504
|
+
};
|
|
7505
|
+
|
|
7499
7506
|
export const CompactNoSearchCollapseIsFinal: Story = {
|
|
7500
7507
|
parameters: {
|
|
7501
7508
|
docs: {
|
|
@@ -7831,3 +7838,299 @@ export const CompactNoMaxFieldsShown: Story = {
|
|
|
7831
7838
|
expect(canvasElement.querySelector('.readfirst-more-row')).toBeNull();
|
|
7832
7839
|
},
|
|
7833
7840
|
};
|
|
7841
|
+
|
|
7842
|
+
/**
|
|
7843
|
+
* A test's cases read as chips on the collapsed row.
|
|
7844
|
+
*
|
|
7845
|
+
* `test-cases` is a renderer-only `ui_type`, so it reached none of the typed
|
|
7846
|
+
* branches in `renderReadFirstValue` and fell through to the generic summary,
|
|
7847
|
+
* which flattens the array of case hashes to `"case_1, resolved_expected_values"`.
|
|
7848
|
+
* The table the row opens already draws each name as a chip, so the collapsed
|
|
7849
|
+
* row was the only surface still printing an identifier as prose.
|
|
7850
|
+
*
|
|
7851
|
+
* The fall-throughs are the point of the other two fields: chips are all-or-
|
|
7852
|
+
* nothing, because a partly-chipped row reads as though the unnamed cases were
|
|
7853
|
+
* missing entirely.
|
|
7854
|
+
*/
|
|
7855
|
+
export const CompactRowTestCaseChips: Story = {
|
|
7856
|
+
parameters: {
|
|
7857
|
+
docs: {
|
|
7858
|
+
description: {
|
|
7859
|
+
story:
|
|
7860
|
+
'Renders a collapsed `test-cases` row: every case name is a chip, matching the cases table the row opens. A value whose entries are not all named, and an empty value, keep the plain summary instead — chips are all-or-nothing.',
|
|
7861
|
+
},
|
|
7862
|
+
},
|
|
7863
|
+
},
|
|
7864
|
+
args: {
|
|
7865
|
+
compact: true,
|
|
7866
|
+
minColumnWidth: '360px',
|
|
7867
|
+
value: {
|
|
7868
|
+
cases: {
|
|
7869
|
+
type: 'list',
|
|
7870
|
+
value: [
|
|
7871
|
+
{ name: 'case_1', desc: 'the happy path' },
|
|
7872
|
+
{ name: 'resolved_expected_values', desc: 'reference resolution' },
|
|
7873
|
+
],
|
|
7874
|
+
},
|
|
7875
|
+
mixed: {
|
|
7876
|
+
type: 'list',
|
|
7877
|
+
value: [{ name: 'case_1' }, { desc: 'a case that has no name yet' }],
|
|
7878
|
+
},
|
|
7879
|
+
none: { type: 'list', value: [] },
|
|
7880
|
+
},
|
|
7881
|
+
// Cast for the same reason the markdown/cron/dpql stories need one: a
|
|
7882
|
+
// renderer-only `ui_type` names an EDITOR, and ts-toolkit's `TQorusType`
|
|
7883
|
+
// enumerates STORAGE types, so it has no member for one.
|
|
7884
|
+
options: {
|
|
7885
|
+
cases: { type: 'list', ui_type: 'test-cases', display_name: 'Cases' },
|
|
7886
|
+
mixed: { type: 'list', ui_type: 'test-cases', display_name: 'Mixed' },
|
|
7887
|
+
none: { type: 'list', ui_type: 'test-cases', display_name: 'None' },
|
|
7888
|
+
} as never,
|
|
7889
|
+
},
|
|
7890
|
+
play: async ({ canvasElement }) => {
|
|
7891
|
+
// (a) Every case name is a chip, in order.
|
|
7892
|
+
await waitFor(
|
|
7893
|
+
() => {
|
|
7894
|
+
const chips = canvasElement.querySelectorAll(
|
|
7895
|
+
'[data-field="cases"] .options-readfirst-case-tag'
|
|
7896
|
+
);
|
|
7897
|
+
expect(chips.length).toBe(2);
|
|
7898
|
+
expect(Array.from(chips).map((chip) => chip.textContent?.trim())).toEqual([
|
|
7899
|
+
'case_1',
|
|
7900
|
+
'resolved_expected_values',
|
|
7901
|
+
]);
|
|
7902
|
+
},
|
|
7903
|
+
{ timeout: 5000 }
|
|
7904
|
+
);
|
|
7905
|
+
|
|
7906
|
+
// (b) …and the comma-joined prose they replace is gone. Asserting the text
|
|
7907
|
+
// rather than only the chip count: the first cut rendered the chips
|
|
7908
|
+
// BESIDE the summary, which is the same identifiers twice.
|
|
7909
|
+
const casesText =
|
|
7910
|
+
canvasElement
|
|
7911
|
+
.querySelector('[data-field="cases"] .options-readfirst-valuetext')
|
|
7912
|
+
?.textContent?.trim() ?? '';
|
|
7913
|
+
expect(casesText).not.toContain('case_1,');
|
|
7914
|
+
|
|
7915
|
+
// (c) NEGATIVE: one entry has no name, so no entry is chipped — a
|
|
7916
|
+
// half-chipped row would read as though that case were missing.
|
|
7917
|
+
const mixedRow = canvasElement.querySelector('[data-field="mixed"]');
|
|
7918
|
+
expect(mixedRow?.querySelectorAll('.options-readfirst-case-tag').length).toBe(0);
|
|
7919
|
+
|
|
7920
|
+
// (d) NEGATIVE: an empty value is not a SET value, so it never reaches
|
|
7921
|
+
// this branch at all — its row sits in the collapsed optional group and
|
|
7922
|
+
// is not in the DOM. Counting canvas-wide says the same thing more
|
|
7923
|
+
// strongly than probing for a row that does not exist: the only chips
|
|
7924
|
+
// on the whole form are the two named cases, so neither the unnamed nor
|
|
7925
|
+
// the empty field contributed one.
|
|
7926
|
+
expect(canvasElement.querySelectorAll('.options-readfirst-case-tag').length).toBe(2);
|
|
7927
|
+
},
|
|
7928
|
+
};
|
|
7929
|
+
|
|
7930
|
+
/**
|
|
7931
|
+
* Clear-value is offered only when there is something to clear.
|
|
7932
|
+
*
|
|
7933
|
+
* `[]` is neither `undefined`, `null` nor `''`, so a list field whose last item
|
|
7934
|
+
* had just been removed went on showing the red clear button, and confirming it
|
|
7935
|
+
* cleared nothing. It is visible wherever a list field stays open — a test's
|
|
7936
|
+
* cases table sits above exactly such a button once its last case is deleted.
|
|
7937
|
+
*
|
|
7938
|
+
* Both fields here are lists with the same schema; only the value differs, so
|
|
7939
|
+
* the button's presence is decided by the value alone.
|
|
7940
|
+
*/
|
|
7941
|
+
export const ClearValueOnlyWhenThereIsAValue: Story = {
|
|
7942
|
+
parameters: {
|
|
7943
|
+
docs: {
|
|
7944
|
+
description: {
|
|
7945
|
+
story:
|
|
7946
|
+
'A list field with items offers Clear value; the same field with an empty list does not, because there is nothing to clear.',
|
|
7947
|
+
},
|
|
7948
|
+
},
|
|
7949
|
+
},
|
|
7950
|
+
args: {
|
|
7951
|
+
compact: true,
|
|
7952
|
+
minColumnWidth: '360px',
|
|
7953
|
+
/* Both rows OPEN, because that is where this button lives. A collapsed list
|
|
7954
|
+
row shows no clear at all — the inline one is only for editors with no
|
|
7955
|
+
clear of their own (bools, fixed-choice pickers) — so the button under
|
|
7956
|
+
test is the expanded card's, which is what a consumer that opens a field
|
|
7957
|
+
by default puts permanently on screen. */
|
|
7958
|
+
initialExpandedOptions: ['filled', 'emptied'],
|
|
7959
|
+
value: {
|
|
7960
|
+
filled: { type: 'list', value: ['alpha', 'beta'] },
|
|
7961
|
+
emptied: { type: 'list', value: [] },
|
|
7962
|
+
},
|
|
7963
|
+
options: {
|
|
7964
|
+
/* Both REQUIRED, like a test's `cases`. An unset optional field folds
|
|
7965
|
+
into the collapsed Optional box and is not in the DOM at all, so an
|
|
7966
|
+
empty list only stays on screen — where this button can be misclicked —
|
|
7967
|
+
when the field is one the form insists on. */
|
|
7968
|
+
filled: {
|
|
7969
|
+
type: 'list',
|
|
7970
|
+
ui_type: 'list',
|
|
7971
|
+
element_type: 'string',
|
|
7972
|
+
display_name: 'Filled',
|
|
7973
|
+
required: true,
|
|
7974
|
+
},
|
|
7975
|
+
emptied: {
|
|
7976
|
+
type: 'list',
|
|
7977
|
+
ui_type: 'list',
|
|
7978
|
+
element_type: 'string',
|
|
7979
|
+
display_name: 'Emptied',
|
|
7980
|
+
required: true,
|
|
7981
|
+
},
|
|
7982
|
+
} as never,
|
|
7983
|
+
},
|
|
7984
|
+
play: async ({ canvasElement }) => {
|
|
7985
|
+
await waitFor(
|
|
7986
|
+
() => {
|
|
7987
|
+
expect(canvasElement.querySelector('[data-field="filled"]')).toBeTruthy();
|
|
7988
|
+
},
|
|
7989
|
+
{ timeout: 5000 }
|
|
7990
|
+
);
|
|
7991
|
+
|
|
7992
|
+
// (a) items present -> the clear button is offered
|
|
7993
|
+
const filled = canvasElement.querySelector('[data-field="filled"]');
|
|
7994
|
+
expect(filled?.querySelectorAll('.options-readfirst-clear').length).toBe(1);
|
|
7995
|
+
|
|
7996
|
+
// (b) empty list -> nothing to clear, so no button and no dialog to reach
|
|
7997
|
+
const emptied = canvasElement.querySelector('[data-field="emptied"]');
|
|
7998
|
+
expect(emptied).toBeTruthy();
|
|
7999
|
+
expect(emptied?.querySelectorAll('.options-readfirst-clear').length).toBe(0);
|
|
8000
|
+
},
|
|
8001
|
+
};
|
|
8002
|
+
|
|
8003
|
+
/**
|
|
8004
|
+
* A creatable picker is ONE control, not two.
|
|
8005
|
+
*
|
|
8006
|
+
* `rendersCreatableValueSelect` exists so that exactly one of the two renderers
|
|
8007
|
+
* draws the value: the chip picker holds it and offers the candidates, and the
|
|
8008
|
+
* raw editor stands down. It asked for `type === 'string'` — but a picker field
|
|
8009
|
+
* declares a storage type (`*string`) AND a `ui_type` (`select-string`), and by
|
|
8010
|
+
* the time the schema arrives the two are flattened to `type: 'select-string'`.
|
|
8011
|
+
* So neither renderer stood down: the picker drew the value, and the
|
|
8012
|
+
* saved-and-suggested list drew the same candidates again beneath it.
|
|
8013
|
+
*
|
|
8014
|
+
* It showed on a test's service-method step, where Service and Method offered
|
|
8015
|
+
* 183 and 7 values twice over in two controls that set one value each.
|
|
8016
|
+
*/
|
|
8017
|
+
export const CreatablePickerIsOneControl: Story = {
|
|
8018
|
+
parameters: {
|
|
8019
|
+
docs: {
|
|
8020
|
+
description: {
|
|
8021
|
+
story:
|
|
8022
|
+
'A creatable select-string with candidates renders a single picker that both holds the value and offers the list — not a picker plus a second "Saved & Suggested Values" control listing the same items.',
|
|
8023
|
+
},
|
|
8024
|
+
},
|
|
8025
|
+
},
|
|
8026
|
+
args: {
|
|
8027
|
+
compact: true,
|
|
8028
|
+
minColumnWidth: '360px',
|
|
8029
|
+
initialExpandedOptions: ['method'],
|
|
8030
|
+
value: { method: { type: 'select-string', value: 'init' } },
|
|
8031
|
+
options: {
|
|
8032
|
+
method: {
|
|
8033
|
+
type: 'select-string',
|
|
8034
|
+
ui_type: 'select-string',
|
|
8035
|
+
display_name: 'Method',
|
|
8036
|
+
required: true,
|
|
8037
|
+
allowed_values_creatable: true,
|
|
8038
|
+
allowed_values: [
|
|
8039
|
+
{ display_name: 'init', value: { type: 'string', value: 'init' } },
|
|
8040
|
+
{ display_name: 'start', value: { type: 'string', value: 'start' } },
|
|
8041
|
+
{ display_name: 'stop', value: { type: 'string', value: 'stop' } },
|
|
8042
|
+
],
|
|
8043
|
+
},
|
|
8044
|
+
} as never,
|
|
8045
|
+
},
|
|
8046
|
+
play: async ({ canvasElement }) => {
|
|
8047
|
+
const row = await waitFor(
|
|
8048
|
+
() => {
|
|
8049
|
+
const el = canvasElement.querySelector('[data-field="method"]');
|
|
8050
|
+
expect(el).toBeTruthy();
|
|
8051
|
+
return el as HTMLElement;
|
|
8052
|
+
},
|
|
8053
|
+
{ timeout: 5000 }
|
|
8054
|
+
);
|
|
8055
|
+
|
|
8056
|
+
// The value is still shown and still editable — one control, not none.
|
|
8057
|
+
expect(row.textContent).toContain('init');
|
|
8058
|
+
|
|
8059
|
+
// …and the second way to set the same field is gone. Asserting the label
|
|
8060
|
+
// rather than a count: it is what the duplicate control announces itself as,
|
|
8061
|
+
// and what a reader saw twice.
|
|
8062
|
+
expect(row.textContent).not.toContain('Saved & Suggested Values');
|
|
8063
|
+
},
|
|
8064
|
+
};
|
|
8065
|
+
|
|
8066
|
+
/**
|
|
8067
|
+
* A chosen template hovers ONCE, and says what the value is.
|
|
8068
|
+
*
|
|
8069
|
+
* Two defects met on the same row. The chip carries its own popover and the row
|
|
8070
|
+
* carried a native `title` as well, so hovering fired both — in two places, and
|
|
8071
|
+
* only the popover appears in a screenshot, which is what makes the pair hard to
|
|
8072
|
+
* report. `showsTemplateChips` had already closed this for prose with templates
|
|
8073
|
+
* embedded in it; a value that IS one template was left open.
|
|
8074
|
+
*
|
|
8075
|
+
* And both of them showed the reference — `$._case.title` — which is the
|
|
8076
|
+
* grammar's business, not the author's. The catalogue already carries what they
|
|
8077
|
+
* want: the entry's name and the prose describing it. The reference stays in the
|
|
8078
|
+
* field's own help and in the picker, where it is genuinely needed.
|
|
8079
|
+
*/
|
|
8080
|
+
export const TemplateValueHoversOnceAndDescribesItself: Story = {
|
|
8081
|
+
parameters: {
|
|
8082
|
+
docs: {
|
|
8083
|
+
description: {
|
|
8084
|
+
story:
|
|
8085
|
+
'A row whose value is a chosen template shows one hover — the chip’s own — carrying the value’s name and description rather than the raw reference, and the row adds no second browser tooltip.',
|
|
8086
|
+
},
|
|
8087
|
+
},
|
|
8088
|
+
},
|
|
8089
|
+
args: {
|
|
8090
|
+
compact: true,
|
|
8091
|
+
minColumnWidth: '360px',
|
|
8092
|
+
value: { expected: { type: 'string', value: '$._case.title' } },
|
|
8093
|
+
options: {
|
|
8094
|
+
expected: {
|
|
8095
|
+
type: 'string',
|
|
8096
|
+
display_name: 'Expected Value',
|
|
8097
|
+
supports_templates: true,
|
|
8098
|
+
templates: {
|
|
8099
|
+
items: [
|
|
8100
|
+
{
|
|
8101
|
+
label: 'Values this case captures',
|
|
8102
|
+
items: [
|
|
8103
|
+
{
|
|
8104
|
+
value: '$._case.title',
|
|
8105
|
+
label: 'title (this case)',
|
|
8106
|
+
description: 'A short line naming what this case verifies. (string)',
|
|
8107
|
+
},
|
|
8108
|
+
],
|
|
8109
|
+
},
|
|
8110
|
+
],
|
|
8111
|
+
},
|
|
8112
|
+
},
|
|
8113
|
+
} as never,
|
|
8114
|
+
},
|
|
8115
|
+
play: async ({ canvasElement }) => {
|
|
8116
|
+
const row = await waitFor(
|
|
8117
|
+
() => {
|
|
8118
|
+
const el = canvasElement.querySelector('[data-field="expected"]');
|
|
8119
|
+
expect(el).toBeTruthy();
|
|
8120
|
+
return el as HTMLElement;
|
|
8121
|
+
},
|
|
8122
|
+
{ timeout: 5000 }
|
|
8123
|
+
);
|
|
8124
|
+
|
|
8125
|
+
// (a) The row draws the friendly name, not the reference.
|
|
8126
|
+
expect(row.textContent).toContain('title (this case)');
|
|
8127
|
+
|
|
8128
|
+
// (b) ONE hover on the value. The row must not add a native `title`
|
|
8129
|
+
// alongside the chip's own popover — the label keeps its own, which is
|
|
8130
|
+
// a different element and a different thing to say.
|
|
8131
|
+
const nativeTitles = Array.from(row.querySelectorAll('[title]')).map((el) =>
|
|
8132
|
+
el.getAttribute('title')
|
|
8133
|
+
);
|
|
8134
|
+
expect(nativeTitles).not.toContain('$._case.title');
|
|
8135
|
+
},
|
|
8136
|
+
};
|