@qoretechnologies/reqraft 0.10.53 → 0.10.54-pr.115.ge2d364c
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/RENDER_EXPRESSION_TRANSPORT.md +18 -0
- 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/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 +29 -2
- package/dist/components/dpqlEditor/types.d.ts.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 +186 -21
- 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 +159 -11
- 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/ExpressionField.d.ts +7 -1
- package/dist/components/form/expressions/ExpressionField.d.ts.map +1 -1
- package/dist/components/form/expressions/ExpressionField.js +181 -33
- 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 +19 -57
- package/dist/components/form/expressions/builder/index.js.map +1 -1
- package/dist/components/form/expressions/dpqlMockLsp.d.ts +17 -0
- package/dist/components/form/expressions/dpqlMockLsp.d.ts.map +1 -1
- package/dist/components/form/expressions/dpqlMockLsp.js +75 -9
- package/dist/components/form/expressions/dpqlMockLsp.js.map +1 -1
- package/dist/components/form/expressions/renderExpressionToText.d.ts.map +1 -1
- package/dist/components/form/expressions/renderExpressionToText.js +94 -19
- package/dist/components/form/expressions/renderExpressionToText.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 +13 -2
- package/dist/components/form/fields/auto/AutoFormField.js.map +1 -1
- package/dist/components/form/fields/rich-text/RichText.d.ts +11 -0
- package/dist/components/form/fields/rich-text/RichText.d.ts.map +1 -1
- package/dist/components/form/fields/rich-text/RichText.js +84 -5
- 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 +507 -40
- 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/__fixtures__/mockLspServer.d.ts +1 -0
- package/dist/components/smartEditor/__fixtures__/mockLspServer.d.ts.map +1 -1
- package/dist/components/smartEditor/__fixtures__/mockLspServer.js +6 -1
- package/dist/components/smartEditor/__fixtures__/mockLspServer.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 +1 -1
- 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/templates.d.ts +21 -0
- package/dist/helpers/templates.d.ts.map +1 -1
- package/dist/helpers/templates.js +49 -3
- 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 +0 -2
- package/src/components/dpqlEditor/dpqlProbe.ts +165 -0
- package/src/components/dpqlEditor/index.ts +4 -0
- package/src/components/dpqlEditor/types.ts +26 -2
- package/src/components/dpqlEditor/useDpqlProbe.ts +33 -0
- package/src/components/dpqlEditor/useDpqlSession.ts +20 -4
- package/src/components/form/engine/CompactRow.tsx +231 -11
- package/src/components/form/engine/CompactToolbar.tsx +10 -3
- package/src/components/form/engine/FormEngine.stories.tsx +331 -30
- 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/ExpressionField.stories.tsx +828 -31
- package/src/components/form/expressions/ExpressionField.tsx +234 -29
- package/src/components/form/expressions/argumentPresence.ts +200 -0
- package/src/components/form/expressions/builder/argumentLabel.tsx +10 -4
- package/src/components/form/expressions/builder/index.tsx +31 -75
- package/src/components/form/expressions/dpqlMockLsp.ts +81 -1
- package/src/components/form/expressions/renderExpressionToText.ts +106 -17
- 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.tsx +12 -2
- package/src/components/form/fields/rich-text/RichText.tsx +112 -19
- package/src/components/form/fields/template/ReadOnlyTemplateTag.tsx +4 -2
- package/src/components/form/fields/template/TemplateField.tsx +667 -95
- package/src/components/form/index.tsx +1 -0
- package/src/components/log/Log.stories.tsx +2 -1
- package/src/components/smartEditor/__fixtures__/mockLspServer.ts +6 -1
- package/src/helpers/dpqlDetection.ts +209 -0
- package/src/helpers/options.ts +70 -0
- package/src/helpers/templateItems.ts +81 -0
- package/src/helpers/templates.ts +51 -2
- 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,
|
|
@@ -92,29 +91,9 @@ const meta: Meta<typeof FormEngine> = {
|
|
|
92
91
|
chromatic: {
|
|
93
92
|
viewports: [2560],
|
|
94
93
|
},
|
|
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
|
-
],
|
|
94
|
+
// The type catalogue the plays click through (`Option With Any Type` picks
|
|
95
|
+
// "Boolean") is pinned for every story by the global mocks in
|
|
96
|
+
// `src/stories/storyNetwork.ts`.
|
|
118
97
|
},
|
|
119
98
|
render: ({ value, onChange, ...rest }: IFormEngineProps) => {
|
|
120
99
|
const [val, setValue] = useState(value);
|
|
@@ -510,11 +489,11 @@ export const OptionDependsOnOptionOrAnotherOption: Story = {
|
|
|
510
489
|
});
|
|
511
490
|
|
|
512
491
|
await _testsWaitForText(
|
|
513
|
-
'This field is disabled because some dependencies are not fulfilled: "Required Option 2"
|
|
492
|
+
'This field is disabled because some dependencies are not fulfilled: ("Required Option 2" or "Required Option 5")'
|
|
514
493
|
);
|
|
515
494
|
await _testsChangeRichText('I have value', 5);
|
|
516
495
|
await _testsWaitForTextToNotExist(
|
|
517
|
-
'This field is disabled because some dependencies are not fulfilled: "Required Option 2"
|
|
496
|
+
'This field is disabled because some dependencies are not fulfilled: ("Required Option 2" or "Required Option 5")'
|
|
518
497
|
);
|
|
519
498
|
},
|
|
520
499
|
};
|
|
@@ -7469,12 +7448,12 @@ export const CompactToolbarTrimmed: Story = {
|
|
|
7469
7448
|
},
|
|
7470
7449
|
};
|
|
7471
7450
|
|
|
7472
|
-
export const
|
|
7451
|
+
export const CompactLoneOptionalStaysOpenByDefault: Story = {
|
|
7473
7452
|
parameters: {
|
|
7474
7453
|
docs: {
|
|
7475
7454
|
description: {
|
|
7476
7455
|
story:
|
|
7477
|
-
|
|
7456
|
+
'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
7457
|
},
|
|
7479
7458
|
},
|
|
7480
7459
|
},
|
|
@@ -7486,16 +7465,42 @@ export const CompactCollapseListLeavesLoneOptionalOpen: Story = {
|
|
|
7486
7465
|
beta: { type: 'string', ui_type: 'string', display_name: 'Beta' },
|
|
7487
7466
|
} as unknown as IOptionsSchema,
|
|
7488
7467
|
value: {} as IOptions,
|
|
7489
|
-
compactCollapsedGroups: ['optional'],
|
|
7490
7468
|
},
|
|
7491
7469
|
play: async () => {
|
|
7492
|
-
//
|
|
7470
|
+
// No collapse list was passed, so the invariant holds and the rows are on
|
|
7471
|
+
// screen with no click.
|
|
7493
7472
|
await expectBoxOpen('Optional');
|
|
7494
7473
|
await _testsWaitForText('Alpha');
|
|
7495
7474
|
await _testsWaitForText('Beta');
|
|
7496
7475
|
},
|
|
7497
7476
|
};
|
|
7498
7477
|
|
|
7478
|
+
export const CompactCollapseListCollapsesLoneOptional: Story = {
|
|
7479
|
+
parameters: {
|
|
7480
|
+
docs: {
|
|
7481
|
+
description: {
|
|
7482
|
+
story:
|
|
7483
|
+
"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.",
|
|
7484
|
+
},
|
|
7485
|
+
},
|
|
7486
|
+
},
|
|
7487
|
+
args: {
|
|
7488
|
+
compact: true,
|
|
7489
|
+
minColumnWidth: '300px',
|
|
7490
|
+
options: {
|
|
7491
|
+
alpha: { type: 'string', ui_type: 'string', display_name: 'Alpha' },
|
|
7492
|
+
beta: { type: 'string', ui_type: 'string', display_name: 'Beta' },
|
|
7493
|
+
} as unknown as IOptionsSchema,
|
|
7494
|
+
value: {} as IOptions,
|
|
7495
|
+
compactCollapsedGroups: ['optional'],
|
|
7496
|
+
},
|
|
7497
|
+
play: async () => {
|
|
7498
|
+
// The caller asked for it folded, so it is folded even though it is the
|
|
7499
|
+
// only box: `ReqorePanel` unmounts collapsed content, so the rows are gone.
|
|
7500
|
+
await expectBoxCollapsed('Optional');
|
|
7501
|
+
},
|
|
7502
|
+
};
|
|
7503
|
+
|
|
7499
7504
|
export const CompactNoSearchCollapseIsFinal: Story = {
|
|
7500
7505
|
parameters: {
|
|
7501
7506
|
docs: {
|
|
@@ -7831,3 +7836,299 @@ export const CompactNoMaxFieldsShown: Story = {
|
|
|
7831
7836
|
expect(canvasElement.querySelector('.readfirst-more-row')).toBeNull();
|
|
7832
7837
|
},
|
|
7833
7838
|
};
|
|
7839
|
+
|
|
7840
|
+
/**
|
|
7841
|
+
* A test's cases read as chips on the collapsed row.
|
|
7842
|
+
*
|
|
7843
|
+
* `test-cases` is a renderer-only `ui_type`, so it reached none of the typed
|
|
7844
|
+
* branches in `renderReadFirstValue` and fell through to the generic summary,
|
|
7845
|
+
* which flattens the array of case hashes to `"case_1, resolved_expected_values"`.
|
|
7846
|
+
* The table the row opens already draws each name as a chip, so the collapsed
|
|
7847
|
+
* row was the only surface still printing an identifier as prose.
|
|
7848
|
+
*
|
|
7849
|
+
* The fall-throughs are the point of the other two fields: chips are all-or-
|
|
7850
|
+
* nothing, because a partly-chipped row reads as though the unnamed cases were
|
|
7851
|
+
* missing entirely.
|
|
7852
|
+
*/
|
|
7853
|
+
export const CompactRowTestCaseChips: Story = {
|
|
7854
|
+
parameters: {
|
|
7855
|
+
docs: {
|
|
7856
|
+
description: {
|
|
7857
|
+
story:
|
|
7858
|
+
'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.',
|
|
7859
|
+
},
|
|
7860
|
+
},
|
|
7861
|
+
},
|
|
7862
|
+
args: {
|
|
7863
|
+
compact: true,
|
|
7864
|
+
minColumnWidth: '360px',
|
|
7865
|
+
value: {
|
|
7866
|
+
cases: {
|
|
7867
|
+
type: 'list',
|
|
7868
|
+
value: [
|
|
7869
|
+
{ name: 'case_1', desc: 'the happy path' },
|
|
7870
|
+
{ name: 'resolved_expected_values', desc: 'reference resolution' },
|
|
7871
|
+
],
|
|
7872
|
+
},
|
|
7873
|
+
mixed: {
|
|
7874
|
+
type: 'list',
|
|
7875
|
+
value: [{ name: 'case_1' }, { desc: 'a case that has no name yet' }],
|
|
7876
|
+
},
|
|
7877
|
+
none: { type: 'list', value: [] },
|
|
7878
|
+
},
|
|
7879
|
+
// Cast for the same reason the markdown/cron/dpql stories need one: a
|
|
7880
|
+
// renderer-only `ui_type` names an EDITOR, and ts-toolkit's `TQorusType`
|
|
7881
|
+
// enumerates STORAGE types, so it has no member for one.
|
|
7882
|
+
options: {
|
|
7883
|
+
cases: { type: 'list', ui_type: 'test-cases', display_name: 'Cases' },
|
|
7884
|
+
mixed: { type: 'list', ui_type: 'test-cases', display_name: 'Mixed' },
|
|
7885
|
+
none: { type: 'list', ui_type: 'test-cases', display_name: 'None' },
|
|
7886
|
+
} as never,
|
|
7887
|
+
},
|
|
7888
|
+
play: async ({ canvasElement }) => {
|
|
7889
|
+
// (a) Every case name is a chip, in order.
|
|
7890
|
+
await waitFor(
|
|
7891
|
+
() => {
|
|
7892
|
+
const chips = canvasElement.querySelectorAll(
|
|
7893
|
+
'[data-field="cases"] .options-readfirst-case-tag'
|
|
7894
|
+
);
|
|
7895
|
+
expect(chips.length).toBe(2);
|
|
7896
|
+
expect(Array.from(chips).map((chip) => chip.textContent?.trim())).toEqual([
|
|
7897
|
+
'case_1',
|
|
7898
|
+
'resolved_expected_values',
|
|
7899
|
+
]);
|
|
7900
|
+
},
|
|
7901
|
+
{ timeout: 5000 }
|
|
7902
|
+
);
|
|
7903
|
+
|
|
7904
|
+
// (b) …and the comma-joined prose they replace is gone. Asserting the text
|
|
7905
|
+
// rather than only the chip count: the first cut rendered the chips
|
|
7906
|
+
// BESIDE the summary, which is the same identifiers twice.
|
|
7907
|
+
const casesText =
|
|
7908
|
+
canvasElement
|
|
7909
|
+
.querySelector('[data-field="cases"] .options-readfirst-valuetext')
|
|
7910
|
+
?.textContent?.trim() ?? '';
|
|
7911
|
+
expect(casesText).not.toContain('case_1,');
|
|
7912
|
+
|
|
7913
|
+
// (c) NEGATIVE: one entry has no name, so no entry is chipped — a
|
|
7914
|
+
// half-chipped row would read as though that case were missing.
|
|
7915
|
+
const mixedRow = canvasElement.querySelector('[data-field="mixed"]');
|
|
7916
|
+
expect(mixedRow?.querySelectorAll('.options-readfirst-case-tag').length).toBe(0);
|
|
7917
|
+
|
|
7918
|
+
// (d) NEGATIVE: an empty value is not a SET value, so it never reaches
|
|
7919
|
+
// this branch at all — its row sits in the collapsed optional group and
|
|
7920
|
+
// is not in the DOM. Counting canvas-wide says the same thing more
|
|
7921
|
+
// strongly than probing for a row that does not exist: the only chips
|
|
7922
|
+
// on the whole form are the two named cases, so neither the unnamed nor
|
|
7923
|
+
// the empty field contributed one.
|
|
7924
|
+
expect(canvasElement.querySelectorAll('.options-readfirst-case-tag').length).toBe(2);
|
|
7925
|
+
},
|
|
7926
|
+
};
|
|
7927
|
+
|
|
7928
|
+
/**
|
|
7929
|
+
* Clear-value is offered only when there is something to clear.
|
|
7930
|
+
*
|
|
7931
|
+
* `[]` is neither `undefined`, `null` nor `''`, so a list field whose last item
|
|
7932
|
+
* had just been removed went on showing the red clear button, and confirming it
|
|
7933
|
+
* cleared nothing. It is visible wherever a list field stays open — a test's
|
|
7934
|
+
* cases table sits above exactly such a button once its last case is deleted.
|
|
7935
|
+
*
|
|
7936
|
+
* Both fields here are lists with the same schema; only the value differs, so
|
|
7937
|
+
* the button's presence is decided by the value alone.
|
|
7938
|
+
*/
|
|
7939
|
+
export const ClearValueOnlyWhenThereIsAValue: Story = {
|
|
7940
|
+
parameters: {
|
|
7941
|
+
docs: {
|
|
7942
|
+
description: {
|
|
7943
|
+
story:
|
|
7944
|
+
'A list field with items offers Clear value; the same field with an empty list does not, because there is nothing to clear.',
|
|
7945
|
+
},
|
|
7946
|
+
},
|
|
7947
|
+
},
|
|
7948
|
+
args: {
|
|
7949
|
+
compact: true,
|
|
7950
|
+
minColumnWidth: '360px',
|
|
7951
|
+
/* Both rows OPEN, because that is where this button lives. A collapsed list
|
|
7952
|
+
row shows no clear at all — the inline one is only for editors with no
|
|
7953
|
+
clear of their own (bools, fixed-choice pickers) — so the button under
|
|
7954
|
+
test is the expanded card's, which is what a consumer that opens a field
|
|
7955
|
+
by default puts permanently on screen. */
|
|
7956
|
+
initialExpandedOptions: ['filled', 'emptied'],
|
|
7957
|
+
value: {
|
|
7958
|
+
filled: { type: 'list', value: ['alpha', 'beta'] },
|
|
7959
|
+
emptied: { type: 'list', value: [] },
|
|
7960
|
+
},
|
|
7961
|
+
options: {
|
|
7962
|
+
/* Both REQUIRED, like a test's `cases`. An unset optional field folds
|
|
7963
|
+
into the collapsed Optional box and is not in the DOM at all, so an
|
|
7964
|
+
empty list only stays on screen — where this button can be misclicked —
|
|
7965
|
+
when the field is one the form insists on. */
|
|
7966
|
+
filled: {
|
|
7967
|
+
type: 'list',
|
|
7968
|
+
ui_type: 'list',
|
|
7969
|
+
element_type: 'string',
|
|
7970
|
+
display_name: 'Filled',
|
|
7971
|
+
required: true,
|
|
7972
|
+
},
|
|
7973
|
+
emptied: {
|
|
7974
|
+
type: 'list',
|
|
7975
|
+
ui_type: 'list',
|
|
7976
|
+
element_type: 'string',
|
|
7977
|
+
display_name: 'Emptied',
|
|
7978
|
+
required: true,
|
|
7979
|
+
},
|
|
7980
|
+
} as never,
|
|
7981
|
+
},
|
|
7982
|
+
play: async ({ canvasElement }) => {
|
|
7983
|
+
await waitFor(
|
|
7984
|
+
() => {
|
|
7985
|
+
expect(canvasElement.querySelector('[data-field="filled"]')).toBeTruthy();
|
|
7986
|
+
},
|
|
7987
|
+
{ timeout: 5000 }
|
|
7988
|
+
);
|
|
7989
|
+
|
|
7990
|
+
// (a) items present -> the clear button is offered
|
|
7991
|
+
const filled = canvasElement.querySelector('[data-field="filled"]');
|
|
7992
|
+
expect(filled?.querySelectorAll('.options-readfirst-clear').length).toBe(1);
|
|
7993
|
+
|
|
7994
|
+
// (b) empty list -> nothing to clear, so no button and no dialog to reach
|
|
7995
|
+
const emptied = canvasElement.querySelector('[data-field="emptied"]');
|
|
7996
|
+
expect(emptied).toBeTruthy();
|
|
7997
|
+
expect(emptied?.querySelectorAll('.options-readfirst-clear').length).toBe(0);
|
|
7998
|
+
},
|
|
7999
|
+
};
|
|
8000
|
+
|
|
8001
|
+
/**
|
|
8002
|
+
* A creatable picker is ONE control, not two.
|
|
8003
|
+
*
|
|
8004
|
+
* `rendersCreatableValueSelect` exists so that exactly one of the two renderers
|
|
8005
|
+
* draws the value: the chip picker holds it and offers the candidates, and the
|
|
8006
|
+
* raw editor stands down. It asked for `type === 'string'` — but a picker field
|
|
8007
|
+
* declares a storage type (`*string`) AND a `ui_type` (`select-string`), and by
|
|
8008
|
+
* the time the schema arrives the two are flattened to `type: 'select-string'`.
|
|
8009
|
+
* So neither renderer stood down: the picker drew the value, and the
|
|
8010
|
+
* saved-and-suggested list drew the same candidates again beneath it.
|
|
8011
|
+
*
|
|
8012
|
+
* It showed on a test's service-method step, where Service and Method offered
|
|
8013
|
+
* 183 and 7 values twice over in two controls that set one value each.
|
|
8014
|
+
*/
|
|
8015
|
+
export const CreatablePickerIsOneControl: Story = {
|
|
8016
|
+
parameters: {
|
|
8017
|
+
docs: {
|
|
8018
|
+
description: {
|
|
8019
|
+
story:
|
|
8020
|
+
'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.',
|
|
8021
|
+
},
|
|
8022
|
+
},
|
|
8023
|
+
},
|
|
8024
|
+
args: {
|
|
8025
|
+
compact: true,
|
|
8026
|
+
minColumnWidth: '360px',
|
|
8027
|
+
initialExpandedOptions: ['method'],
|
|
8028
|
+
value: { method: { type: 'select-string', value: 'init' } },
|
|
8029
|
+
options: {
|
|
8030
|
+
method: {
|
|
8031
|
+
type: 'select-string',
|
|
8032
|
+
ui_type: 'select-string',
|
|
8033
|
+
display_name: 'Method',
|
|
8034
|
+
required: true,
|
|
8035
|
+
allowed_values_creatable: true,
|
|
8036
|
+
allowed_values: [
|
|
8037
|
+
{ display_name: 'init', value: { type: 'string', value: 'init' } },
|
|
8038
|
+
{ display_name: 'start', value: { type: 'string', value: 'start' } },
|
|
8039
|
+
{ display_name: 'stop', value: { type: 'string', value: 'stop' } },
|
|
8040
|
+
],
|
|
8041
|
+
},
|
|
8042
|
+
} as never,
|
|
8043
|
+
},
|
|
8044
|
+
play: async ({ canvasElement }) => {
|
|
8045
|
+
const row = await waitFor(
|
|
8046
|
+
() => {
|
|
8047
|
+
const el = canvasElement.querySelector('[data-field="method"]');
|
|
8048
|
+
expect(el).toBeTruthy();
|
|
8049
|
+
return el as HTMLElement;
|
|
8050
|
+
},
|
|
8051
|
+
{ timeout: 5000 }
|
|
8052
|
+
);
|
|
8053
|
+
|
|
8054
|
+
// The value is still shown and still editable — one control, not none.
|
|
8055
|
+
expect(row.textContent).toContain('init');
|
|
8056
|
+
|
|
8057
|
+
// …and the second way to set the same field is gone. Asserting the label
|
|
8058
|
+
// rather than a count: it is what the duplicate control announces itself as,
|
|
8059
|
+
// and what a reader saw twice.
|
|
8060
|
+
expect(row.textContent).not.toContain('Saved & Suggested Values');
|
|
8061
|
+
},
|
|
8062
|
+
};
|
|
8063
|
+
|
|
8064
|
+
/**
|
|
8065
|
+
* A chosen template hovers ONCE, and says what the value is.
|
|
8066
|
+
*
|
|
8067
|
+
* Two defects met on the same row. The chip carries its own popover and the row
|
|
8068
|
+
* carried a native `title` as well, so hovering fired both — in two places, and
|
|
8069
|
+
* only the popover appears in a screenshot, which is what makes the pair hard to
|
|
8070
|
+
* report. `showsTemplateChips` had already closed this for prose with templates
|
|
8071
|
+
* embedded in it; a value that IS one template was left open.
|
|
8072
|
+
*
|
|
8073
|
+
* And both of them showed the reference — `$._case.title` — which is the
|
|
8074
|
+
* grammar's business, not the author's. The catalogue already carries what they
|
|
8075
|
+
* want: the entry's name and the prose describing it. The reference stays in the
|
|
8076
|
+
* field's own help and in the picker, where it is genuinely needed.
|
|
8077
|
+
*/
|
|
8078
|
+
export const TemplateValueHoversOnceAndDescribesItself: Story = {
|
|
8079
|
+
parameters: {
|
|
8080
|
+
docs: {
|
|
8081
|
+
description: {
|
|
8082
|
+
story:
|
|
8083
|
+
'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.',
|
|
8084
|
+
},
|
|
8085
|
+
},
|
|
8086
|
+
},
|
|
8087
|
+
args: {
|
|
8088
|
+
compact: true,
|
|
8089
|
+
minColumnWidth: '360px',
|
|
8090
|
+
value: { expected: { type: 'string', value: '$._case.title' } },
|
|
8091
|
+
options: {
|
|
8092
|
+
expected: {
|
|
8093
|
+
type: 'string',
|
|
8094
|
+
display_name: 'Expected Value',
|
|
8095
|
+
supports_templates: true,
|
|
8096
|
+
templates: {
|
|
8097
|
+
items: [
|
|
8098
|
+
{
|
|
8099
|
+
label: 'Values this case captures',
|
|
8100
|
+
items: [
|
|
8101
|
+
{
|
|
8102
|
+
value: '$._case.title',
|
|
8103
|
+
label: 'title (this case)',
|
|
8104
|
+
description: 'A short line naming what this case verifies. (string)',
|
|
8105
|
+
},
|
|
8106
|
+
],
|
|
8107
|
+
},
|
|
8108
|
+
],
|
|
8109
|
+
},
|
|
8110
|
+
},
|
|
8111
|
+
} as never,
|
|
8112
|
+
},
|
|
8113
|
+
play: async ({ canvasElement }) => {
|
|
8114
|
+
const row = await waitFor(
|
|
8115
|
+
() => {
|
|
8116
|
+
const el = canvasElement.querySelector('[data-field="expected"]');
|
|
8117
|
+
expect(el).toBeTruthy();
|
|
8118
|
+
return el as HTMLElement;
|
|
8119
|
+
},
|
|
8120
|
+
{ timeout: 5000 }
|
|
8121
|
+
);
|
|
8122
|
+
|
|
8123
|
+
// (a) The row draws the friendly name, not the reference.
|
|
8124
|
+
expect(row.textContent).toContain('title (this case)');
|
|
8125
|
+
|
|
8126
|
+
// (b) ONE hover on the value. The row must not add a native `title`
|
|
8127
|
+
// alongside the chip's own popover — the label keeps its own, which is
|
|
8128
|
+
// a different element and a different thing to say.
|
|
8129
|
+
const nativeTitles = Array.from(row.querySelectorAll('[title]')).map((el) =>
|
|
8130
|
+
el.getAttribute('title')
|
|
8131
|
+
);
|
|
8132
|
+
expect(nativeTitles).not.toContain('$._case.title');
|
|
8133
|
+
},
|
|
8134
|
+
};
|