@semiont/react-ui 0.4.7 → 0.4.10
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-LF6DDTCV.mjs → PdfAnnotationCanvas.client-CW6SKH2U.mjs} +7 -25
- package/dist/PdfAnnotationCanvas.client-CW6SKH2U.mjs.map +1 -0
- package/dist/{EventBusContext-DUIMowqQ.d.mts → TranslationManager-CudgH3gw.d.mts} +1 -74
- package/dist/{ar-3URRW77J.mjs → ar-R4CRNXEF.mjs} +3 -2
- package/dist/ar-R4CRNXEF.mjs.map +1 -0
- package/dist/{bn-DCQD3XZ5.mjs → bn-CZKGRHTA.mjs} +3 -2
- package/dist/bn-CZKGRHTA.mjs.map +1 -0
- package/dist/{chunk-XMCUHQ2Y.mjs → chunk-BQJWOK4C.mjs} +15 -34
- package/dist/chunk-BQJWOK4C.mjs.map +1 -0
- package/dist/{chunk-5JZFKRLW.mjs → chunk-HNZOXH4L.mjs} +33 -35
- package/dist/chunk-HNZOXH4L.mjs.map +1 -0
- package/dist/{chunk-PWIVZQ4X.mjs → chunk-HVMAGUFA.mjs} +3 -2
- package/dist/chunk-HVMAGUFA.mjs.map +1 -0
- package/dist/{chunk-4RMWYJUJ.mjs → chunk-OL5UST25.mjs} +31 -31
- package/dist/{cs-23KOZUFE.mjs → cs-4WIB2IHH.mjs} +3 -2
- package/dist/cs-4WIB2IHH.mjs.map +1 -0
- package/dist/{da-OIQ66A42.mjs → da-JWYEUYPX.mjs} +3 -2
- package/dist/da-JWYEUYPX.mjs.map +1 -0
- package/dist/{de-FCCLKE2X.mjs → de-GWUQZGER.mjs} +3 -2
- package/dist/de-GWUQZGER.mjs.map +1 -0
- package/dist/{el-3ADITCGI.mjs → el-DM2GT7P5.mjs} +3 -2
- package/dist/el-DM2GT7P5.mjs.map +1 -0
- package/dist/{en-LNW2A3RA.mjs → en-IUV4ZXKH.mjs} +2 -2
- package/dist/{es-POQEEYIW.mjs → es-6LVQIM3D.mjs} +3 -2
- package/dist/es-6LVQIM3D.mjs.map +1 -0
- package/dist/{fa-RQPXVELG.mjs → fa-IRUJY3QI.mjs} +3 -2
- package/dist/fa-IRUJY3QI.mjs.map +1 -0
- package/dist/{fi-UXOVOUGT.mjs → fi-53FBOEVT.mjs} +3 -2
- package/dist/fi-53FBOEVT.mjs.map +1 -0
- package/dist/{fr-6W2T3R7G.mjs → fr-Q5KY7QL6.mjs} +3 -2
- package/dist/fr-Q5KY7QL6.mjs.map +1 -0
- package/dist/{he-65UHPZIU.mjs → he-HJNKULBY.mjs} +3 -2
- package/dist/he-HJNKULBY.mjs.map +1 -0
- package/dist/{hi-SGJIVPTN.mjs → hi-UYZ4X6CR.mjs} +3 -2
- package/dist/hi-UYZ4X6CR.mjs.map +1 -0
- package/dist/{id-EYJJQCS2.mjs → id-UAQMH6U2.mjs} +3 -2
- package/dist/id-UAQMH6U2.mjs.map +1 -0
- package/dist/index.css +48 -0
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +176 -125
- package/dist/index.mjs +556 -781
- package/dist/index.mjs.map +1 -1
- package/dist/{it-IZGQEDO7.mjs → it-C7QEBNFA.mjs} +3 -2
- package/dist/it-C7QEBNFA.mjs.map +1 -0
- package/dist/{ja-SR272JSY.mjs → ja-THS6AOSJ.mjs} +3 -2
- package/dist/ja-THS6AOSJ.mjs.map +1 -0
- package/dist/{ko-YWTXVVXE.mjs → ko-XKK3TWQG.mjs} +3 -2
- package/dist/ko-XKK3TWQG.mjs.map +1 -0
- package/dist/{ms-3K2XSJGM.mjs → ms-GSK7LIF7.mjs} +3 -2
- package/dist/ms-GSK7LIF7.mjs.map +1 -0
- package/dist/{nl-YIGP4SLE.mjs → nl-KUBWITGY.mjs} +3 -2
- package/dist/nl-KUBWITGY.mjs.map +1 -0
- package/dist/{no-IFYIL3ND.mjs → no-ECWZUHT6.mjs} +3 -2
- package/dist/no-ECWZUHT6.mjs.map +1 -0
- package/dist/{pl-6MWSASJR.mjs → pl-PLVWSZWS.mjs} +3 -2
- package/dist/pl-PLVWSZWS.mjs.map +1 -0
- package/dist/{pt-NZNN6WUN.mjs → pt-AL74ZTKB.mjs} +3 -2
- package/dist/pt-AL74ZTKB.mjs.map +1 -0
- package/dist/{ro-NF3SMUJS.mjs → ro-WTPHLHGS.mjs} +3 -2
- package/dist/ro-WTPHLHGS.mjs.map +1 -0
- package/dist/{sv-ZHM7GSTD.mjs → sv-QCLI7SG4.mjs} +3 -2
- package/dist/sv-QCLI7SG4.mjs.map +1 -0
- package/dist/test-utils.d.mts +1 -4
- package/dist/test-utils.mjs +4 -6
- package/dist/test-utils.mjs.map +1 -1
- package/dist/{th-LX4NO5BJ.mjs → th-WCKVZU6U.mjs} +3 -2
- package/dist/th-WCKVZU6U.mjs.map +1 -0
- package/dist/{tr-DZ4GDSRR.mjs → tr-2CAFS2XS.mjs} +3 -2
- package/dist/tr-2CAFS2XS.mjs.map +1 -0
- package/dist/{uk-KC5KVVBY.mjs → uk-TDE4JLCY.mjs} +3 -2
- package/dist/uk-TDE4JLCY.mjs.map +1 -0
- package/dist/{vi-KNCR3OXZ.mjs → vi-KKXZ4PCX.mjs} +3 -2
- package/dist/vi-KKXZ4PCX.mjs.map +1 -0
- package/dist/{zh-M2HV2A27.mjs → zh-VH4XN5PV.mjs} +3 -2
- package/dist/zh-VH4XN5PV.mjs.map +1 -0
- package/package.json +1 -1
- package/src/components/Toolbar.tsx +15 -0
- package/src/components/__tests__/AnnotateReferencesProgressWidget.test.tsx +2 -6
- package/src/components/__tests__/Toolbar.test.tsx +2 -6
- package/src/components/annotation/__tests__/AnnotateToolbar.test.tsx +1 -2
- package/src/components/image-annotation/SvgDrawingCanvas.tsx +4 -7
- package/src/components/modals/ConfigureGenerationStep.tsx +54 -60
- package/src/components/modals/ReferenceWizardModal.tsx +3 -3
- package/src/components/navigation/__tests__/ObservableLink.test.tsx +2 -6
- package/src/components/navigation/__tests__/SimpleNavigation.test.tsx +2 -6
- package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +3 -9
- package/src/components/resource/AnnotateView.tsx +4 -5
- package/src/components/resource/AnnotationHistory.tsx +2 -5
- package/src/components/resource/BrowseView.tsx +2 -3
- package/src/components/resource/HistoryEvent.tsx +3 -3
- package/src/components/resource/__tests__/BrowseView.test.tsx +1 -2
- package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +8 -8
- package/src/components/resource/event-formatting.ts +22 -19
- package/src/components/resource/panels/__tests__/AssessmentEntry.test.tsx +1 -2
- package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +1 -2
- package/src/components/resource/panels/__tests__/AssistSection.test.tsx +0 -2
- package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +1 -2
- package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +1 -2
- package/src/components/resource/panels/__tests__/HighlightEntry.test.tsx +1 -2
- package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +0 -2
- package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +1 -2
- package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +1 -2
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +1 -2
- package/src/components/resource/panels/__tests__/TagEntry.test.tsx +1 -2
- package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +1 -2
- package/src/components/settings/__tests__/SettingsPanel.test.tsx +1 -2
- package/src/components/viewers/ImageViewer.tsx +2 -8
- package/src/components/viewers/__tests__/ImageViewer.test.tsx +3 -16
- package/src/features/auth/__tests__/SignInForm.a11y.test.tsx +8 -0
- package/src/features/auth/auth.css +62 -0
- package/src/features/auth/components/SignInForm.tsx +139 -29
- package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +2 -6
- package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +1 -2
- package/src/features/resource-viewer/__tests__/AnnotationCreationPending.test.tsx +1 -2
- package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +1 -2
- package/src/features/resource-viewer/__tests__/AnnotationProgressDismissal.test.tsx +1 -2
- package/src/features/resource-viewer/__tests__/BindFlowIntegration.test.tsx +1 -2
- package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +1 -2
- package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +1 -2
- package/src/features/resource-viewer/__tests__/ResourceMutations.test.tsx +9 -10
- package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +9 -6
- package/src/features/resource-viewer/__tests__/ToastNotifications.test.tsx +2 -12
- package/src/features/resource-viewer/__tests__/YieldFlowIntegration.test.tsx +1 -2
- package/src/features/resource-viewer/__tests__/annotation-progress-flow.test.tsx +16 -6
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +45 -75
- package/src/styles/core/forms.css +32 -0
- package/src/styles/core/sliders.css +5 -0
- package/translations/ar.json +3 -2
- package/translations/bn.json +3 -2
- package/translations/cs.json +3 -2
- package/translations/da.json +3 -2
- package/translations/de.json +3 -2
- package/translations/el.json +3 -2
- package/translations/en.json +4 -3
- package/translations/es.json +3 -2
- package/translations/fa.json +3 -2
- package/translations/fi.json +3 -2
- package/translations/fr.json +3 -2
- package/translations/he.json +3 -2
- package/translations/hi.json +3 -2
- package/translations/id.json +3 -2
- package/translations/it.json +3 -2
- package/translations/ja.json +3 -2
- package/translations/ko.json +3 -2
- package/translations/ms.json +3 -2
- package/translations/nl.json +3 -2
- package/translations/no.json +3 -2
- package/translations/pl.json +3 -2
- package/translations/pt.json +3 -2
- package/translations/ro.json +3 -2
- package/translations/sv.json +3 -2
- package/translations/th.json +3 -2
- package/translations/tr.json +3 -2
- package/translations/uk.json +3 -2
- package/translations/vi.json +3 -2
- package/translations/zh.json +3 -2
- package/dist/PdfAnnotationCanvas.client-LF6DDTCV.mjs.map +0 -1
- package/dist/ar-3URRW77J.mjs.map +0 -1
- package/dist/bn-DCQD3XZ5.mjs.map +0 -1
- package/dist/chunk-5JZFKRLW.mjs.map +0 -1
- package/dist/chunk-PWIVZQ4X.mjs.map +0 -1
- package/dist/chunk-XMCUHQ2Y.mjs.map +0 -1
- package/dist/cs-23KOZUFE.mjs.map +0 -1
- package/dist/da-OIQ66A42.mjs.map +0 -1
- package/dist/de-FCCLKE2X.mjs.map +0 -1
- package/dist/el-3ADITCGI.mjs.map +0 -1
- package/dist/es-POQEEYIW.mjs.map +0 -1
- package/dist/fa-RQPXVELG.mjs.map +0 -1
- package/dist/fi-UXOVOUGT.mjs.map +0 -1
- package/dist/fr-6W2T3R7G.mjs.map +0 -1
- package/dist/he-65UHPZIU.mjs.map +0 -1
- package/dist/hi-SGJIVPTN.mjs.map +0 -1
- package/dist/id-EYJJQCS2.mjs.map +0 -1
- package/dist/it-IZGQEDO7.mjs.map +0 -1
- package/dist/ja-SR272JSY.mjs.map +0 -1
- package/dist/ko-YWTXVVXE.mjs.map +0 -1
- package/dist/ms-3K2XSJGM.mjs.map +0 -1
- package/dist/nl-YIGP4SLE.mjs.map +0 -1
- package/dist/no-IFYIL3ND.mjs.map +0 -1
- package/dist/pl-6MWSASJR.mjs.map +0 -1
- package/dist/pt-NZNN6WUN.mjs.map +0 -1
- package/dist/ro-NF3SMUJS.mjs.map +0 -1
- package/dist/sv-ZHM7GSTD.mjs.map +0 -1
- package/dist/th-LX4NO5BJ.mjs.map +0 -1
- package/dist/tr-DZ4GDSRR.mjs.map +0 -1
- package/dist/uk-KC5KVVBY.mjs.map +0 -1
- package/dist/vi-KNCR3OXZ.mjs.map +0 -1
- package/dist/zh-M2HV2A27.mjs.map +0 -1
- /package/dist/{chunk-4RMWYJUJ.mjs.map → chunk-OL5UST25.mjs.map} +0 -0
- /package/dist/{en-LNW2A3RA.mjs.map → en-IUV4ZXKH.mjs.map} +0 -0
|
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { screen } from '@testing-library/react';
|
|
4
4
|
import '@testing-library/jest-dom';
|
|
5
|
-
import { renderWithProviders
|
|
5
|
+
import { renderWithProviders } from '../../../../test-utils';
|
|
6
6
|
import userEvent from '@testing-library/user-event';
|
|
7
7
|
import type { components } from '@semiont/core';
|
|
8
8
|
import type { RouteBuilder } from '../../../../contexts/RoutingContext';
|
|
@@ -86,7 +86,6 @@ describe('ReferenceEntry', () => {
|
|
|
86
86
|
|
|
87
87
|
beforeEach(() => {
|
|
88
88
|
vi.clearAllMocks();
|
|
89
|
-
resetEventBusForTesting();
|
|
90
89
|
mockGetAnnotationExactText.mockReturnValue('referenced text');
|
|
91
90
|
mockIsBodyResolved.mockReturnValue(false);
|
|
92
91
|
mockGetBodySource.mockReturnValue(null);
|
|
@@ -4,7 +4,7 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
|
4
4
|
import userEvent from '@testing-library/user-event';
|
|
5
5
|
import '@testing-library/jest-dom';
|
|
6
6
|
import { ReferencesPanel } from '../ReferencesPanel';
|
|
7
|
-
import { EventBusProvider,
|
|
7
|
+
import { EventBusProvider, useEventBus } from '../../../../contexts/EventBusContext';
|
|
8
8
|
|
|
9
9
|
// Composition-based event tracker
|
|
10
10
|
interface TrackedEvent {
|
|
@@ -127,7 +127,6 @@ describe('ReferencesPanel Component', () => {
|
|
|
127
127
|
};
|
|
128
128
|
|
|
129
129
|
beforeEach(() => {
|
|
130
|
-
resetEventBusForTesting();
|
|
131
130
|
vi.clearAllMocks();
|
|
132
131
|
});
|
|
133
132
|
|
|
@@ -3,7 +3,7 @@ import React from 'react';
|
|
|
3
3
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
4
4
|
import '@testing-library/jest-dom';
|
|
5
5
|
import { ResourceInfoPanel } from '../ResourceInfoPanel';
|
|
6
|
-
import { EventBusProvider,
|
|
6
|
+
import { EventBusProvider, useEventBus } from '../../../../contexts/EventBusContext';
|
|
7
7
|
|
|
8
8
|
// Mock TranslationContext
|
|
9
9
|
vi.mock('../../../../contexts/TranslationContext', () => ({
|
|
@@ -129,7 +129,6 @@ describe('ResourceInfoPanel Component', () => {
|
|
|
129
129
|
};
|
|
130
130
|
|
|
131
131
|
beforeEach(() => {
|
|
132
|
-
resetEventBusForTesting();
|
|
133
132
|
vi.clearAllMocks();
|
|
134
133
|
});
|
|
135
134
|
|
|
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { screen } from '@testing-library/react';
|
|
4
4
|
import '@testing-library/jest-dom';
|
|
5
|
-
import { renderWithProviders
|
|
5
|
+
import { renderWithProviders } from '../../../../test-utils';
|
|
6
6
|
import userEvent from '@testing-library/user-event';
|
|
7
7
|
import type { components } from '@semiont/core';
|
|
8
8
|
|
|
@@ -73,7 +73,6 @@ describe('TagEntry', () => {
|
|
|
73
73
|
|
|
74
74
|
beforeEach(() => {
|
|
75
75
|
vi.clearAllMocks();
|
|
76
|
-
resetEventBusForTesting();
|
|
77
76
|
mockGetAnnotationExactText.mockReturnValue('Tagged text content');
|
|
78
77
|
mockGetTagCategory.mockReturnValue('Entity');
|
|
79
78
|
mockGetTagSchemaId.mockReturnValue(null);
|
|
@@ -5,7 +5,7 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
|
5
5
|
import userEvent from '@testing-library/user-event';
|
|
6
6
|
import '@testing-library/jest-dom';
|
|
7
7
|
import { TaggingPanel } from '../TaggingPanel';
|
|
8
|
-
import { EventBusProvider,
|
|
8
|
+
import { EventBusProvider, useEventBus } from '../../../../contexts/EventBusContext';
|
|
9
9
|
import type { components } from '@semiont/core';
|
|
10
10
|
|
|
11
11
|
type Annotation = components['schemas']['Annotation'];
|
|
@@ -203,7 +203,6 @@ describe('TaggingPanel Component', () => {
|
|
|
203
203
|
};
|
|
204
204
|
|
|
205
205
|
beforeEach(() => {
|
|
206
|
-
resetEventBusForTesting();
|
|
207
206
|
vi.clearAllMocks();
|
|
208
207
|
|
|
209
208
|
// Mock scrollIntoView for jsdom
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { screen, fireEvent } from '@testing-library/react';
|
|
4
|
-
import { renderWithProviders
|
|
4
|
+
import { renderWithProviders } from '../../../test-utils';
|
|
5
5
|
import '@testing-library/jest-dom';
|
|
6
6
|
import { SettingsPanel } from '../SettingsPanel';
|
|
7
7
|
|
|
@@ -36,7 +36,6 @@ describe('SettingsPanel', () => {
|
|
|
36
36
|
|
|
37
37
|
beforeEach(() => {
|
|
38
38
|
vi.clearAllMocks();
|
|
39
|
-
resetEventBusForTesting();
|
|
40
39
|
});
|
|
41
40
|
|
|
42
41
|
it('renders settings title', () => {
|
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
import type { ResourceId } from '@semiont/core';
|
|
2
|
-
|
|
3
1
|
interface ImageViewerProps {
|
|
4
|
-
|
|
2
|
+
imageUrl: string;
|
|
5
3
|
mimeType: string;
|
|
6
4
|
alt?: string;
|
|
7
5
|
}
|
|
8
6
|
|
|
9
|
-
export function ImageViewer({
|
|
10
|
-
// Use Next.js API route proxy instead of direct backend call
|
|
11
|
-
// This allows us to add authentication headers which <img> tags can't send
|
|
12
|
-
const imageUrl = `/api/resources/${resourceUri}`;
|
|
13
|
-
|
|
7
|
+
export function ImageViewer({ imageUrl, alt = 'Resource image' }: ImageViewerProps) {
|
|
14
8
|
return (
|
|
15
9
|
<div className="semiont-image-viewer">
|
|
16
10
|
<img
|
|
@@ -4,19 +4,18 @@ import { screen } from '@testing-library/react';
|
|
|
4
4
|
import '@testing-library/jest-dom';
|
|
5
5
|
import { renderWithProviders } from '../../../test-utils';
|
|
6
6
|
import { ImageViewer } from '../ImageViewer';
|
|
7
|
-
import type { ResourceId } from '@semiont/core';
|
|
8
7
|
|
|
9
8
|
describe('ImageViewer', () => {
|
|
10
9
|
const defaultProps = {
|
|
11
|
-
|
|
10
|
+
imageUrl: 'http://localhost:4000/resources/abc-123?token=tok',
|
|
12
11
|
mimeType: 'image/png',
|
|
13
12
|
};
|
|
14
13
|
|
|
15
|
-
it('should render an img element with
|
|
14
|
+
it('should render an img element with the provided src', () => {
|
|
16
15
|
renderWithProviders(<ImageViewer {...defaultProps} />);
|
|
17
16
|
|
|
18
17
|
const img = screen.getByRole('img');
|
|
19
|
-
expect(img).toHaveAttribute('src',
|
|
18
|
+
expect(img).toHaveAttribute('src', defaultProps.imageUrl);
|
|
20
19
|
});
|
|
21
20
|
|
|
22
21
|
it('should use default alt text when none provided', () => {
|
|
@@ -35,18 +34,6 @@ describe('ImageViewer', () => {
|
|
|
35
34
|
expect(img).toHaveAttribute('alt', 'A beautiful diagram');
|
|
36
35
|
});
|
|
37
36
|
|
|
38
|
-
it('should extract the last segment of the URI as resource ID', () => {
|
|
39
|
-
renderWithProviders(
|
|
40
|
-
<ImageViewer
|
|
41
|
-
resourceUri={'resource-xyz' as ResourceId}
|
|
42
|
-
mimeType="image/jpeg"
|
|
43
|
-
/>
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
const img = screen.getByRole('img');
|
|
47
|
-
expect(img).toHaveAttribute('src', '/api/resources/resource-xyz');
|
|
48
|
-
});
|
|
49
|
-
|
|
50
37
|
it('should render with correct class names', () => {
|
|
51
38
|
const { container } = renderWithProviders(<ImageViewer {...defaultProps} />);
|
|
52
39
|
|
|
@@ -29,6 +29,8 @@ const mockTranslations = {
|
|
|
29
29
|
welcomeBack: 'Welcome back to Semiont',
|
|
30
30
|
signInPrompt: 'Sign in to your knowledge workspace',
|
|
31
31
|
continueWithGoogle: 'Continue with Google',
|
|
32
|
+
backendUrlLabel: 'Backend URL',
|
|
33
|
+
backendUrlPlaceholder: 'https://your-semiont-server.com',
|
|
32
34
|
emailLabel: 'Email',
|
|
33
35
|
emailPlaceholder: 'your@email.com',
|
|
34
36
|
passwordLabel: 'Password',
|
|
@@ -42,6 +44,7 @@ const mockTranslations = {
|
|
|
42
44
|
signUpInstead: 'Sign Up Instead',
|
|
43
45
|
errorEmailRequired: 'Email is required',
|
|
44
46
|
errorPasswordRequired: 'Password is required',
|
|
47
|
+
errorBackendUrlRequired: 'Backend URL is required',
|
|
45
48
|
tagline: 'make meaning',
|
|
46
49
|
};
|
|
47
50
|
|
|
@@ -53,6 +56,7 @@ describe('SignInForm - Accessibility', () => {
|
|
|
53
56
|
const { container } = render(
|
|
54
57
|
<SignInForm
|
|
55
58
|
onGoogleSignIn={onGoogleSignIn}
|
|
59
|
+
backendUrl="http://localhost:4000"
|
|
56
60
|
Link={MockLink}
|
|
57
61
|
translations={mockTranslations}
|
|
58
62
|
/>
|
|
@@ -200,6 +204,7 @@ describe('SignInForm - Accessibility', () => {
|
|
|
200
204
|
render(
|
|
201
205
|
<SignInForm
|
|
202
206
|
onGoogleSignIn={onGoogleSignIn}
|
|
207
|
+
backendUrl="http://localhost:4000"
|
|
203
208
|
Link={MockLink}
|
|
204
209
|
translations={mockTranslations}
|
|
205
210
|
/>
|
|
@@ -257,6 +262,7 @@ describe('SignInForm - Accessibility', () => {
|
|
|
257
262
|
render(
|
|
258
263
|
<SignInForm
|
|
259
264
|
onGoogleSignIn={onGoogleSignIn}
|
|
265
|
+
backendUrl="http://localhost:4000"
|
|
260
266
|
Link={MockLink}
|
|
261
267
|
translations={mockTranslations}
|
|
262
268
|
/>
|
|
@@ -374,6 +380,7 @@ describe('SignInForm - Accessibility', () => {
|
|
|
374
380
|
render(
|
|
375
381
|
<SignInForm
|
|
376
382
|
onGoogleSignIn={onGoogleSignIn}
|
|
383
|
+
backendUrl="http://localhost:4000"
|
|
377
384
|
Link={MockLink}
|
|
378
385
|
translations={mockTranslations}
|
|
379
386
|
/>
|
|
@@ -391,6 +398,7 @@ describe('SignInForm - Accessibility', () => {
|
|
|
391
398
|
const { container } = render(
|
|
392
399
|
<SignInForm
|
|
393
400
|
onGoogleSignIn={onGoogleSignIn}
|
|
401
|
+
backendUrl="http://localhost:4000"
|
|
394
402
|
Link={MockLink}
|
|
395
403
|
translations={mockTranslations}
|
|
396
404
|
/>
|
|
@@ -307,3 +307,65 @@
|
|
|
307
307
|
white-space: nowrap;
|
|
308
308
|
border-width: 0;
|
|
309
309
|
}
|
|
310
|
+
|
|
311
|
+
/* URL field with inline probe indicator */
|
|
312
|
+
.semiont-form__input-row {
|
|
313
|
+
display: flex;
|
|
314
|
+
align-items: center;
|
|
315
|
+
gap: 0.5rem;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.semiont-form__input-row:focus-visible {
|
|
319
|
+
outline: 2px solid var(--semiont-focus-ring, #3b82f6);
|
|
320
|
+
outline-offset: 2px;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
[data-theme="dark"] .semiont-form__input-row:focus-visible {
|
|
324
|
+
outline-color: var(--semiont-focus-ring, #60a5fa);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.semiont-form__input-row .semiont-input {
|
|
328
|
+
flex: 1;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.semiont-form__input-row .semiont-input:focus-visible {
|
|
332
|
+
outline: 2px solid var(--semiont-focus-ring, #3b82f6);
|
|
333
|
+
outline-offset: 2px;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
[data-theme="dark"] .semiont-form__input-row .semiont-input:focus-visible {
|
|
337
|
+
outline-color: var(--semiont-focus-ring, #60a5fa);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.semiont-form__url-status {
|
|
341
|
+
flex-shrink: 0;
|
|
342
|
+
font-size: 1rem;
|
|
343
|
+
width: 1.25rem;
|
|
344
|
+
text-align: center;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.semiont-form__url-status--checking {
|
|
348
|
+
color: var(--semiont-text-muted, #888888);
|
|
349
|
+
animation: spin 1s linear infinite;
|
|
350
|
+
display: inline-block;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
[data-theme="dark"] .semiont-form__url-status--checking {
|
|
354
|
+
color: var(--semiont-text-muted, #888888);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.semiont-form__url-status--ok {
|
|
358
|
+
color: var(--semiont-success, #22c55e);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
[data-theme="dark"] .semiont-form__url-status--ok {
|
|
362
|
+
color: var(--semiont-success, #22c55e);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.semiont-form__url-status--error {
|
|
366
|
+
color: var(--semiont-error, #ef4444);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
[data-theme="dark"] .semiont-form__url-status--error {
|
|
370
|
+
color: var(--semiont-error, #ef4444);
|
|
371
|
+
}
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Supports both Google OAuth and email/password credentials.
|
|
5
5
|
* No Next.js dependencies - all data via props.
|
|
6
|
+
*
|
|
7
|
+
* When backendUrl is provided it is shown as a locked read-only field.
|
|
8
|
+
* When backendUrl is omitted the user must enter a backend URL to connect to.
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
11
|
import React from 'react';
|
|
@@ -11,14 +14,22 @@ import '../auth.css';
|
|
|
11
14
|
|
|
12
15
|
export interface SignInFormProps {
|
|
13
16
|
/**
|
|
14
|
-
* Callback when user clicks Google sign-in
|
|
17
|
+
* Callback when user clicks Google sign-in.
|
|
18
|
+
* Receives the backend URL (either the locked one or what the user typed).
|
|
19
|
+
*/
|
|
20
|
+
onGoogleSignIn: (backendUrl: string) => Promise<void>;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Callback when user submits email/password credentials.
|
|
24
|
+
* Receives the backend URL along with email and password.
|
|
15
25
|
*/
|
|
16
|
-
|
|
26
|
+
onCredentialsSignIn?: ((backendUrl: string, email: string, password: string) => Promise<void>) | undefined;
|
|
17
27
|
|
|
18
28
|
/**
|
|
19
|
-
*
|
|
29
|
+
* Pre-filled backend URL. When provided the field is read-only (re-auth to known workspace).
|
|
30
|
+
* When omitted the user enters the URL themselves (new connection).
|
|
20
31
|
*/
|
|
21
|
-
|
|
32
|
+
backendUrl?: string;
|
|
22
33
|
|
|
23
34
|
/**
|
|
24
35
|
* Error message to display (if any)
|
|
@@ -48,6 +59,8 @@ export interface SignInFormProps {
|
|
|
48
59
|
welcomeBack: string;
|
|
49
60
|
signInPrompt: string;
|
|
50
61
|
continueWithGoogle: string;
|
|
62
|
+
backendUrlLabel: string;
|
|
63
|
+
backendUrlPlaceholder: string;
|
|
51
64
|
emailLabel: string;
|
|
52
65
|
emailPlaceholder: string;
|
|
53
66
|
passwordLabel: string;
|
|
@@ -59,6 +72,9 @@ export interface SignInFormProps {
|
|
|
59
72
|
backToHome: string;
|
|
60
73
|
learnMore: string;
|
|
61
74
|
signUpInstead: string;
|
|
75
|
+
errorBackendUrlRequired: string;
|
|
76
|
+
errorBackendUrlInvalid: string;
|
|
77
|
+
errorBackendUrlUnreachable: string;
|
|
62
78
|
errorEmailRequired: string;
|
|
63
79
|
errorPasswordRequired: string;
|
|
64
80
|
tagline: string;
|
|
@@ -91,43 +107,87 @@ function GoogleIcon() {
|
|
|
91
107
|
);
|
|
92
108
|
}
|
|
93
109
|
|
|
110
|
+
type UrlProbeStatus = 'idle' | 'checking' | 'ok' | 'error';
|
|
111
|
+
|
|
112
|
+
function normalizeUrlForProbe(raw: string): string | null {
|
|
113
|
+
const trimmed = raw.trim();
|
|
114
|
+
if (!trimmed) return null;
|
|
115
|
+
if (/^https?:\/\//i.test(trimmed)) return trimmed;
|
|
116
|
+
return `http://${trimmed}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
94
119
|
/**
|
|
95
|
-
* CredentialsAuthForm -
|
|
120
|
+
* CredentialsAuthForm - Backend URL + email/password form.
|
|
121
|
+
* When lockedBackendUrl is provided, the URL field is shown read-only.
|
|
96
122
|
*/
|
|
97
123
|
function CredentialsAuthForm({
|
|
124
|
+
lockedBackendUrl,
|
|
98
125
|
onSubmit,
|
|
99
126
|
translations: t,
|
|
100
127
|
}: {
|
|
101
|
-
|
|
128
|
+
lockedBackendUrl: string | undefined;
|
|
129
|
+
onSubmit: (backendUrl: string, email: string, password: string) => Promise<void>;
|
|
102
130
|
translations: SignInFormProps['translations'];
|
|
103
131
|
}) {
|
|
132
|
+
const [backendUrl, setBackendUrl] = React.useState(lockedBackendUrl ?? '');
|
|
104
133
|
const [email, setEmail] = React.useState('');
|
|
105
134
|
const [password, setPassword] = React.useState('');
|
|
106
135
|
const [validationError, setValidationError] = React.useState<string | null>(null);
|
|
107
|
-
const [fieldErrors, setFieldErrors] = React.useState<{ email?: string; password?: string }>({});
|
|
136
|
+
const [fieldErrors, setFieldErrors] = React.useState<{ backendUrl?: string; email?: string; password?: string }>({});
|
|
137
|
+
const [urlProbeStatus, setUrlProbeStatus] = React.useState<UrlProbeStatus>('idle');
|
|
138
|
+
|
|
139
|
+
const probeUrl = async (raw: string) => {
|
|
140
|
+
const url = normalizeUrlForProbe(raw);
|
|
141
|
+
if (!url) return;
|
|
142
|
+
// Validate format
|
|
143
|
+
try { new URL(url); } catch {
|
|
144
|
+
setFieldErrors(prev => ({ ...prev, backendUrl: t.errorBackendUrlInvalid }));
|
|
145
|
+
setUrlProbeStatus('error');
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
setUrlProbeStatus('checking');
|
|
149
|
+
try {
|
|
150
|
+
const res = await fetch(`${url}/api/health`, { signal: AbortSignal.timeout(5000) });
|
|
151
|
+
setUrlProbeStatus(res.ok ? 'ok' : 'error');
|
|
152
|
+
if (!res.ok) {
|
|
153
|
+
setFieldErrors(prev => ({ ...prev, backendUrl: t.errorBackendUrlUnreachable }));
|
|
154
|
+
}
|
|
155
|
+
} catch {
|
|
156
|
+
setUrlProbeStatus('error');
|
|
157
|
+
setFieldErrors(prev => ({ ...prev, backendUrl: t.errorBackendUrlUnreachable }));
|
|
158
|
+
}
|
|
159
|
+
};
|
|
108
160
|
|
|
109
161
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
110
162
|
e.preventDefault();
|
|
111
|
-
const errors: { email?: string; password?: string } = {};
|
|
163
|
+
const errors: { backendUrl?: string; email?: string; password?: string } = {};
|
|
112
164
|
|
|
113
|
-
if (!
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (!password) {
|
|
117
|
-
errors.password = t.errorPasswordRequired;
|
|
118
|
-
}
|
|
165
|
+
if (!backendUrl.trim()) errors.backendUrl = t.errorBackendUrlRequired;
|
|
166
|
+
if (!email) errors.email = t.errorEmailRequired;
|
|
167
|
+
if (!password) errors.password = t.errorPasswordRequired;
|
|
119
168
|
|
|
120
169
|
if (Object.keys(errors).length > 0) {
|
|
121
170
|
setFieldErrors(errors);
|
|
122
|
-
setValidationError(Object.values(errors)[0]);
|
|
171
|
+
setValidationError(Object.values(errors)[0]);
|
|
123
172
|
return;
|
|
124
173
|
}
|
|
125
174
|
|
|
126
175
|
setFieldErrors({});
|
|
127
176
|
setValidationError(null);
|
|
128
|
-
await onSubmit(email, password);
|
|
177
|
+
await onSubmit(backendUrl.trim(), email, password);
|
|
129
178
|
};
|
|
130
179
|
|
|
180
|
+
const probeIndicator = !lockedBackendUrl && urlProbeStatus !== 'idle' ? (
|
|
181
|
+
<span
|
|
182
|
+
className={`semiont-form__url-status semiont-form__url-status--${urlProbeStatus}`}
|
|
183
|
+
aria-live="polite"
|
|
184
|
+
>
|
|
185
|
+
{urlProbeStatus === 'checking' && '⟳'}
|
|
186
|
+
{urlProbeStatus === 'ok' && '✓'}
|
|
187
|
+
{urlProbeStatus === 'error' && '✗'}
|
|
188
|
+
</span>
|
|
189
|
+
) : null;
|
|
190
|
+
|
|
131
191
|
return (
|
|
132
192
|
<>
|
|
133
193
|
{validationError && (
|
|
@@ -137,6 +197,38 @@ function CredentialsAuthForm({
|
|
|
137
197
|
)}
|
|
138
198
|
|
|
139
199
|
<form onSubmit={handleSubmit} className="semiont-auth__form" noValidate>
|
|
200
|
+
<div className="semiont-form__field">
|
|
201
|
+
<label htmlFor="backend-url" className="semiont-form__label">
|
|
202
|
+
{t.backendUrlLabel}
|
|
203
|
+
</label>
|
|
204
|
+
<div className="semiont-form__input-row">
|
|
205
|
+
<input
|
|
206
|
+
id="backend-url"
|
|
207
|
+
type="url"
|
|
208
|
+
value={backendUrl}
|
|
209
|
+
onChange={(e) => {
|
|
210
|
+
if (lockedBackendUrl) return;
|
|
211
|
+
setBackendUrl(e.target.value);
|
|
212
|
+
setUrlProbeStatus('idle');
|
|
213
|
+
if (fieldErrors.backendUrl) setFieldErrors({ ...fieldErrors, backendUrl: undefined });
|
|
214
|
+
}}
|
|
215
|
+
onBlur={() => { if (!lockedBackendUrl) probeUrl(backendUrl); }}
|
|
216
|
+
readOnly={!!lockedBackendUrl}
|
|
217
|
+
placeholder={t.backendUrlPlaceholder}
|
|
218
|
+
className={`semiont-input${lockedBackendUrl ? ' semiont-input--readonly' : ''}`}
|
|
219
|
+
aria-invalid={!!fieldErrors.backendUrl}
|
|
220
|
+
aria-describedby={fieldErrors.backendUrl ? 'backend-url-error' : undefined}
|
|
221
|
+
required
|
|
222
|
+
/>
|
|
223
|
+
{probeIndicator}
|
|
224
|
+
</div>
|
|
225
|
+
{fieldErrors.backendUrl && (
|
|
226
|
+
<span id="backend-url-error" className="semiont-form__error" role="alert">
|
|
227
|
+
{fieldErrors.backendUrl}
|
|
228
|
+
</span>
|
|
229
|
+
)}
|
|
230
|
+
</div>
|
|
231
|
+
|
|
140
232
|
<div className="semiont-form__field">
|
|
141
233
|
<label htmlFor="email" className="semiont-form__label">
|
|
142
234
|
{t.emailLabel}
|
|
@@ -147,9 +239,7 @@ function CredentialsAuthForm({
|
|
|
147
239
|
value={email}
|
|
148
240
|
onChange={(e) => {
|
|
149
241
|
setEmail(e.target.value);
|
|
150
|
-
if (fieldErrors.email) {
|
|
151
|
-
setFieldErrors({ ...fieldErrors, email: undefined });
|
|
152
|
-
}
|
|
242
|
+
if (fieldErrors.email) setFieldErrors({ ...fieldErrors, email: undefined });
|
|
153
243
|
}}
|
|
154
244
|
placeholder={t.emailPlaceholder}
|
|
155
245
|
className="semiont-input"
|
|
@@ -163,6 +253,7 @@ function CredentialsAuthForm({
|
|
|
163
253
|
</span>
|
|
164
254
|
)}
|
|
165
255
|
</div>
|
|
256
|
+
|
|
166
257
|
<div className="semiont-form__field">
|
|
167
258
|
<label htmlFor="password" className="semiont-form__label">
|
|
168
259
|
{t.passwordLabel}
|
|
@@ -173,9 +264,7 @@ function CredentialsAuthForm({
|
|
|
173
264
|
value={password}
|
|
174
265
|
onChange={(e) => {
|
|
175
266
|
setPassword(e.target.value);
|
|
176
|
-
if (fieldErrors.password) {
|
|
177
|
-
setFieldErrors({ ...fieldErrors, password: undefined });
|
|
178
|
-
}
|
|
267
|
+
if (fieldErrors.password) setFieldErrors({ ...fieldErrors, password: undefined });
|
|
179
268
|
}}
|
|
180
269
|
placeholder={t.passwordPlaceholder}
|
|
181
270
|
className="semiont-input"
|
|
@@ -189,6 +278,7 @@ function CredentialsAuthForm({
|
|
|
189
278
|
</span>
|
|
190
279
|
)}
|
|
191
280
|
</div>
|
|
281
|
+
|
|
192
282
|
<button type="submit" className={`${buttonStyles.primary.base} semiont-button--full-width`}>
|
|
193
283
|
{t.signInWithCredentials}
|
|
194
284
|
</button>
|
|
@@ -203,17 +293,29 @@ function CredentialsAuthForm({
|
|
|
203
293
|
}
|
|
204
294
|
|
|
205
295
|
/**
|
|
206
|
-
* SignInForm - Main sign-in component
|
|
296
|
+
* SignInForm - Main sign-in / connect component.
|
|
297
|
+
*
|
|
298
|
+
* When backendUrl is provided (re-auth to known workspace) the URL field is locked.
|
|
299
|
+
* When backendUrl is omitted (new connection) the user enters the URL themselves.
|
|
207
300
|
*/
|
|
208
301
|
export function SignInForm({
|
|
209
302
|
onGoogleSignIn,
|
|
210
303
|
onCredentialsSignIn,
|
|
304
|
+
backendUrl,
|
|
211
305
|
error,
|
|
212
306
|
showCredentialsAuth = false,
|
|
213
307
|
isLoading = false,
|
|
214
308
|
Link,
|
|
215
309
|
translations: t,
|
|
216
310
|
}: SignInFormProps) {
|
|
311
|
+
const handleGoogleClick = () => {
|
|
312
|
+
// For Google auth we need the backend URL from the locked prop or we cannot proceed.
|
|
313
|
+
// The caller is responsible for ensuring backendUrl is set when Google sign-in is offered.
|
|
314
|
+
if (backendUrl) {
|
|
315
|
+
onGoogleSignIn(backendUrl);
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
|
|
217
319
|
return (
|
|
218
320
|
<main className="semiont-auth__main" role="main">
|
|
219
321
|
<div className="semiont-auth__container">
|
|
@@ -245,12 +347,21 @@ export function SignInForm({
|
|
|
245
347
|
<div className="semiont-auth__forms">
|
|
246
348
|
{!isLoading ? (
|
|
247
349
|
<>
|
|
248
|
-
{showCredentialsAuth && onCredentialsSignIn &&
|
|
350
|
+
{showCredentialsAuth && onCredentialsSignIn && (
|
|
351
|
+
<CredentialsAuthForm
|
|
352
|
+
lockedBackendUrl={backendUrl}
|
|
353
|
+
onSubmit={onCredentialsSignIn}
|
|
354
|
+
translations={t}
|
|
355
|
+
/>
|
|
356
|
+
)}
|
|
249
357
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
{
|
|
253
|
-
|
|
358
|
+
{/* Google sign-in only shown when a backend URL is known */}
|
|
359
|
+
{backendUrl && (
|
|
360
|
+
<button onClick={handleGoogleClick} className={`${buttonStyles.primary.base} semiont-button--full-width`}>
|
|
361
|
+
<GoogleIcon />
|
|
362
|
+
{t.continueWithGoogle}
|
|
363
|
+
</button>
|
|
364
|
+
)}
|
|
254
365
|
|
|
255
366
|
<div className="semiont-auth__info">
|
|
256
367
|
{showCredentialsAuth ? t.credentialsAuthEnabled : t.approvedDomainsOnly}
|
|
@@ -258,7 +369,6 @@ export function SignInForm({
|
|
|
258
369
|
</>
|
|
259
370
|
) : (
|
|
260
371
|
<div className="semiont-auth__loading" aria-busy="true" aria-live="polite">
|
|
261
|
-
{/* Placeholder to maintain consistent height while loading */}
|
|
262
372
|
<div style={{ height: '200px' }}></div>
|
|
263
373
|
</div>
|
|
264
374
|
)}
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
* No Next.js mocking required - all dependencies passed as props!
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { describe, it, expect, vi
|
|
8
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
9
9
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
10
10
|
import { ResourceComposePage } from '../components/ResourceComposePage';
|
|
11
11
|
import type { ResourceComposePageProps, SaveResourceParams } from '../components/ResourceComposePage';
|
|
12
|
-
import { EventBusProvider
|
|
12
|
+
import { EventBusProvider } from '../../../contexts/EventBusContext';
|
|
13
13
|
|
|
14
14
|
// Mock CodeMirrorRenderer to avoid CodeMirror dependencies
|
|
15
15
|
vi.mock('../../../components/CodeMirrorRenderer', () => ({
|
|
@@ -78,10 +78,6 @@ const renderWithProviders = (ui: React.ReactElement) => {
|
|
|
78
78
|
};
|
|
79
79
|
|
|
80
80
|
describe('ResourceComposePage', () => {
|
|
81
|
-
beforeEach(() => {
|
|
82
|
-
resetEventBusForTesting();
|
|
83
|
-
});
|
|
84
|
-
|
|
85
81
|
describe('Basic Rendering - New Resource Mode', () => {
|
|
86
82
|
it('renders without crashing', () => {
|
|
87
83
|
const props = createMockProps();
|
|
@@ -9,7 +9,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
9
9
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
10
10
|
import { ResourceDiscoveryPage } from '../components/ResourceDiscoveryPage';
|
|
11
11
|
import type { ResourceDiscoveryPageProps } from '../components/ResourceDiscoveryPage';
|
|
12
|
-
import { EventBusProvider
|
|
12
|
+
import { EventBusProvider } from '../../../contexts/EventBusContext';
|
|
13
13
|
|
|
14
14
|
const createMockResource = (id: string, name: string, entityTypes: string[] = []) => ({
|
|
15
15
|
'@context': 'https://www.w3.org/ns/anno.jsonld',
|
|
@@ -63,7 +63,6 @@ const renderWithProviders = (ui: React.ReactElement) => {
|
|
|
63
63
|
|
|
64
64
|
describe('ResourceDiscoveryPage', () => {
|
|
65
65
|
beforeEach(() => {
|
|
66
|
-
resetEventBusForTesting();
|
|
67
66
|
});
|
|
68
67
|
|
|
69
68
|
describe('Basic Rendering', () => {
|
|
@@ -21,7 +21,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
|
21
21
|
import { render, screen, waitFor } from '@testing-library/react';
|
|
22
22
|
import { act } from 'react';
|
|
23
23
|
import { useMarkFlow } from '../../../hooks/useMarkFlow';
|
|
24
|
-
import { EventBusProvider, useEventBus
|
|
24
|
+
import { EventBusProvider, useEventBus } from '../../../contexts/EventBusContext';
|
|
25
25
|
import { ApiClientProvider } from '../../../contexts/ApiClientContext';
|
|
26
26
|
import { AuthTokenProvider } from '../../../contexts/AuthTokenContext';
|
|
27
27
|
import { SemiontApiClient } from '@semiont/api-client';
|
|
@@ -107,7 +107,6 @@ describe('Annotation creation clears pendingAnnotation', () => {
|
|
|
107
107
|
|
|
108
108
|
beforeEach(() => {
|
|
109
109
|
vi.clearAllMocks();
|
|
110
|
-
resetEventBusForTesting();
|
|
111
110
|
markAnnotationSpy = vi
|
|
112
111
|
.spyOn(SemiontApiClient.prototype, 'markAnnotation')
|
|
113
112
|
.mockResolvedValue({ annotationId: MOCK_ANNOTATION.id } as any);
|
|
@@ -27,7 +27,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
|
27
27
|
import { render, waitFor } from '@testing-library/react';
|
|
28
28
|
import { act } from 'react';
|
|
29
29
|
import { useMarkFlow } from '../../../hooks/useMarkFlow';
|
|
30
|
-
import { EventBusProvider, useEventBus
|
|
30
|
+
import { EventBusProvider, useEventBus } from '../../../contexts/EventBusContext';
|
|
31
31
|
|
|
32
32
|
// Mock Toast module to prevent "useToast must be used within a ToastProvider" errors
|
|
33
33
|
vi.mock('../../../components/Toast', () => ({
|
|
@@ -51,7 +51,6 @@ describe('Annotation Deletion - Feature Integration', () => {
|
|
|
51
51
|
|
|
52
52
|
beforeEach(() => {
|
|
53
53
|
vi.clearAllMocks();
|
|
54
|
-
resetEventBusForTesting();
|
|
55
54
|
|
|
56
55
|
// Mock the deleteAnnotation method on SemiontApiClient prototype
|
|
57
56
|
deleteAnnotationSpy = vi.fn().mockResolvedValue({ success: true });
|