@semiont/react-ui 0.4.20 → 0.4.21
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/README.md +8 -5
- package/dist/{PdfAnnotationCanvas.client-CHDCGQBR.mjs → PdfAnnotationCanvas.client-6ZGFEN2N.mjs} +9 -13
- package/dist/PdfAnnotationCanvas.client-6ZGFEN2N.mjs.map +1 -0
- package/dist/TranslationManager-9Xj3MIWQ.d.mts +16 -0
- package/dist/chunk-KEDFYI6N.mjs +7788 -0
- package/dist/chunk-KEDFYI6N.mjs.map +1 -0
- package/dist/index.d.mts +171 -1140
- package/dist/index.mjs +3263 -13644
- package/dist/index.mjs.map +1 -1
- package/dist/test-utils.d.mts +46 -21
- package/dist/test-utils.mjs +2499 -87
- package/dist/test-utils.mjs.map +1 -1
- package/package.json +1 -2
- package/src/components/AnnotateReferencesProgressWidget.tsx +21 -28
- package/src/components/CodeMirrorRenderer.tsx +9 -11
- package/src/components/StatusDisplay.tsx +42 -16
- package/src/components/Toolbar.tsx +4 -4
- package/src/components/__tests__/AnnotateReferencesProgressWidget.test.tsx +34 -20
- package/src/components/__tests__/StatusDisplay.test.tsx +47 -64
- package/src/components/__tests__/Toolbar.test.tsx +4 -4
- package/src/components/annotation/AnnotateToolbar.tsx +8 -7
- package/src/components/annotation/__tests__/AnnotateToolbar.test.tsx +31 -77
- package/src/components/annotation-popups/__tests__/JsonLdView.test.tsx +0 -1
- package/src/components/image-annotation/AnnotationOverlay.tsx +12 -13
- package/src/components/image-annotation/SvgDrawingCanvas.tsx +7 -7
- package/src/components/modals/PermissionDeniedModal.tsx +11 -11
- package/src/components/modals/ReferenceWizardModal.tsx +14 -18
- package/src/components/modals/ResourceSearchModal.tsx +10 -6
- package/src/components/modals/SearchModal.tsx +10 -6
- package/src/components/modals/SessionExpiredModal.tsx +11 -11
- package/src/components/modals/__tests__/PermissionDeniedModal.test.tsx +7 -7
- package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +10 -8
- package/src/components/modals/__tests__/SearchModal.search-wiring.test.tsx +10 -7
- package/src/components/modals/__tests__/SessionExpiredModal.test.tsx +5 -5
- package/src/components/navigation/CollapsibleResourceNavigation.tsx +10 -10
- package/src/components/navigation/ObservableLink.tsx +6 -6
- package/src/components/navigation/SimpleNavigation.tsx +4 -4
- package/src/components/navigation/__tests__/ObservableLink.test.tsx +4 -4
- package/src/components/navigation/__tests__/SimpleNavigation.test.tsx +4 -4
- package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +9 -11
- package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +0 -1
- package/src/components/resource/AnnotateView.tsx +7 -6
- package/src/components/resource/AnnotationHistory.tsx +9 -12
- package/src/components/resource/BrowseView.tsx +8 -7
- package/src/components/resource/ResourceViewer.tsx +17 -25
- package/src/components/resource/__tests__/AnnotationHistory.test.tsx +54 -192
- package/src/components/resource/__tests__/BrowseView.test.tsx +34 -83
- package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +40 -31
- package/src/components/resource/panels/AssessmentEntry.tsx +5 -4
- package/src/components/resource/panels/AssessmentPanel.tsx +19 -15
- package/src/components/resource/panels/AssistSection.tsx +11 -13
- package/src/components/resource/panels/CollaborationPanel.tsx +29 -7
- package/src/components/resource/panels/CommentEntry.tsx +5 -4
- package/src/components/resource/panels/CommentsPanel.tsx +9 -11
- package/src/components/resource/panels/HighlightEntry.tsx +5 -4
- package/src/components/resource/panels/HighlightPanel.tsx +10 -11
- package/src/components/resource/panels/ReferenceEntry.tsx +8 -8
- package/src/components/resource/panels/ReferencesPanel.tsx +13 -12
- package/src/components/resource/panels/ResourceInfoPanel.tsx +7 -6
- package/src/components/resource/panels/TagEntry.tsx +5 -4
- package/src/components/resource/panels/TaggingPanel.tsx +10 -16
- package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +3 -2
- package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +18 -52
- package/src/components/resource/panels/__tests__/CollaborationPanel.test.tsx +51 -20
- package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +18 -56
- package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +0 -1
- package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +4 -5
- package/src/components/resource/panels/__tests__/ReferencesPanel.observable-flow.test.tsx +153 -0
- package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +51 -106
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +15 -47
- package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +15 -47
- package/src/components/settings/SettingsPanel.tsx +8 -8
- package/src/components/settings/__tests__/SettingsPanel.test.tsx +12 -12
- package/src/features/admin-devops/components/AdminDevOpsPage.tsx +1 -1
- package/src/features/admin-exchange/components/AdminExchangePage.tsx +1 -1
- package/src/features/admin-exchange/components/ImportCard.tsx +2 -6
- package/src/features/admin-security/components/AdminSecurityPage.tsx +1 -1
- package/src/features/admin-users/components/AdminUsersPage.tsx +1 -1
- package/src/features/moderate-entity-tags/components/EntityTagsPage.tsx +1 -1
- package/src/features/moderate-recent/components/RecentDocumentsPage.tsx +1 -1
- package/src/features/moderate-tag-schemas/components/TagSchemasPage.tsx +1 -1
- package/src/features/moderation-linked-data/components/LinkedDataPage.tsx +1 -1
- package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +5 -3
- package/src/features/resource-compose/components/ResourceComposePage.tsx +5 -22
- package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +4 -3
- package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +1 -1
- package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +38 -45
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +123 -192
- package/dist/KnowledgeBaseSessionContext-BNNunwzO.d.mts +0 -175
- package/dist/PdfAnnotationCanvas.client-CHDCGQBR.mjs.map +0 -1
- package/dist/chunk-OZICDVH7.mjs +0 -62
- package/dist/chunk-OZICDVH7.mjs.map +0 -1
- package/dist/chunk-R4CCMFJH.mjs +0 -877
- package/dist/chunk-R4CCMFJH.mjs.map +0 -1
- package/dist/chunk-VN5NY4SN.mjs +0 -200
- package/dist/chunk-VN5NY4SN.mjs.map +0 -1
- package/src/components/modals/ProposeEntitiesModal.tsx +0 -179
- package/src/components/modals/__tests__/ProposeEntitiesModal.test.tsx +0 -129
- package/src/features/resource-viewer/__tests__/AnnotationCreationPending.test.tsx +0 -323
- package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +0 -245
- package/src/features/resource-viewer/__tests__/AnnotationProgressDismissal.test.tsx +0 -303
- package/src/features/resource-viewer/__tests__/BindFlowIntegration.test.tsx +0 -150
- package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +0 -243
- package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +0 -383
- package/src/features/resource-viewer/__tests__/ResourceMutations.test.tsx +0 -299
- package/src/features/resource-viewer/__tests__/ToastNotifications.test.tsx +0 -186
- package/src/features/resource-viewer/__tests__/YieldFlowIntegration.test.tsx +0 -429
- package/src/features/resource-viewer/__tests__/annotation-progress-flow.test.tsx +0 -348
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Toast Notifications Test - Verifies Toast Integration
|
|
3
|
-
*
|
|
4
|
-
* This test verifies the fix for the issue identified in commit 9690806abc910bad490e684d6ef71d874a90579c.
|
|
5
|
-
*
|
|
6
|
-
* SOLUTION IMPLEMENTED:
|
|
7
|
-
* - Pattern B: Both useMarkFlow and useYieldFlow call useToast internally
|
|
8
|
-
* - Toast notifications are shown from within the hooks (self-contained)
|
|
9
|
-
*
|
|
10
|
-
* EXPECTED BEHAVIOR (after fix):
|
|
11
|
-
* - Detection completes → User sees success toast ✓
|
|
12
|
-
* - Detection fails → User sees error toast ✓
|
|
13
|
-
* - Generation completes → User sees success toast ✓
|
|
14
|
-
* - Generation fails → User sees error toast ✓
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
18
|
-
import { render, waitFor, act } from '@testing-library/react';
|
|
19
|
-
import { EventBusProvider, useEventBus } from '../../../contexts/EventBusContext';
|
|
20
|
-
import { ApiClientProvider } from '../../../contexts/ApiClientContext';
|
|
21
|
-
import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
|
|
22
|
-
import { resourceId } from '@semiont/core';
|
|
23
|
-
import { useMarkFlow } from '../../../hooks/useMarkFlow';
|
|
24
|
-
import { useYieldFlow } from '../../../hooks/useYieldFlow';
|
|
25
|
-
|
|
26
|
-
// Mock the toast hook to track calls
|
|
27
|
-
const mockShowSuccess = vi.fn();
|
|
28
|
-
const mockShowError = vi.fn();
|
|
29
|
-
|
|
30
|
-
vi.mock('../../../components/Toast', () => ({
|
|
31
|
-
useToast: () => ({
|
|
32
|
-
showSuccess: mockShowSuccess,
|
|
33
|
-
showError: mockShowError,
|
|
34
|
-
showInfo: vi.fn(),
|
|
35
|
-
showWarning: vi.fn(),
|
|
36
|
-
}),
|
|
37
|
-
}));
|
|
38
|
-
|
|
39
|
-
describe('Toast Notifications - Verifies Toast Integration', () => {
|
|
40
|
-
let eventBusInstance: any;
|
|
41
|
-
const rUri = resourceId('test');
|
|
42
|
-
|
|
43
|
-
beforeEach(() => {
|
|
44
|
-
mockShowSuccess.mockClear();
|
|
45
|
-
mockShowError.mockClear();
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Test component that uses both hooks to verify toast integration
|
|
50
|
-
*/
|
|
51
|
-
function TestComponentWithDetection() {
|
|
52
|
-
eventBusInstance = useEventBus();
|
|
53
|
-
useMarkFlow(rUri);
|
|
54
|
-
return <div data-testid="test">Test</div>;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function TestComponentWithGeneration() {
|
|
58
|
-
eventBusInstance = useEventBus();
|
|
59
|
-
useYieldFlow('en', 'test-resource', vi.fn());
|
|
60
|
-
return <div data-testid="test">Test</div>;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function renderDetectionTest() {
|
|
64
|
-
return render(
|
|
65
|
-
<EventBusProvider>
|
|
66
|
-
<AuthTokenProvider token={null}>
|
|
67
|
-
<ApiClientProvider baseUrl="http://localhost:4000">
|
|
68
|
-
<TestComponentWithDetection />
|
|
69
|
-
</ApiClientProvider>
|
|
70
|
-
</AuthTokenProvider>
|
|
71
|
-
</EventBusProvider>
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function renderGenerationTest() {
|
|
76
|
-
return render(
|
|
77
|
-
<EventBusProvider>
|
|
78
|
-
<AuthTokenProvider token={null}>
|
|
79
|
-
<ApiClientProvider baseUrl="http://localhost:4000">
|
|
80
|
-
<TestComponentWithGeneration />
|
|
81
|
-
</ApiClientProvider>
|
|
82
|
-
</AuthTokenProvider>
|
|
83
|
-
</EventBusProvider>
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
describe('Detection Events Trigger Toasts', () => {
|
|
88
|
-
it('mark:assist-finished shows success toast', async () => {
|
|
89
|
-
renderDetectionTest();
|
|
90
|
-
|
|
91
|
-
// Clear any potential mount-related calls
|
|
92
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
93
|
-
mockShowSuccess.mockClear();
|
|
94
|
-
|
|
95
|
-
// Emit detection finished event (what SSE would emit)
|
|
96
|
-
act(() => {
|
|
97
|
-
eventBusInstance.get('mark:assist-finished').next({
|
|
98
|
-
motivation: 'linking' as any
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
// Wait for toast to be called
|
|
103
|
-
await waitFor(() => {
|
|
104
|
-
expect(mockShowSuccess).toHaveBeenCalledWith('Annotation complete');
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('mark:assist-failed shows error toast', async () => {
|
|
109
|
-
renderDetectionTest();
|
|
110
|
-
|
|
111
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
112
|
-
mockShowError.mockClear();
|
|
113
|
-
|
|
114
|
-
// Emit detection failed event
|
|
115
|
-
act(() => {
|
|
116
|
-
eventBusInstance.get('mark:assist-failed').next({
|
|
117
|
-
resourceId: 'test' as any,
|
|
118
|
-
message: 'AI service unavailable',
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
// Wait for toast to be called
|
|
123
|
-
await waitFor(() => {
|
|
124
|
-
expect(mockShowError).toHaveBeenCalledWith('AI service unavailable');
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
describe('Generation Events Trigger Toasts', () => {
|
|
130
|
-
it('yield:finished shows success toast', async () => {
|
|
131
|
-
renderGenerationTest();
|
|
132
|
-
|
|
133
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
134
|
-
mockShowSuccess.mockClear();
|
|
135
|
-
|
|
136
|
-
// Emit generation finished event
|
|
137
|
-
act(() => {
|
|
138
|
-
eventBusInstance.get('yield:finished').next({
|
|
139
|
-
status: 'complete',
|
|
140
|
-
message: 'Document generated successfully',
|
|
141
|
-
percentage: 100,
|
|
142
|
-
referenceId: 'ref-1',
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
// Wait for toast to be called
|
|
147
|
-
await waitFor(() => {
|
|
148
|
-
expect(mockShowSuccess).toHaveBeenCalledWith('Resource created successfully!');
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it('yield:failed shows error toast', async () => {
|
|
153
|
-
renderGenerationTest();
|
|
154
|
-
|
|
155
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
156
|
-
mockShowError.mockClear();
|
|
157
|
-
|
|
158
|
-
// Emit generation failed event
|
|
159
|
-
act(() => {
|
|
160
|
-
eventBusInstance.get('yield:failed').next({
|
|
161
|
-
error: 'Failed to generate document',
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
// Wait for toast to be called
|
|
166
|
-
await waitFor(() => {
|
|
167
|
-
expect(mockShowError).toHaveBeenCalledWith('Resource generation failed: Failed to generate document');
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
describe('Verification: Toast Mock Works Correctly', () => {
|
|
173
|
-
it('SANITY CHECK: can verify toast is NOT called', () => {
|
|
174
|
-
// This test proves our mock works correctly
|
|
175
|
-
expect(mockShowSuccess).not.toHaveBeenCalled();
|
|
176
|
-
expect(mockShowError).not.toHaveBeenCalled();
|
|
177
|
-
|
|
178
|
-
// If we call them, they should be tracked
|
|
179
|
-
mockShowSuccess('test');
|
|
180
|
-
mockShowError('test');
|
|
181
|
-
|
|
182
|
-
expect(mockShowSuccess).toHaveBeenCalledWith('test');
|
|
183
|
-
expect(mockShowError).toHaveBeenCalledWith('test');
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
});
|
|
@@ -1,429 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Layer 3: Feature Integration Test - Generation Flow Architecture
|
|
3
|
-
*
|
|
4
|
-
* Tests the COMPLETE generation flow with real component composition:
|
|
5
|
-
* - EventBusProvider (REAL)
|
|
6
|
-
* - ApiClientProvider (REAL, with MOCKED client)
|
|
7
|
-
* - useYieldFlow (REAL, with inlined progress state)
|
|
8
|
-
* - useBindFlow (REAL)
|
|
9
|
-
* - useEventSubscriptions (REAL)
|
|
10
|
-
*
|
|
11
|
-
* This test focuses on ARCHITECTURE and EVENT WIRING:
|
|
12
|
-
* - Verifies API called exactly ONCE (catches duplicate subscriptions)
|
|
13
|
-
* - Tests event propagation through the event bus
|
|
14
|
-
* - Validates modal workflow (open → submit → SSE stream)
|
|
15
|
-
* - Ensures generation progress updates correctly
|
|
16
|
-
* - Tests success/error handling
|
|
17
|
-
*
|
|
18
|
-
* NO BACKEND SERVER - only mocked API client boundary
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
22
|
-
import { render, screen, waitFor } from '@testing-library/react';
|
|
23
|
-
import { act } from 'react';
|
|
24
|
-
import { useYieldFlow } from '../../../hooks/useYieldFlow';
|
|
25
|
-
import { EventBusProvider, useEventBus } from '../../../contexts/EventBusContext';
|
|
26
|
-
import { ApiClientProvider } from '../../../contexts/ApiClientContext';
|
|
27
|
-
import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
|
|
28
|
-
import { useBindFlow } from '../../../hooks/useBindFlow';
|
|
29
|
-
import { SemiontApiClient } from '@semiont/api-client';
|
|
30
|
-
import type { AnnotationId, ResourceId } from '@semiont/core';
|
|
31
|
-
import { resourceId, annotationId } from '@semiont/core';
|
|
32
|
-
import type { EventMap } from '@semiont/core';
|
|
33
|
-
|
|
34
|
-
// Mock Toast module to prevent "useToast must be used within a ToastProvider" errors
|
|
35
|
-
vi.mock('../../../components/Toast', () => ({
|
|
36
|
-
useToast: () => ({
|
|
37
|
-
showSuccess: vi.fn(),
|
|
38
|
-
showError: vi.fn(),
|
|
39
|
-
showInfo: vi.fn(),
|
|
40
|
-
showWarning: vi.fn(),
|
|
41
|
-
}),
|
|
42
|
-
}));
|
|
43
|
-
|
|
44
|
-
describe('Generation Flow - Feature Integration', () => {
|
|
45
|
-
let generateResourceSpy: any;
|
|
46
|
-
let mockShowSuccess: ReturnType<typeof vi.fn>;
|
|
47
|
-
let mockShowError: ReturnType<typeof vi.fn>;
|
|
48
|
-
let mockCacheManager: { invalidate: ReturnType<typeof vi.fn> };
|
|
49
|
-
|
|
50
|
-
beforeEach(() => {
|
|
51
|
-
vi.clearAllMocks();
|
|
52
|
-
|
|
53
|
-
// Spy on SemiontApiClient prototype HTTP method (namespace methods call this)
|
|
54
|
-
generateResourceSpy = vi.spyOn(SemiontApiClient.prototype, 'yieldResourceFromAnnotation').mockResolvedValue({ correlationId: 'c1', jobId: 'j1' });
|
|
55
|
-
|
|
56
|
-
// Mock callbacks
|
|
57
|
-
mockShowSuccess = vi.fn();
|
|
58
|
-
mockShowError = vi.fn();
|
|
59
|
-
mockCacheManager = { invalidate: vi.fn() };
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
afterEach(() => {
|
|
63
|
-
vi.restoreAllMocks();
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('should call yieldResource exactly ONCE when generation starts', async () => {
|
|
67
|
-
const testResourceId = resourceId('test-resource');
|
|
68
|
-
const testAnnotationId = annotationId('test-annotation');
|
|
69
|
-
|
|
70
|
-
const { emitGenerationStart, getEventBus } = renderYieldFlow(
|
|
71
|
-
testResourceId
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
// Trigger generation with full options
|
|
75
|
-
act(() => {
|
|
76
|
-
emitGenerationStart(testAnnotationId, testResourceId, {
|
|
77
|
-
title: 'Generated Document',
|
|
78
|
-
prompt: 'Create a comprehensive document',
|
|
79
|
-
language: 'en',
|
|
80
|
-
temperature: 0.7,
|
|
81
|
-
maxTokens: 2000,
|
|
82
|
-
context: {
|
|
83
|
-
sourceText: 'Reference text from the document',
|
|
84
|
-
entityTypes: ['Person', 'Organization'],
|
|
85
|
-
},
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
// CRITICAL ASSERTION: API called exactly once (not twice!)
|
|
90
|
-
await waitFor(() => {
|
|
91
|
-
expect(generateResourceSpy).toHaveBeenCalledTimes(1);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
// Verify correct parameters — resourceId and annotationId are now bare IDs
|
|
95
|
-
expect(generateResourceSpy).toHaveBeenCalledWith(
|
|
96
|
-
testResourceId,
|
|
97
|
-
testAnnotationId,
|
|
98
|
-
expect.objectContaining({
|
|
99
|
-
title: 'Generated Document',
|
|
100
|
-
prompt: 'Create a comprehensive document',
|
|
101
|
-
language: 'en',
|
|
102
|
-
temperature: 0.7,
|
|
103
|
-
maxTokens: 2000,
|
|
104
|
-
}),
|
|
105
|
-
expect.objectContaining({ auth: undefined })
|
|
106
|
-
);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('should propagate SSE progress events to useYieldProgress state', async () => {
|
|
110
|
-
const testResourceId = resourceId('test-resource');
|
|
111
|
-
const testAnnotationId = annotationId('test-annotation');
|
|
112
|
-
|
|
113
|
-
const { emitGenerationStart, getEventBus } = renderYieldFlow(
|
|
114
|
-
testResourceId
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
// Start generation
|
|
118
|
-
act(() => {
|
|
119
|
-
emitGenerationStart(testAnnotationId, testResourceId, {
|
|
120
|
-
title: 'Test Doc',
|
|
121
|
-
context: { sourceText: 'test' },
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
// Wait for stream to be created
|
|
126
|
-
await waitFor(() => {
|
|
127
|
-
expect(generateResourceSpy).toHaveBeenCalled();
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
// Simulate SSE progress callback being invoked
|
|
131
|
-
act(() => {
|
|
132
|
-
getEventBus().get('yield:progress').next({
|
|
133
|
-
status: 'generating',
|
|
134
|
-
message: 'Generating content...',
|
|
135
|
-
percentage: 25,
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
// Verify progress propagated to UI
|
|
140
|
-
await waitFor(() => {
|
|
141
|
-
expect(screen.getByTestId('progress')).toHaveTextContent('Generating content...');
|
|
142
|
-
expect(screen.getByTestId('is-generating')).toHaveTextContent('true');
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it('should handle multiple progress updates correctly', async () => {
|
|
147
|
-
const testResourceId = resourceId('test-resource');
|
|
148
|
-
const testAnnotationId = annotationId('test-annotation');
|
|
149
|
-
|
|
150
|
-
const { emitGenerationStart, getEventBus } = renderYieldFlow(
|
|
151
|
-
testResourceId
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
// Start generation
|
|
155
|
-
act(() => {
|
|
156
|
-
emitGenerationStart(testAnnotationId, testResourceId, {
|
|
157
|
-
title: 'Test',
|
|
158
|
-
context: { sourceText: 'test' },
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
await waitFor(() => {
|
|
163
|
-
expect(generateResourceSpy).toHaveBeenCalledTimes(1);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
// First progress update
|
|
167
|
-
act(() => {
|
|
168
|
-
getEventBus().get('yield:progress').next({
|
|
169
|
-
status: 'started',
|
|
170
|
-
message: 'Starting generation...',
|
|
171
|
-
percentage: 0,
|
|
172
|
-
});
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
await waitFor(() => {
|
|
176
|
-
expect(screen.getByTestId('progress')).toHaveTextContent('Starting generation...');
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
// Second progress update
|
|
180
|
-
act(() => {
|
|
181
|
-
getEventBus().get('yield:progress').next({
|
|
182
|
-
status: 'generating',
|
|
183
|
-
message: 'Creating document structure...',
|
|
184
|
-
percentage: 50,
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
await waitFor(() => {
|
|
189
|
-
expect(screen.getByTestId('progress')).toHaveTextContent('Creating document structure...');
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
// Final progress update via onComplete
|
|
193
|
-
act(() => {
|
|
194
|
-
getEventBus().get('yield:finished').next({
|
|
195
|
-
status: 'complete',
|
|
196
|
-
referenceId: testAnnotationId,
|
|
197
|
-
message: 'Document created successfully',
|
|
198
|
-
percentage: 100,
|
|
199
|
-
resourceName: 'Generated Document',
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
await waitFor(() => {
|
|
204
|
-
expect(screen.getByTestId('progress')).toHaveTextContent('Document created successfully');
|
|
205
|
-
// Progress stays visible after completion (like detection flow)
|
|
206
|
-
});
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
it('should show success toast on generation complete', async () => {
|
|
210
|
-
const testResourceId = resourceId('test-resource');
|
|
211
|
-
const testAnnotationId = annotationId('test-annotation');
|
|
212
|
-
|
|
213
|
-
const { emitGenerationStart, getEventBus } = renderYieldFlow(
|
|
214
|
-
testResourceId
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
// Start generation
|
|
218
|
-
act(() => {
|
|
219
|
-
emitGenerationStart(testAnnotationId, testResourceId, {
|
|
220
|
-
title: 'Test',
|
|
221
|
-
context: { sourceText: 'test' },
|
|
222
|
-
});
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
await waitFor(() => {
|
|
226
|
-
expect(generateResourceSpy).toHaveBeenCalled();
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
// Simulate completion with final chunk
|
|
230
|
-
act(() => {
|
|
231
|
-
getEventBus().get('yield:progress').next({
|
|
232
|
-
status: 'complete',
|
|
233
|
-
message: 'Complete',
|
|
234
|
-
resourceName: 'Generated Document',
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
// Emit completion event
|
|
239
|
-
act(() => {
|
|
240
|
-
getEventBus().get('yield:finished').next({
|
|
241
|
-
status: 'complete',
|
|
242
|
-
referenceId: testAnnotationId,
|
|
243
|
-
resourceName: 'Generated Document',
|
|
244
|
-
percentage: 100,
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
// Verify generation completes successfully
|
|
249
|
-
// Note: Progress stays visible after completion (like detection flow)
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
it('should clear progress on generation failure', async () => {
|
|
253
|
-
const testResourceId = resourceId('test-resource');
|
|
254
|
-
const testAnnotationId = annotationId('test-annotation');
|
|
255
|
-
|
|
256
|
-
const { emitGenerationStart, getEventBus } = renderYieldFlow(
|
|
257
|
-
testResourceId
|
|
258
|
-
);
|
|
259
|
-
|
|
260
|
-
// Start generation
|
|
261
|
-
act(() => {
|
|
262
|
-
emitGenerationStart(testAnnotationId, testResourceId, {
|
|
263
|
-
title: 'Test',
|
|
264
|
-
context: { sourceText: 'test' },
|
|
265
|
-
});
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
// Add some progress
|
|
269
|
-
act(() => {
|
|
270
|
-
getEventBus().get('yield:progress').next({
|
|
271
|
-
status: 'generating',
|
|
272
|
-
message: 'Generating...',
|
|
273
|
-
});
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
await waitFor(() => {
|
|
277
|
-
expect(screen.getByTestId('progress')).toHaveTextContent('Generating...');
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
// Emit failure
|
|
281
|
-
act(() => {
|
|
282
|
-
getEventBus().get('yield:failed').next({ error: 'Network error' });
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
// Verify: progress cleared and not generating
|
|
286
|
-
await waitFor(() => {
|
|
287
|
-
expect(screen.getByTestId('is-generating')).toHaveTextContent('false');
|
|
288
|
-
expect(screen.getByTestId('progress')).toHaveTextContent('No progress');
|
|
289
|
-
});
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
it('should only call API once even with multiple renders', async () => {
|
|
293
|
-
const testResourceId = resourceId('test-resource');
|
|
294
|
-
const testAnnotationId = annotationId('test-annotation');
|
|
295
|
-
|
|
296
|
-
const { emitGenerationStart } = renderYieldFlow(
|
|
297
|
-
testResourceId
|
|
298
|
-
);
|
|
299
|
-
|
|
300
|
-
// Trigger generation
|
|
301
|
-
act(() => {
|
|
302
|
-
emitGenerationStart(testAnnotationId, testResourceId, {
|
|
303
|
-
title: 'Test',
|
|
304
|
-
context: { sourceText: 'test' },
|
|
305
|
-
});
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
// Wait for operation to complete
|
|
309
|
-
await waitFor(() => {
|
|
310
|
-
expect(generateResourceSpy).toHaveBeenCalled();
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
// VERIFY: API called exactly once
|
|
314
|
-
expect(generateResourceSpy).toHaveBeenCalledTimes(1);
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
it('should forward final chunk as progress before emitting complete', async () => {
|
|
318
|
-
const testResourceId = resourceId('test-resource');
|
|
319
|
-
const testAnnotationId = annotationId('test-annotation');
|
|
320
|
-
|
|
321
|
-
const { emitGenerationStart, getEventBus } = renderYieldFlow(
|
|
322
|
-
testResourceId
|
|
323
|
-
);
|
|
324
|
-
|
|
325
|
-
// Start generation
|
|
326
|
-
act(() => {
|
|
327
|
-
emitGenerationStart(testAnnotationId, testResourceId, {
|
|
328
|
-
title: 'Test',
|
|
329
|
-
context: { sourceText: 'test' },
|
|
330
|
-
});
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
await waitFor(() => {
|
|
334
|
-
expect(generateResourceSpy).toHaveBeenCalled();
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
// Simulate onComplete with final chunk
|
|
338
|
-
act(() => {
|
|
339
|
-
getEventBus().get('yield:finished').next({
|
|
340
|
-
status: 'complete',
|
|
341
|
-
referenceId: testAnnotationId,
|
|
342
|
-
message: 'Document created: My Document',
|
|
343
|
-
resourceName: 'My Document',
|
|
344
|
-
percentage: 100,
|
|
345
|
-
});
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
// Verify final chunk is visible as progress
|
|
349
|
-
await waitFor(() => {
|
|
350
|
-
expect(screen.getByTestId('progress')).toHaveTextContent('Document created: My Document');
|
|
351
|
-
// Progress stays visible after completion (like detection flow)
|
|
352
|
-
});
|
|
353
|
-
});
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* Helper: Render useYieldFlow hook with real component composition
|
|
358
|
-
* Returns methods to interact with the rendered component
|
|
359
|
-
*/
|
|
360
|
-
function renderYieldFlow(
|
|
361
|
-
testResourceId: ResourceId
|
|
362
|
-
) {
|
|
363
|
-
let eventBusInstance: ReturnType<typeof useEventBus>;
|
|
364
|
-
let generateFn: ReturnType<typeof useYieldFlow>['onGenerateDocument'];
|
|
365
|
-
|
|
366
|
-
// Component to capture EventBus instance and set up event operations
|
|
367
|
-
function EventBusCapture() {
|
|
368
|
-
eventBusInstance = useEventBus();
|
|
369
|
-
useBindFlow(testResourceId);
|
|
370
|
-
return null;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// Test harness component that uses the hook
|
|
374
|
-
function YieldFlowTestHarness() {
|
|
375
|
-
const {
|
|
376
|
-
isGenerating,
|
|
377
|
-
generationProgress,
|
|
378
|
-
onGenerateDocument,
|
|
379
|
-
} = useYieldFlow(
|
|
380
|
-
'en',
|
|
381
|
-
testResourceId,
|
|
382
|
-
vi.fn()
|
|
383
|
-
);
|
|
384
|
-
|
|
385
|
-
generateFn = onGenerateDocument;
|
|
386
|
-
|
|
387
|
-
return (
|
|
388
|
-
<div>
|
|
389
|
-
<div data-testid="is-generating">
|
|
390
|
-
{isGenerating ? 'true' : 'false'}
|
|
391
|
-
</div>
|
|
392
|
-
<div data-testid="progress">
|
|
393
|
-
{generationProgress?.message || 'No progress'}
|
|
394
|
-
</div>
|
|
395
|
-
</div>
|
|
396
|
-
);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
render(
|
|
400
|
-
<EventBusProvider>
|
|
401
|
-
<AuthTokenProvider token={null}>
|
|
402
|
-
<ApiClientProvider baseUrl="http://localhost:4000">
|
|
403
|
-
<EventBusCapture />
|
|
404
|
-
<YieldFlowTestHarness />
|
|
405
|
-
</ApiClientProvider>
|
|
406
|
-
</AuthTokenProvider>
|
|
407
|
-
</EventBusProvider>
|
|
408
|
-
);
|
|
409
|
-
|
|
410
|
-
return {
|
|
411
|
-
emitGenerationStart: (
|
|
412
|
-
aId: AnnotationId,
|
|
413
|
-
_rId: ResourceId,
|
|
414
|
-
options: {
|
|
415
|
-
title: string;
|
|
416
|
-
storageUri?: string;
|
|
417
|
-
prompt?: string;
|
|
418
|
-
language?: string;
|
|
419
|
-
temperature?: number;
|
|
420
|
-
maxTokens?: number;
|
|
421
|
-
context: any;
|
|
422
|
-
}
|
|
423
|
-
) => {
|
|
424
|
-
// Call the hook's callback directly (no longer EventBus-driven)
|
|
425
|
-
generateFn(aId as string, { storageUri: options.storageUri ?? 'file:///tmp/test', ...options });
|
|
426
|
-
},
|
|
427
|
-
getEventBus: () => eventBusInstance,
|
|
428
|
-
};
|
|
429
|
-
}
|