@semiont/react-ui 0.5.27 → 0.5.28
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/dist/{PdfAnnotationCanvas.client-EVUPTUWZ.js → PdfAnnotationCanvas.client-C2IGG4SW.js} +31 -4
- package/dist/{ar-OU3DQGC6.js → ar-FABPTJVB.js} +39 -23
- package/dist/{bn-PHWCR6AZ.js → bn-LKP6BDMH.js} +39 -23
- package/dist/{chunk-PCRRBU7H.js → chunk-2GOHTTHW.js} +39 -23
- package/dist/{chunk-5VDTWRXJ.js → chunk-2YHEE63S.js} +31 -31
- package/dist/{cs-NEXYZ62I.js → cs-YQYNWHRG.js} +39 -23
- package/dist/{da-FAT43YCU.js → da-QHZOYKRY.js} +39 -23
- package/dist/{de-KRQ3WBCU.js → de-GKA7RD7J.js} +39 -23
- package/dist/{el-Q4IU3EKC.js → el-45T257AL.js} +39 -23
- package/dist/{en-2KADE7N4.js → en-RVW5EQ66.js} +2 -2
- package/dist/{es-2IO3R2MK.js → es-AVER7RU4.js} +39 -23
- package/dist/{fa-Q5GOQFXX.js → fa-K3VPK3O2.js} +39 -23
- package/dist/{fi-R6NMRWBE.js → fi-5NEKF7EB.js} +39 -23
- package/dist/{fr-QY4D5E6J.js → fr-FUMNDLBZ.js} +39 -23
- package/dist/{he-X6Y77XHY.js → he-3JGNA3PR.js} +39 -23
- package/dist/{hi-XL7IOEQM.js → hi-IJY375DF.js} +39 -23
- package/dist/{id-QZ4ZZS67.js → id-LWHKJ7ZD.js} +39 -23
- package/dist/index.css +279 -74
- package/dist/index.d.ts +170 -145
- package/dist/index.js +2743 -2365
- package/dist/{it-ME2Q7OB6.js → it-EBVOV4RW.js} +39 -23
- package/dist/{ja-HX7QASRI.js → ja-KWRIX2NF.js} +39 -23
- package/dist/{ko-EQO5AHAV.js → ko-L5CJMFDS.js} +39 -23
- package/dist/{ms-UILFSF3I.js → ms-LSNMJDR2.js} +39 -23
- package/dist/{nl-NNI45PMK.js → nl-ZB32MB7U.js} +39 -23
- package/dist/{no-MAL5F6P6.js → no-4GTNY7M5.js} +39 -23
- package/dist/{pl-4RMY6JAJ.js → pl-LUHBLTHB.js} +39 -23
- package/dist/{pt-VQH2X3QQ.js → pt-C4H3UNQ6.js} +39 -23
- package/dist/{ro-XXCQO2OG.js → ro-GGITS5V4.js} +39 -23
- package/dist/{sv-7Z6EJ6U4.js → sv-T56RTLBP.js} +39 -23
- package/dist/test-utils.js +2 -2
- package/dist/{th-L3DXXYVQ.js → th-22X2IWCB.js} +39 -23
- package/dist/{tr-P3DFVLFJ.js → tr-77TFG62H.js} +39 -23
- package/dist/{uk-7C5D3PXS.js → uk-MIIL6NXT.js} +39 -23
- package/dist/{vi-ZLTLEO7P.js → vi-7JYOIGNV.js} +39 -23
- package/dist/{zh-5FHVWDIN.js → zh-ID6DRYU4.js} +39 -23
- package/package.json +9 -8
- package/src/components/AssistProgress.tsx +36 -2
- package/src/components/CodeMirrorRenderer.tsx +8 -8
- package/src/components/__tests__/AssistProgress.test.tsx +59 -0
- package/src/components/modals/ComposeStep.tsx +218 -0
- package/src/components/modals/ConfigureGatherStep.tsx +10 -18
- package/src/components/modals/ConfigureGenerationStep.tsx +35 -19
- package/src/components/modals/ConfigureSearchStep.tsx +19 -0
- package/src/components/modals/ContextSummary.tsx +185 -72
- package/src/components/modals/CorpusPane.tsx +75 -0
- package/src/components/modals/DiscardPrompt.tsx +38 -0
- package/src/components/modals/GatherContextStep.tsx +237 -107
- package/src/components/modals/ReferenceWizardModal.tsx +238 -50
- package/src/components/modals/ResourceGenerateModal.tsx +216 -108
- package/src/components/modals/SearchModal.css +337 -101
- package/src/components/modals/SearchResultsStep.tsx +61 -68
- package/src/components/modals/WizardFooter.tsx +7 -1
- package/src/components/modals/__tests__/ComposeStep.test.tsx +152 -0
- package/src/components/modals/__tests__/ConfigureGatherStep.test.tsx +14 -26
- package/src/components/modals/__tests__/ConfigureGenerationStep.test.tsx +35 -0
- package/src/components/modals/__tests__/ConfigureSearchStep.test.tsx +24 -1
- package/src/components/modals/__tests__/GatherContextStep.resource.test.tsx +463 -25
- package/src/components/modals/__tests__/ReferenceWizardModal.test.tsx +417 -38
- package/src/components/modals/__tests__/ResourceGenerateModal.test.tsx +276 -85
- package/src/components/modals/__tests__/SearchResultsStep.test.tsx +61 -0
- package/src/components/modals/__tests__/WizardFooter.test.tsx +113 -4
- package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +34 -1
- package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +38 -1
- package/src/components/resource/AnnotateView.tsx +3 -3
- package/src/components/resource/BrowseView.tsx +5 -5
- package/src/components/resource/ResourceViewer.tsx +4 -4
- package/src/components/resource/__tests__/AnnotateView.embeddable.test.tsx +1 -1
- package/src/components/resource/__tests__/BrowseView.embeddable.test.tsx +1 -1
- package/src/components/resource/__tests__/BrowseView.test.tsx +9 -9
- package/src/components/resource/annotate-renderers.tsx +3 -3
- package/src/components/resource/panels/AssistSection.css +35 -0
- package/src/components/resource/panels/ReferenceEntry.tsx +9 -2
- package/src/components/resource/panels/ReferencesPanel.tsx +4 -0
- package/src/components/resource/panels/ResourceInfoPanel.tsx +89 -20
- package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +7 -2
- package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +41 -0
- package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +33 -1
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +148 -6
- package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +0 -65
- package/src/features/resource-compose/components/ResourceComposePage.tsx +17 -130
- package/src/features/resource-compose/state/__tests__/compose-page-state-unit.test.ts +1 -100
- package/src/features/resource-compose/state/compose-page-state-unit.ts +5 -57
- package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +71 -15
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +133 -59
- package/src/styles/core/forms.css +35 -8
- package/src/styles/features/resource-viewer.css +0 -22
- package/src/styles/features/resource.css +0 -4
- package/translations/ar.json +38 -22
- package/translations/bn.json +38 -22
- package/translations/cs.json +38 -22
- package/translations/da.json +38 -22
- package/translations/de.json +38 -22
- package/translations/el.json +38 -22
- package/translations/en.json +38 -22
- package/translations/es.json +38 -22
- package/translations/fa.json +38 -22
- package/translations/fi.json +38 -22
- package/translations/fr.json +38 -22
- package/translations/he.json +38 -22
- package/translations/hi.json +38 -22
- package/translations/id.json +38 -22
- package/translations/it.json +38 -22
- package/translations/ja.json +38 -22
- package/translations/ko.json +38 -22
- package/translations/ms.json +38 -22
- package/translations/nl.json +38 -22
- package/translations/no.json +38 -22
- package/translations/pl.json +38 -22
- package/translations/pt.json +38 -22
- package/translations/ro.json +38 -22
- package/translations/sv.json +38 -22
- package/translations/th.json +38 -22
- package/translations/tr.json +38 -22
- package/translations/uk.json +38 -22
- package/translations/vi.json +38 -22
- package/translations/zh.json +38 -22
- package/dist/PdfAnnotationCanvas.client-EVUPTUWZ.js.map +0 -1
- package/dist/PdfAnnotationCanvas.client-TGDPPGXJ.css.map +0 -1
- package/dist/ar-OU3DQGC6.js.map +0 -1
- package/dist/bn-PHWCR6AZ.js.map +0 -1
- package/dist/chunk-5VDTWRXJ.js.map +0 -1
- package/dist/chunk-BTUG3VJJ.js.map +0 -1
- package/dist/chunk-O23TLLXW.js.map +0 -1
- package/dist/chunk-PCRRBU7H.js.map +0 -1
- package/dist/chunk-QT7LYM72.js.map +0 -1
- package/dist/chunk-VXASXU4K.js.map +0 -1
- package/dist/cs-NEXYZ62I.js.map +0 -1
- package/dist/da-FAT43YCU.js.map +0 -1
- package/dist/de-KRQ3WBCU.js.map +0 -1
- package/dist/el-Q4IU3EKC.js.map +0 -1
- package/dist/en-2KADE7N4.js.map +0 -1
- package/dist/es-2IO3R2MK.js.map +0 -1
- package/dist/fa-Q5GOQFXX.js.map +0 -1
- package/dist/fi-R6NMRWBE.js.map +0 -1
- package/dist/fr-QY4D5E6J.js.map +0 -1
- package/dist/he-X6Y77XHY.js.map +0 -1
- package/dist/hi-XL7IOEQM.js.map +0 -1
- package/dist/id-QZ4ZZS67.js.map +0 -1
- package/dist/index.css.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/integrations/css-modules-helper.js.map +0 -1
- package/dist/integrations/styled-components-theme.js.map +0 -1
- package/dist/it-ME2Q7OB6.js.map +0 -1
- package/dist/ja-HX7QASRI.js.map +0 -1
- package/dist/ko-EQO5AHAV.js.map +0 -1
- package/dist/ms-UILFSF3I.js.map +0 -1
- package/dist/nl-NNI45PMK.js.map +0 -1
- package/dist/no-MAL5F6P6.js.map +0 -1
- package/dist/pl-4RMY6JAJ.js.map +0 -1
- package/dist/pt-VQH2X3QQ.js.map +0 -1
- package/dist/ro-XXCQO2OG.js.map +0 -1
- package/dist/sv-7Z6EJ6U4.js.map +0 -1
- package/dist/test-utils.css.map +0 -1
- package/dist/test-utils.js.map +0 -1
- package/dist/th-L3DXXYVQ.js.map +0 -1
- package/dist/tr-P3DFVLFJ.js.map +0 -1
- package/dist/uk-7C5D3PXS.js.map +0 -1
- package/dist/vi-ZLTLEO7P.js.map +0 -1
- package/dist/zh-5FHVWDIN.js.map +0 -1
- package/src/components/modals/ResourceSearchModal.tsx +0 -224
- package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +0 -163
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* COMPOSE-IN-MODAL P1 — the compose strategy's form, as a pure step.
|
|
3
|
+
*
|
|
4
|
+
* The from-reference slice of the compose page, lifted: name, save location,
|
|
5
|
+
* entity types (read-only tags when the reference fixed them, picker
|
|
6
|
+
* otherwise), language, and the editor. Deliberately NONE of the page's
|
|
7
|
+
* upload/format/encoding machinery — the fences reference mode already
|
|
8
|
+
* proved are here as absence pins. The draft is CONTROLLED by the host
|
|
9
|
+
* (WIZARD-NAVIGATION D3: stepping Back must not discard typed work), and
|
|
10
|
+
* the evidence display is the host's job (plan A3: no context redux here).
|
|
11
|
+
*/
|
|
12
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
13
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
14
|
+
import '@testing-library/jest-dom';
|
|
15
|
+
import { ComposeStep, type ComposeDraft } from '../ComposeStep';
|
|
16
|
+
|
|
17
|
+
// Mock CodeMirrorRenderer to avoid CodeMirror dependencies (same mock the
|
|
18
|
+
// compose page's own tests use).
|
|
19
|
+
vi.mock('../../CodeMirrorRenderer', () => ({
|
|
20
|
+
CodeMirrorRenderer: ({ content, onChange, editable }: any) => (
|
|
21
|
+
<textarea
|
|
22
|
+
data-testid="code-editor"
|
|
23
|
+
value={content}
|
|
24
|
+
disabled={!editable}
|
|
25
|
+
onChange={(e) => onChange?.(e.target.value)}
|
|
26
|
+
/>
|
|
27
|
+
),
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
const T = {
|
|
31
|
+
resourceTitle: 'New resource title',
|
|
32
|
+
resourceTitlePlaceholder: 'Title…',
|
|
33
|
+
saveLocation: 'Save location',
|
|
34
|
+
entityTypes: 'Entity types',
|
|
35
|
+
language: 'Language',
|
|
36
|
+
contentLabel: 'Content',
|
|
37
|
+
back: 'Back',
|
|
38
|
+
createAndLink: 'Create & Link',
|
|
39
|
+
creatingAndLinking: 'Creating…',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const DRAFT: ComposeDraft = {
|
|
43
|
+
name: 'Black Hawk',
|
|
44
|
+
storagePath: 'people/black-hawk.md',
|
|
45
|
+
content: 'Sauk leader.',
|
|
46
|
+
entityTypes: [],
|
|
47
|
+
language: 'en',
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
function renderStep(over: Partial<React.ComponentProps<typeof ComposeStep>> = {}) {
|
|
51
|
+
const onDraftChange = vi.fn();
|
|
52
|
+
const onBack = vi.fn();
|
|
53
|
+
const onCompose = vi.fn<(p: unknown) => Promise<void>>().mockResolvedValue(undefined);
|
|
54
|
+
const utils = render(
|
|
55
|
+
<ComposeStep
|
|
56
|
+
draft={DRAFT}
|
|
57
|
+
onDraftChange={onDraftChange}
|
|
58
|
+
referenceEntityTypes={['Person']}
|
|
59
|
+
entityTypeOptions={['Person', 'Topic', 'Location']}
|
|
60
|
+
showLineNumbers={false}
|
|
61
|
+
hoverDelayMs={300}
|
|
62
|
+
onBack={onBack}
|
|
63
|
+
onCompose={onCompose}
|
|
64
|
+
translations={T}
|
|
65
|
+
{...over}
|
|
66
|
+
/>,
|
|
67
|
+
);
|
|
68
|
+
return { ...utils, onDraftChange, onBack, onCompose };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
describe('ComposeStep — the from-reference slice, and nothing else', () => {
|
|
72
|
+
it('renders the draft: name, save location, content, language', () => {
|
|
73
|
+
renderStep();
|
|
74
|
+
expect(screen.getByLabelText(T.resourceTitle)).toHaveValue('Black Hawk');
|
|
75
|
+
expect(screen.getByLabelText(T.saveLocation)).toHaveValue('people/black-hawk.md');
|
|
76
|
+
expect(screen.getByTestId('code-editor')).toHaveValue('Sauk leader.');
|
|
77
|
+
expect(screen.getByLabelText(T.language)).toBeInTheDocument();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('edits flow through onDraftChange — the host owns the draft (D3)', () => {
|
|
81
|
+
const { onDraftChange } = renderStep();
|
|
82
|
+
fireEvent.change(screen.getByLabelText(T.resourceTitle), { target: { value: 'Black Hawk (Sauk)' } });
|
|
83
|
+
expect(onDraftChange).toHaveBeenCalledWith({ name: 'Black Hawk (Sauk)' });
|
|
84
|
+
fireEvent.change(screen.getByTestId('code-editor'), { target: { value: 'A Sauk leader.' } });
|
|
85
|
+
expect(onDraftChange).toHaveBeenCalledWith({ content: 'A Sauk leader.' });
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('reference-fixed entity types render as read-only tags, not a picker (D6)', () => {
|
|
89
|
+
renderStep(); // referenceEntityTypes: ['Person']
|
|
90
|
+
expect(screen.getByText('Person')).toBeInTheDocument();
|
|
91
|
+
// No toggling: the types were chosen when the reference was created.
|
|
92
|
+
expect(screen.queryByRole('button', { name: /Person/ })).not.toBeInTheDocument();
|
|
93
|
+
expect(screen.queryByRole('button', { name: /Topic/ })).not.toBeInTheDocument();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('an empty reference set falls back to the picker over the owner-supplied options (D6)', () => {
|
|
97
|
+
const { onDraftChange } = renderStep({ referenceEntityTypes: [] });
|
|
98
|
+
const topic = screen.getByRole('button', { name: /Topic/ });
|
|
99
|
+
fireEvent.click(topic);
|
|
100
|
+
expect(onDraftChange).toHaveBeenCalledWith({ entityTypes: ['Topic'] });
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('carries NONE of the page-only machinery: no upload, no format, no encoding', () => {
|
|
104
|
+
const { container } = renderStep();
|
|
105
|
+
expect(container.querySelector('.semiont-form__upload-dropzone')).toBeNull();
|
|
106
|
+
expect(container.querySelector('.semiont-form__content-source-toggle')).toBeNull();
|
|
107
|
+
expect(container.querySelector('#format-select')).toBeNull();
|
|
108
|
+
expect(container.querySelector('#charset-select')).toBeNull();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('renders no context of its own — the evidence is host-stacked (A3)', () => {
|
|
112
|
+
const { container } = renderStep();
|
|
113
|
+
expect(container.querySelector('.semiont-gather-pane')).toBeNull();
|
|
114
|
+
expect(container.querySelector('.semiont-gather__outer')).toBeNull();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('submit emits the full params with the file:// prefix applied at the seam', async () => {
|
|
118
|
+
const { onCompose } = renderStep({ referenceEntityTypes: ['Person'] });
|
|
119
|
+
fireEvent.click(screen.getByRole('button', { name: T.createAndLink }));
|
|
120
|
+
await waitFor(() => expect(onCompose).toHaveBeenCalledTimes(1));
|
|
121
|
+
expect(onCompose).toHaveBeenCalledWith({
|
|
122
|
+
name: 'Black Hawk',
|
|
123
|
+
storagePath: 'file://people/black-hawk.md',
|
|
124
|
+
content: 'Sauk leader.',
|
|
125
|
+
entityTypes: ['Person'],
|
|
126
|
+
language: 'en',
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('the footer is a wizard footer: Back + Create, pending while in flight, re-enabled on rejection (A4)', async () => {
|
|
131
|
+
let reject!: (e: Error) => void;
|
|
132
|
+
const onCompose = vi.fn(() => new Promise<void>((_, r) => { reject = r; }));
|
|
133
|
+
const { container } = renderStep({ onCompose });
|
|
134
|
+
|
|
135
|
+
expect(container.querySelector('.semiont-modal__actions--wizard')).not.toBeNull();
|
|
136
|
+
fireEvent.click(screen.getByRole('button', { name: T.createAndLink }));
|
|
137
|
+
expect(await screen.findByRole('button', { name: T.creatingAndLinking })).toBeDisabled();
|
|
138
|
+
|
|
139
|
+
reject(new Error('boom'));
|
|
140
|
+
expect(await screen.findByRole('button', { name: T.createAndLink })).toBeEnabled();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('Back is the only retreat and dismissal never lives in the footer (A4)', () => {
|
|
144
|
+
const { container, onBack } = renderStep();
|
|
145
|
+
const footerButtons = Array.from(
|
|
146
|
+
container.querySelectorAll('.semiont-modal__actions button'),
|
|
147
|
+
).map((b) => b.textContent ?? '');
|
|
148
|
+
expect(footerButtons.filter((l) => /cancel|✕/i.test(l))).toEqual([]);
|
|
149
|
+
fireEvent.click(screen.getByRole('button', { name: `◀ ${T.back}` }));
|
|
150
|
+
expect(onBack).toHaveBeenCalledTimes(1);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
@@ -16,13 +16,12 @@ const t = {
|
|
|
16
16
|
includeSummary: 'Include summary',
|
|
17
17
|
depth: 'Depth',
|
|
18
18
|
maxResources: 'Max resources',
|
|
19
|
-
cancel: 'Cancel',
|
|
20
19
|
gather: 'Gather',
|
|
21
20
|
};
|
|
22
21
|
|
|
23
22
|
describe('ConfigureGatherStep', () => {
|
|
24
23
|
it('renders the intro and the default option values', () => {
|
|
25
|
-
render(<ConfigureGatherStep onGather={vi.fn()}
|
|
24
|
+
render(<ConfigureGatherStep onGather={vi.fn()} translations={t} />);
|
|
26
25
|
expect(screen.getByText('Choose what to include.')).toBeInTheDocument();
|
|
27
26
|
expect(screen.getByLabelText('Include content')).toBeChecked();
|
|
28
27
|
expect(screen.getByLabelText('Include summary')).toBeChecked();
|
|
@@ -32,14 +31,14 @@ describe('ConfigureGatherStep', () => {
|
|
|
32
31
|
|
|
33
32
|
it('emits the default config on submit', () => {
|
|
34
33
|
const onGather = vi.fn();
|
|
35
|
-
render(<ConfigureGatherStep onGather={onGather}
|
|
34
|
+
render(<ConfigureGatherStep onGather={onGather} translations={t} />);
|
|
36
35
|
fireEvent.click(screen.getByRole('button', { name: /Gather/ }));
|
|
37
36
|
expect(onGather).toHaveBeenCalledWith({ includeContent: true, includeSummary: true, depth: 2, maxResources: 10 });
|
|
38
37
|
});
|
|
39
38
|
|
|
40
39
|
it('reflects edits in the emitted config', () => {
|
|
41
40
|
const onGather = vi.fn();
|
|
42
|
-
render(<ConfigureGatherStep onGather={onGather}
|
|
41
|
+
render(<ConfigureGatherStep onGather={onGather} translations={t} />);
|
|
43
42
|
fireEvent.click(screen.getByLabelText('Include content')); // uncheck
|
|
44
43
|
fireEvent.click(screen.getByLabelText('Include summary')); // uncheck
|
|
45
44
|
fireEvent.change(screen.getByLabelText('Depth'), { target: { value: '4' } });
|
|
@@ -48,32 +47,21 @@ describe('ConfigureGatherStep', () => {
|
|
|
48
47
|
expect(onGather).toHaveBeenCalledWith({ includeContent: false, includeSummary: false, depth: 4, maxResources: 25 });
|
|
49
48
|
});
|
|
50
49
|
|
|
51
|
-
it('
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
);
|
|
61
|
-
expect(screen.getByLabelText('Include summary')).not.toBeChecked();
|
|
62
|
-
expect(screen.getByLabelText('Depth')).toHaveValue(1);
|
|
63
|
-
fireEvent.click(screen.getByRole('button', { name: /Gather/ }));
|
|
64
|
-
expect(onGather).toHaveBeenCalledWith({ includeContent: true, includeSummary: false, depth: 1, maxResources: 5 });
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('calls onCancel from the cancel button', () => {
|
|
68
|
-
const onCancel = vi.fn();
|
|
69
|
-
render(<ConfigureGatherStep onGather={vi.fn()} onCancel={onCancel} translations={t} />);
|
|
70
|
-
fireEvent.click(screen.getByRole('button', { name: /Cancel/ }));
|
|
71
|
-
expect(onCancel).toHaveBeenCalledTimes(1);
|
|
50
|
+
it('the footer is the wizard footer: advance only, no dismissal, no flex (GFR A5)', () => {
|
|
51
|
+
// Dismissal lives on the modal's corner ✕/Esc/backdrop, never in a step
|
|
52
|
+
// footer (WIZARD-NAVIGATION D1); this is the first step, so no retreat either.
|
|
53
|
+
const { container } = render(<ConfigureGatherStep onGather={vi.fn()} translations={t} />);
|
|
54
|
+
const footer = container.querySelector('.semiont-modal__actions--wizard');
|
|
55
|
+
expect(footer).not.toBeNull();
|
|
56
|
+
const buttons = Array.from(footer!.querySelectorAll('button'));
|
|
57
|
+
expect(buttons).toHaveLength(1);
|
|
58
|
+
expect(buttons[0]!.textContent).not.toMatch(/cancel|✕/i);
|
|
59
|
+
expect(buttons[0]!.className).not.toContain('semiont-button--flex');
|
|
72
60
|
});
|
|
73
61
|
|
|
74
62
|
it('renders the children slot (exclusion picker)', () => {
|
|
75
63
|
render(
|
|
76
|
-
<ConfigureGatherStep onGather={vi.fn()}
|
|
64
|
+
<ConfigureGatherStep onGather={vi.fn()} translations={t}>
|
|
77
65
|
<div>EXCLUDE-SLOT</div>
|
|
78
66
|
</ConfigureGatherStep>,
|
|
79
67
|
);
|
|
@@ -17,6 +17,7 @@ import { ConfigureGenerationStep } from '../ConfigureGenerationStep';
|
|
|
17
17
|
const translations = {
|
|
18
18
|
resourceTitle: 'Title',
|
|
19
19
|
resourceTitlePlaceholder: 'Title…',
|
|
20
|
+
saveLocation: 'Save location',
|
|
20
21
|
additionalInstructions: 'Instructions',
|
|
21
22
|
additionalInstructionsPlaceholder: 'Instructions…',
|
|
22
23
|
language: 'Language',
|
|
@@ -96,6 +97,17 @@ describe('ConfigureGenerationStep — ceiling awareness', () => {
|
|
|
96
97
|
expect(screen.getByText(/Limited to 64000 tokens by claude-sonnet-5\./)).toBeInTheDocument();
|
|
97
98
|
});
|
|
98
99
|
|
|
100
|
+
it('the ceiling sentence is a row-level footnote, never a column child', () => {
|
|
101
|
+
// A full sentence inside the 5.5rem Max Length column wraps one word per
|
|
102
|
+
// line and its height taxes the whole flex row — the void under the other
|
|
103
|
+
// controls IS this caption. It renders full-width under the row instead.
|
|
104
|
+
renderStep(agentWithCeiling(64_000));
|
|
105
|
+
|
|
106
|
+
const help = screen.getByText(/Limited to 64000 tokens/);
|
|
107
|
+
expect(help).toHaveClass('semiont-form__help--row');
|
|
108
|
+
expect(help.closest('.semiont-form__field--narrow')).toBeNull();
|
|
109
|
+
});
|
|
110
|
+
|
|
99
111
|
it('will not commit a value above the ceiling', () => {
|
|
100
112
|
// `max` on a number input does not prevent TYPING an over-value — it only
|
|
101
113
|
// marks the field invalid. D6 says the value cannot be entered, so assert
|
|
@@ -253,3 +265,26 @@ describe('ConfigureGenerationStep — the draft is the owner\'s', () => {
|
|
|
253
265
|
expect(props.onGenerate.mock.calls[0]![0]).not.toHaveProperty('prompt');
|
|
254
266
|
});
|
|
255
267
|
});
|
|
268
|
+
|
|
269
|
+
describe('ConfigureGenerationStep — the hint echo (GEP P1c, D8)', () => {
|
|
270
|
+
it('echoes a non-empty hint, and renders nothing without one', () => {
|
|
271
|
+
const { container, rerender } = render(
|
|
272
|
+
<ConfigureGenerationStep
|
|
273
|
+
context={context} config={DRAFT} onConfigChange={vi.fn()}
|
|
274
|
+
onBack={vi.fn()} onGenerate={vi.fn()} translations={translations}
|
|
275
|
+
hintEcho={{ label: 'Hint', value: 'the ancient city, not the modern province' }}
|
|
276
|
+
/>,
|
|
277
|
+
);
|
|
278
|
+
const echo = container.querySelector('.semiont-wizard__hint-echo');
|
|
279
|
+
expect(echo).not.toBeNull();
|
|
280
|
+
expect(echo!.textContent).toContain('the ancient city, not the modern province');
|
|
281
|
+
|
|
282
|
+
rerender(
|
|
283
|
+
<ConfigureGenerationStep
|
|
284
|
+
context={context} config={DRAFT} onConfigChange={vi.fn()}
|
|
285
|
+
onBack={vi.fn()} onGenerate={vi.fn()} translations={translations}
|
|
286
|
+
/>,
|
|
287
|
+
);
|
|
288
|
+
expect(container.querySelector('.semiont-wizard__hint-echo')).toBeNull();
|
|
289
|
+
});
|
|
290
|
+
});
|
|
@@ -16,7 +16,7 @@ import { ConfigureSearchStep, type SearchConfig } from '../ConfigureSearchStep';
|
|
|
16
16
|
const T = {
|
|
17
17
|
maxResults: 'Max Results', semanticScoring: 'Semantic Scoring',
|
|
18
18
|
semanticScoringHelp: 'Use AI to score results by semantic relevance',
|
|
19
|
-
back: 'Back', search: 'Search', searching: 'Searching…',
|
|
19
|
+
back: 'Back', search: 'Search', searching: 'Searching…', searchFailed: 'Search failed',
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
const CONFIG: SearchConfig = { limit: 10, useSemanticScoring: true };
|
|
@@ -50,6 +50,29 @@ describe('ConfigureSearchStep', () => {
|
|
|
50
50
|
expect(onConfigChange).toHaveBeenCalledWith({ limit: 10, useSemanticScoring: false });
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
+
it('echoes the hint being steered by, and stays silent without one (GEP P1c, D8)', () => {
|
|
54
|
+
// The thing the hint steers goes invisible behind the step transition —
|
|
55
|
+
// one quiet line keeps it visible and advertises that the hint did something.
|
|
56
|
+
const { container, rerender } = render(
|
|
57
|
+
<ConfigureSearchStep
|
|
58
|
+
config={CONFIG} onConfigChange={vi.fn()} onBack={vi.fn()} onSearch={vi.fn()}
|
|
59
|
+
translations={T}
|
|
60
|
+
hintEcho={{ label: 'Hint', value: 'the ancient city, not the modern province' }}
|
|
61
|
+
/>,
|
|
62
|
+
);
|
|
63
|
+
const echo = container.querySelector('.semiont-wizard__hint-echo');
|
|
64
|
+
expect(echo).not.toBeNull();
|
|
65
|
+
expect(echo!.textContent).toContain('the ancient city, not the modern province');
|
|
66
|
+
|
|
67
|
+
rerender(
|
|
68
|
+
<ConfigureSearchStep
|
|
69
|
+
config={CONFIG} onConfigChange={vi.fn()} onBack={vi.fn()} onSearch={vi.fn()}
|
|
70
|
+
translations={T}
|
|
71
|
+
/>,
|
|
72
|
+
);
|
|
73
|
+
expect(container.querySelector('.semiont-wizard__hint-echo')).toBeNull();
|
|
74
|
+
});
|
|
75
|
+
|
|
53
76
|
it('submits whatever the owner currently holds, not a default', async () => {
|
|
54
77
|
// The point of controlled state: a step that kept its own copy could submit
|
|
55
78
|
// something the wizard never saw.
|