@semiont/react-ui 0.2.33-build.79 → 0.2.33-build.80

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.
Files changed (213) hide show
  1. package/dist/EventBusContext-7GvDyO0d.d.mts +414 -0
  2. package/dist/{PdfAnnotationCanvas.client-ADC4FFSE.mjs → PdfAnnotationCanvas.client-RAJRPQLU.mjs} +42 -27
  3. package/dist/PdfAnnotationCanvas.client-RAJRPQLU.mjs.map +1 -0
  4. package/dist/{ar-EMHEHPCJ.mjs → ar-4ZEORRW2.mjs} +7 -4
  5. package/dist/ar-4ZEORRW2.mjs.map +1 -0
  6. package/dist/{bn-OVCI4F6X.mjs → bn-SEDE5BQJ.mjs} +7 -4
  7. package/dist/bn-SEDE5BQJ.mjs.map +1 -0
  8. package/dist/{chunk-LIHZTECW.mjs → chunk-D7NBW4RV.mjs} +7 -4
  9. package/dist/chunk-D7NBW4RV.mjs.map +1 -0
  10. package/dist/{chunk-JZIO2A3B.mjs → chunk-ZR4ZV2LY.mjs} +206 -146
  11. package/dist/chunk-ZR4ZV2LY.mjs.map +1 -0
  12. package/dist/{cs-FAN66Q2F.mjs → cs-7W4WF5WD.mjs} +7 -4
  13. package/dist/cs-7W4WF5WD.mjs.map +1 -0
  14. package/dist/{da-YBBIHI2O.mjs → da-75XGBCBK.mjs} +7 -4
  15. package/dist/da-75XGBCBK.mjs.map +1 -0
  16. package/dist/{de-MAYU33LB.mjs → de-ODJVFLHM.mjs} +7 -4
  17. package/dist/de-ODJVFLHM.mjs.map +1 -0
  18. package/dist/{el-MKGSWN4O.mjs → el-C4PM4WB3.mjs} +7 -4
  19. package/dist/el-C4PM4WB3.mjs.map +1 -0
  20. package/dist/{en-DDLIXJCU.mjs → en-KJCJQ4OO.mjs} +2 -2
  21. package/dist/{es-52LHUWJD.mjs → es-WD33R7QL.mjs} +7 -4
  22. package/dist/es-WD33R7QL.mjs.map +1 -0
  23. package/dist/{fa-FJICRANB.mjs → fa-2BP6V56P.mjs} +7 -4
  24. package/dist/fa-2BP6V56P.mjs.map +1 -0
  25. package/dist/{fi-O455XFCR.mjs → fi-USRRW24J.mjs} +7 -4
  26. package/dist/fi-USRRW24J.mjs.map +1 -0
  27. package/dist/{fr-TXIXHOOE.mjs → fr-EC5S6WVF.mjs} +7 -4
  28. package/dist/fr-EC5S6WVF.mjs.map +1 -0
  29. package/dist/{he-JBSOX5IN.mjs → he-7TBVIKAA.mjs} +7 -4
  30. package/dist/he-7TBVIKAA.mjs.map +1 -0
  31. package/dist/{hi-KGHI3XVT.mjs → hi-FO4VIZLA.mjs} +7 -4
  32. package/dist/hi-FO4VIZLA.mjs.map +1 -0
  33. package/dist/{id-5OCPPZLO.mjs → id-7U7GGVWY.mjs} +7 -4
  34. package/dist/id-7U7GGVWY.mjs.map +1 -0
  35. package/dist/index.css +123 -85
  36. package/dist/index.css.map +1 -1
  37. package/dist/index.d.mts +645 -471
  38. package/dist/index.mjs +3461 -3025
  39. package/dist/index.mjs.map +1 -1
  40. package/dist/{it-PNBBZSM2.mjs → it-Y4OPL6I2.mjs} +7 -4
  41. package/dist/it-Y4OPL6I2.mjs.map +1 -0
  42. package/dist/{ja-LDD7R3TJ.mjs → ja-PK7SQL55.mjs} +7 -4
  43. package/dist/ja-PK7SQL55.mjs.map +1 -0
  44. package/dist/{ko-F47ZDEY3.mjs → ko-L25PXMYD.mjs} +7 -4
  45. package/dist/ko-L25PXMYD.mjs.map +1 -0
  46. package/dist/{ms-Z7LMXJWL.mjs → ms-STH777QM.mjs} +7 -4
  47. package/dist/ms-STH777QM.mjs.map +1 -0
  48. package/dist/{nl-6SJFBPJ3.mjs → nl-Y7LECDDR.mjs} +7 -4
  49. package/dist/nl-Y7LECDDR.mjs.map +1 -0
  50. package/dist/{no-YXPBPSGF.mjs → no-KEKCEWU6.mjs} +7 -4
  51. package/dist/no-KEKCEWU6.mjs.map +1 -0
  52. package/dist/{pl-P4AZ2QME.mjs → pl-7A7OC75O.mjs} +7 -4
  53. package/dist/pl-7A7OC75O.mjs.map +1 -0
  54. package/dist/{pt-LHWUS6U6.mjs → pt-35HTM7RA.mjs} +7 -4
  55. package/dist/pt-35HTM7RA.mjs.map +1 -0
  56. package/dist/{ro-EA5J2ZON.mjs → ro-VAWL5KQA.mjs} +7 -4
  57. package/dist/ro-VAWL5KQA.mjs.map +1 -0
  58. package/dist/{sv-DATBS3UQ.mjs → sv-7ZK5EQEB.mjs} +7 -4
  59. package/dist/sv-7ZK5EQEB.mjs.map +1 -0
  60. package/dist/test-utils.d.mts +18 -8
  61. package/dist/test-utils.mjs +36 -14
  62. package/dist/test-utils.mjs.map +1 -1
  63. package/dist/{th-WTFJRWPT.mjs → th-UDWZ4X34.mjs} +7 -4
  64. package/dist/th-UDWZ4X34.mjs.map +1 -0
  65. package/dist/{tr-IKO3RXOX.mjs → tr-4WMPK3UX.mjs} +7 -4
  66. package/dist/tr-4WMPK3UX.mjs.map +1 -0
  67. package/dist/{uk-CF6CTTRK.mjs → uk-SSLASQYJ.mjs} +7 -4
  68. package/dist/uk-SSLASQYJ.mjs.map +1 -0
  69. package/dist/{vi-AJLTXPZQ.mjs → vi-IF42Z5PU.mjs} +7 -4
  70. package/dist/vi-IF42Z5PU.mjs.map +1 -0
  71. package/dist/{zh-U3ORHHYH.mjs → zh-HRQTNTAI.mjs} +7 -4
  72. package/dist/zh-HRQTNTAI.mjs.map +1 -0
  73. package/package.json +3 -1
  74. package/src/components/CodeMirrorRenderer.tsx +66 -93
  75. package/src/components/DetectionProgressWidget.tsx +16 -5
  76. package/src/components/LiveRegion.tsx +18 -18
  77. package/src/components/ResizeHandle.tsx +10 -4
  78. package/src/components/SessionTimer.tsx +2 -2
  79. package/src/components/Toolbar.tsx +18 -9
  80. package/src/components/__tests__/SessionTimer.test.tsx +9 -9
  81. package/src/components/annotation/AnnotateToolbar.tsx +17 -15
  82. package/src/components/annotation/__tests__/AnnotateToolbar.test.tsx +165 -63
  83. package/src/components/annotation/annotation-entries.css +10 -0
  84. package/src/components/annotation-popups/JsonLdView.tsx +8 -2
  85. package/src/components/image-annotation/AnnotationOverlay.tsx +42 -22
  86. package/src/components/image-annotation/SvgDrawingCanvas.tsx +27 -30
  87. package/src/components/layout/__tests__/LeftSidebar.test.tsx +12 -33
  88. package/src/components/layout/__tests__/PageLayout.test.tsx +37 -32
  89. package/src/components/layout/__tests__/UnifiedHeader.test.tsx +21 -40
  90. package/src/components/modals/ResourceSearchModal.tsx +2 -2
  91. package/src/components/modals/SearchModal.tsx +1 -1
  92. package/src/components/navigation/CollapsibleResourceNavigation.tsx +14 -9
  93. package/src/components/navigation/NavigationTabs.css +36 -24
  94. package/src/components/navigation/ObservableLink.tsx +91 -0
  95. package/src/components/navigation/SimpleNavigation.tsx +20 -16
  96. package/src/components/navigation/SortableResourceTab.tsx +11 -5
  97. package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +51 -26
  98. package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +28 -22
  99. package/src/components/resource/AnnotateView.tsx +64 -134
  100. package/src/components/resource/BrowseView.tsx +86 -166
  101. package/src/components/resource/HistoryEvent.tsx +13 -7
  102. package/src/components/resource/ResourceViewer.tsx +122 -264
  103. package/src/components/resource/__tests__/BrowseView.test.tsx +631 -0
  104. package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +231 -0
  105. package/src/components/resource/panels/AssessmentEntry.tsx +25 -33
  106. package/src/components/resource/panels/AssessmentPanel.tsx +106 -28
  107. package/src/components/resource/panels/CommentEntry.tsx +38 -32
  108. package/src/components/resource/panels/CommentsPanel.tsx +121 -28
  109. package/src/components/resource/panels/DetectSection.css +36 -1
  110. package/src/components/resource/panels/DetectSection.tsx +38 -10
  111. package/src/components/resource/panels/HighlightEntry.tsx +25 -33
  112. package/src/components/resource/panels/HighlightPanel.tsx +100 -25
  113. package/src/components/resource/panels/ReferenceEntry.tsx +61 -75
  114. package/src/components/resource/panels/ReferencesPanel.tsx +134 -42
  115. package/src/components/resource/panels/ResourceInfoPanel.tsx +47 -48
  116. package/src/components/resource/panels/TagEntry.tsx +25 -33
  117. package/src/components/resource/panels/TaggingPanel.tsx +119 -30
  118. package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +30 -92
  119. package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +129 -110
  120. package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +86 -78
  121. package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +144 -149
  122. package/src/components/resource/panels/__tests__/DetectSection.test.tsx +480 -0
  123. package/src/components/resource/panels/__tests__/HighlightPanel.detectionProgress.test.tsx +362 -0
  124. package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +226 -111
  125. package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +117 -61
  126. package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +128 -106
  127. package/src/components/settings/SettingsPanel.tsx +15 -12
  128. package/src/features/admin-devops/__tests__/AdminDevOpsPage.test.tsx +1 -46
  129. package/src/features/admin-devops/components/AdminDevOpsPage.tsx +0 -9
  130. package/src/features/admin-security/__tests__/AdminSecurityPage.test.tsx +0 -3
  131. package/src/features/admin-security/components/AdminSecurityPage.tsx +0 -9
  132. package/src/features/admin-users/__tests__/AdminUsersPage.test.tsx +0 -3
  133. package/src/features/admin-users/components/AdminUsersPage.tsx +0 -9
  134. package/src/features/moderate-entity-tags/__tests__/EntityTagsPage.test.tsx +0 -3
  135. package/src/features/moderate-entity-tags/components/EntityTagsPage.tsx +1 -9
  136. package/src/features/moderate-recent/__tests__/RecentDocumentsPage.test.tsx +0 -32
  137. package/src/features/moderate-recent/components/RecentDocumentsPage.tsx +1 -9
  138. package/src/features/moderate-tag-schemas/__tests__/TagSchemasPage.test.tsx +0 -32
  139. package/src/features/moderate-tag-schemas/components/TagSchemasPage.tsx +1 -9
  140. package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +51 -54
  141. package/src/features/resource-compose/components/ResourceComposePage.tsx +3 -13
  142. package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +39 -45
  143. package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +9 -13
  144. package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +231 -0
  145. package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +234 -0
  146. package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +388 -0
  147. package/src/features/resource-viewer/__tests__/DetectionProgressDismissal.test.tsx +318 -0
  148. package/src/features/resource-viewer/__tests__/GenerationFlowIntegration.test.tsx +504 -0
  149. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +135 -88
  150. package/src/features/resource-viewer/__tests__/detection-progress-flow.test.tsx +322 -0
  151. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +308 -528
  152. package/translations/ar.json +6 -3
  153. package/translations/bn.json +6 -3
  154. package/translations/cs.json +6 -3
  155. package/translations/da.json +6 -3
  156. package/translations/de.json +6 -3
  157. package/translations/el.json +6 -3
  158. package/translations/en.json +6 -3
  159. package/translations/es.json +6 -3
  160. package/translations/fa.json +6 -3
  161. package/translations/fi.json +6 -3
  162. package/translations/fr.json +6 -3
  163. package/translations/he.json +6 -3
  164. package/translations/hi.json +6 -3
  165. package/translations/id.json +6 -3
  166. package/translations/it.json +6 -3
  167. package/translations/ja.json +6 -3
  168. package/translations/ko.json +6 -3
  169. package/translations/ms.json +6 -3
  170. package/translations/nl.json +6 -3
  171. package/translations/no.json +6 -3
  172. package/translations/pl.json +6 -3
  173. package/translations/pt.json +6 -3
  174. package/translations/ro.json +6 -3
  175. package/translations/sv.json +6 -3
  176. package/translations/th.json +6 -3
  177. package/translations/tr.json +6 -3
  178. package/translations/uk.json +6 -3
  179. package/translations/vi.json +6 -3
  180. package/translations/zh.json +6 -3
  181. package/dist/PdfAnnotationCanvas.client-ADC4FFSE.mjs.map +0 -1
  182. package/dist/TranslationManager-Co_5fSxl.d.mts +0 -118
  183. package/dist/ar-EMHEHPCJ.mjs.map +0 -1
  184. package/dist/bn-OVCI4F6X.mjs.map +0 -1
  185. package/dist/chunk-JZIO2A3B.mjs.map +0 -1
  186. package/dist/chunk-LIHZTECW.mjs.map +0 -1
  187. package/dist/cs-FAN66Q2F.mjs.map +0 -1
  188. package/dist/da-YBBIHI2O.mjs.map +0 -1
  189. package/dist/de-MAYU33LB.mjs.map +0 -1
  190. package/dist/el-MKGSWN4O.mjs.map +0 -1
  191. package/dist/es-52LHUWJD.mjs.map +0 -1
  192. package/dist/fa-FJICRANB.mjs.map +0 -1
  193. package/dist/fi-O455XFCR.mjs.map +0 -1
  194. package/dist/fr-TXIXHOOE.mjs.map +0 -1
  195. package/dist/he-JBSOX5IN.mjs.map +0 -1
  196. package/dist/hi-KGHI3XVT.mjs.map +0 -1
  197. package/dist/id-5OCPPZLO.mjs.map +0 -1
  198. package/dist/it-PNBBZSM2.mjs.map +0 -1
  199. package/dist/ja-LDD7R3TJ.mjs.map +0 -1
  200. package/dist/ko-F47ZDEY3.mjs.map +0 -1
  201. package/dist/ms-Z7LMXJWL.mjs.map +0 -1
  202. package/dist/nl-6SJFBPJ3.mjs.map +0 -1
  203. package/dist/no-YXPBPSGF.mjs.map +0 -1
  204. package/dist/pl-P4AZ2QME.mjs.map +0 -1
  205. package/dist/pt-LHWUS6U6.mjs.map +0 -1
  206. package/dist/ro-EA5J2ZON.mjs.map +0 -1
  207. package/dist/sv-DATBS3UQ.mjs.map +0 -1
  208. package/dist/th-WTFJRWPT.mjs.map +0 -1
  209. package/dist/tr-IKO3RXOX.mjs.map +0 -1
  210. package/dist/uk-CF6CTTRK.mjs.map +0 -1
  211. package/dist/vi-AJLTXPZQ.mjs.map +0 -1
  212. package/dist/zh-U3ORHHYH.mjs.map +0 -1
  213. /package/dist/{en-DDLIXJCU.mjs.map → en-KJCJQ4OO.mjs.map} +0 -0
@@ -1,10 +1,12 @@
1
1
  import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
2
  import React from 'react';
3
- import { render, screen, fireEvent, waitFor } from '@testing-library/react';
3
+ import { screen, fireEvent, waitFor } from '@testing-library/react';
4
+ import { renderWithProviders, resetEventBusForTesting } from '../../../../test-utils';
4
5
  import userEvent from '@testing-library/user-event';
5
6
  import '@testing-library/jest-dom';
6
7
  import { CommentEntry } from '../CommentEntry';
7
8
  import type { components } from '@semiont/api-client';
9
+ import type { EventBus } from '../../../../contexts/EventBusContext';
8
10
 
9
11
  type Annotation = components['schemas']['Annotation'];
10
12
 
@@ -18,6 +20,7 @@ vi.mock('../../../../contexts/TranslationContext', () => ({
18
20
  };
19
21
  return translations[key] || key;
20
22
  }),
23
+ TranslationProvider: ({ children }: { children: React.ReactNode }) => children,
21
24
  }));
22
25
 
23
26
  // Mock @semiont/api-client utilities
@@ -102,6 +105,7 @@ describe('CommentEntry Component', () => {
102
105
 
103
106
  beforeEach(() => {
104
107
  vi.clearAllMocks();
108
+ resetEventBusForTesting(); // Reset event bus between tests
105
109
  mockGetCommentText.mockReturnValue('This is a test comment');
106
110
  mockGetAnnotationExactText.mockReturnValue('This is th');
107
111
 
@@ -115,14 +119,14 @@ describe('CommentEntry Component', () => {
115
119
 
116
120
  describe('Rendering', () => {
117
121
  it('should render comment with text and metadata', () => {
118
- render(<CommentEntry {...defaultProps} />);
122
+ renderWithProviders(<CommentEntry {...defaultProps} />);
119
123
 
120
124
  expect(screen.getByText('This is a test comment')).toBeInTheDocument();
121
125
  expect(screen.getByText(/user@example.com/)).toBeInTheDocument();
122
126
  });
123
127
 
124
128
  it('should render selected text quote', () => {
125
- const { container } = render(<CommentEntry {...defaultProps} />);
129
+ const { container } = renderWithProviders(<CommentEntry {...defaultProps} />);
126
130
 
127
131
  const quote = container.querySelector('.semiont-annotation-entry__quote');
128
132
  expect(quote).toBeInTheDocument();
@@ -133,14 +137,14 @@ describe('CommentEntry Component', () => {
133
137
  const longText = 'A'.repeat(150);
134
138
  mockGetAnnotationExactText.mockReturnValue(longText);
135
139
 
136
- render(<CommentEntry {...defaultProps} />);
140
+ renderWithProviders(<CommentEntry {...defaultProps} />);
137
141
 
138
142
  expect(screen.getByText(new RegExp(`"${'A'.repeat(100)}`))).toBeInTheDocument();
139
143
  expect(screen.getByText(/\.\.\./)).toBeInTheDocument();
140
144
  });
141
145
 
142
146
  it('should render creator name from creator object', () => {
143
- render(
147
+ renderWithProviders(
144
148
  <CommentEntry
145
149
  {...defaultProps}
146
150
  comment={mockCommentStates.withCreatorObject}
@@ -151,7 +155,7 @@ describe('CommentEntry Component', () => {
151
155
  });
152
156
 
153
157
  it('should handle creator as object with name', () => {
154
- render(<CommentEntry {...defaultProps} />);
158
+ renderWithProviders(<CommentEntry {...defaultProps} />);
155
159
 
156
160
  expect(screen.getByText(/user@example.com/)).toBeInTheDocument();
157
161
  });
@@ -160,7 +164,7 @@ describe('CommentEntry Component', () => {
160
164
  const { creator, ...rest } = createMockComment();
161
165
  const commentWithoutCreator = rest as Annotation;
162
166
 
163
- render(
167
+ renderWithProviders(
164
168
  <CommentEntry
165
169
  {...defaultProps}
166
170
  comment={commentWithoutCreator}
@@ -171,7 +175,7 @@ describe('CommentEntry Component', () => {
171
175
  });
172
176
 
173
177
  it('should format relative time correctly for recent comments', () => {
174
- render(
178
+ renderWithProviders(
175
179
  <CommentEntry
176
180
  {...defaultProps}
177
181
  comment={mockCommentStates.recentComment}
@@ -182,7 +186,7 @@ describe('CommentEntry Component', () => {
182
186
  });
183
187
 
184
188
  it('should format relative time correctly for old comments', () => {
185
- render(
189
+ renderWithProviders(
186
190
  <CommentEntry
187
191
  {...defaultProps}
188
192
  comment={mockCommentStates.oldComment}
@@ -195,7 +199,7 @@ describe('CommentEntry Component', () => {
195
199
 
196
200
  describe('Focus State', () => {
197
201
  it('should apply focus styles when focused', () => {
198
- const { container } = render(
202
+ const { container } = renderWithProviders(
199
203
  <CommentEntry {...defaultProps} isFocused={true} />
200
204
  );
201
205
 
@@ -205,7 +209,7 @@ describe('CommentEntry Component', () => {
205
209
  });
206
210
 
207
211
  it('should not apply focus styles when not focused', () => {
208
- const { container } = render(
212
+ const { container } = renderWithProviders(
209
213
  <CommentEntry {...defaultProps} isFocused={false} />
210
214
  );
211
215
 
@@ -217,7 +221,7 @@ describe('CommentEntry Component', () => {
217
221
  const mockScrollIntoView = vi.fn();
218
222
  Element.prototype.scrollIntoView = mockScrollIntoView;
219
223
 
220
- const { rerender } = render(
224
+ const { rerender } = renderWithProviders(
221
225
  <CommentEntry {...defaultProps} isFocused={false} />
222
226
  );
223
227
 
@@ -237,20 +241,31 @@ describe('CommentEntry Component', () => {
237
241
  });
238
242
 
239
243
  describe('Click Interactions', () => {
240
- it('should call onClick when comment is clicked', async () => {
241
- const onClick = vi.fn();
242
- const { container } = render(
243
- <CommentEntry {...defaultProps} onClick={onClick} />
244
+ it('should emit annotation:click event when comment is clicked', async () => {
245
+ const clickHandler = vi.fn();
246
+
247
+ const { container, eventBus } = renderWithProviders(
248
+ <CommentEntry {...defaultProps} />,
249
+ { returnEventBus: true }
244
250
  );
245
251
 
252
+ // Subscribe to actual event on real event bus
253
+ eventBus!.on('annotation:click', clickHandler);
254
+
246
255
  const commentDiv = container.firstChild as HTMLElement;
247
256
  await userEvent.click(commentDiv);
248
257
 
249
- expect(onClick).toHaveBeenCalledOnce();
258
+ expect(clickHandler).toHaveBeenCalledWith({
259
+ annotationId: 'comment-1',
260
+ motivation: 'commenting'
261
+ });
262
+
263
+ // Clean up
264
+ eventBus!.off('annotation:click', clickHandler);
250
265
  });
251
266
 
252
267
  it('should be clickable with cursor-pointer class', () => {
253
- const { container } = render(<CommentEntry {...defaultProps} />);
268
+ const { container } = renderWithProviders(<CommentEntry {...defaultProps} />);
254
269
 
255
270
  const commentDiv = container.firstChild as HTMLElement;
256
271
  expect(commentDiv).toHaveClass('semiont-annotation-entry');
@@ -258,34 +273,49 @@ describe('CommentEntry Component', () => {
258
273
  });
259
274
 
260
275
  describe('Hover Interactions', () => {
261
- it('should call onCommentHover with comment id on mouse enter', () => {
262
- const onCommentHover = vi.fn();
263
- const { container } = render(
264
- <CommentEntry {...defaultProps} onCommentHover={onCommentHover} />
276
+ it('should emit annotation:hover event with annotation id on mouse enter', () => {
277
+ const hoverHandler = vi.fn();
278
+
279
+ const { container, eventBus } = renderWithProviders(
280
+ <CommentEntry {...defaultProps} />,
281
+ { returnEventBus: true }
265
282
  );
266
283
 
284
+ // Subscribe to actual event
285
+ eventBus!.on('annotation:hover', hoverHandler);
286
+
267
287
  const commentDiv = container.firstChild as HTMLElement;
268
288
  fireEvent.mouseEnter(commentDiv);
269
289
 
270
- expect(onCommentHover).toHaveBeenCalledWith('comment-1');
290
+ expect(hoverHandler).toHaveBeenCalledWith({ annotationId: 'comment-1' });
291
+
292
+ // Clean up
293
+ eventBus!.off('annotation:hover', hoverHandler);
271
294
  });
272
295
 
273
- it('should call onCommentHover with null on mouse leave', () => {
274
- const onCommentHover = vi.fn();
275
- const { container } = render(
276
- <CommentEntry {...defaultProps} onCommentHover={onCommentHover} />
296
+ it('should emit annotation:hover event with null on mouse leave', () => {
297
+ const hoverHandler = vi.fn();
298
+
299
+ const { container, eventBus } = renderWithProviders(
300
+ <CommentEntry {...defaultProps} />,
301
+ { returnEventBus: true }
277
302
  );
278
303
 
304
+ // Subscribe to actual event
305
+ eventBus!.on('annotation:hover', hoverHandler);
306
+
279
307
  const commentDiv = container.firstChild as HTMLElement;
280
308
  fireEvent.mouseLeave(commentDiv);
281
309
 
282
- expect(onCommentHover).toHaveBeenCalledWith(null);
310
+ expect(hoverHandler).toHaveBeenCalledWith({ annotationId: null });
311
+
312
+ // Clean up
313
+ eventBus!.off('annotation:hover', hoverHandler);
283
314
  });
284
315
 
285
- it('should not error if onCommentHover is not provided', () => {
286
- const { onCommentHover, ...propsWithoutHover } = defaultProps;
287
- const { container } = render(
288
- <CommentEntry {...propsWithoutHover} />
316
+ it('should handle hover events without errors', () => {
317
+ const { container } = renderWithProviders(
318
+ <CommentEntry {...defaultProps} />
289
319
  );
290
320
 
291
321
  const commentDiv = container.firstChild as HTMLElement;
@@ -299,13 +329,13 @@ describe('CommentEntry Component', () => {
299
329
 
300
330
  describe('Edit Functionality', () => {
301
331
  it('should show edit button', () => {
302
- render(<CommentEntry {...defaultProps} />);
332
+ renderWithProviders(<CommentEntry {...defaultProps} />);
303
333
 
304
334
  expect(screen.getByText('Edit')).toBeInTheDocument();
305
335
  });
306
336
 
307
337
  it('should enter edit mode when edit button is clicked', async () => {
308
- render(<CommentEntry {...defaultProps} />);
338
+ renderWithProviders(<CommentEntry {...defaultProps} />);
309
339
 
310
340
  const editButton = screen.getByText('Edit');
311
341
  await userEvent.click(editButton);
@@ -318,7 +348,7 @@ describe('CommentEntry Component', () => {
318
348
  });
319
349
 
320
350
  it('should show save and cancel buttons in edit mode', async () => {
321
- render(<CommentEntry {...defaultProps} />);
351
+ renderWithProviders(<CommentEntry {...defaultProps} />);
322
352
 
323
353
  const editButton = screen.getByText('Edit');
324
354
  await userEvent.click(editButton);
@@ -330,7 +360,7 @@ describe('CommentEntry Component', () => {
330
360
 
331
361
  it('should stop event propagation when clicking textarea in edit mode', async () => {
332
362
  const onClick = vi.fn();
333
- render(<CommentEntry {...defaultProps} onClick={onClick} />);
363
+ renderWithProviders(<CommentEntry {...defaultProps} onClick={onClick} />);
334
364
 
335
365
  await userEvent.click(screen.getByText('Edit'));
336
366
 
@@ -345,7 +375,7 @@ describe('CommentEntry Component', () => {
345
375
  });
346
376
 
347
377
  it('should update text in textarea', async () => {
348
- render(<CommentEntry {...defaultProps} />);
378
+ renderWithProviders(<CommentEntry {...defaultProps} />);
349
379
 
350
380
  await userEvent.click(screen.getByText('Edit'));
351
381
  const textarea = screen.getByRole('textbox');
@@ -357,7 +387,7 @@ describe('CommentEntry Component', () => {
357
387
  });
358
388
 
359
389
  it('should show character count in edit mode', async () => {
360
- render(<CommentEntry {...defaultProps} />);
390
+ renderWithProviders(<CommentEntry {...defaultProps} />);
361
391
 
362
392
  await userEvent.click(screen.getByText('Edit'));
363
393
 
@@ -365,7 +395,7 @@ describe('CommentEntry Component', () => {
365
395
  });
366
396
 
367
397
  it('should enforce max length of 2000 characters', async () => {
368
- render(<CommentEntry {...defaultProps} />);
398
+ renderWithProviders(<CommentEntry {...defaultProps} />);
369
399
 
370
400
  await userEvent.click(screen.getByText('Edit'));
371
401
  const textarea = screen.getByRole('textbox') as HTMLTextAreaElement;
@@ -374,7 +404,7 @@ describe('CommentEntry Component', () => {
374
404
  });
375
405
 
376
406
  it('should update textarea value and exit edit mode when save is clicked', async () => {
377
- render(<CommentEntry {...defaultProps} />);
407
+ renderWithProviders(<CommentEntry {...defaultProps} />);
378
408
 
379
409
  await userEvent.click(screen.getByText('Edit'));
380
410
  const textarea = screen.getByRole('textbox');
@@ -393,7 +423,7 @@ describe('CommentEntry Component', () => {
393
423
  });
394
424
 
395
425
  it('should exit edit mode after save', async () => {
396
- render(<CommentEntry {...defaultProps} />);
426
+ renderWithProviders(<CommentEntry {...defaultProps} />);
397
427
 
398
428
  await userEvent.click(screen.getByText('Edit'));
399
429
  await userEvent.click(screen.getByText('Save'));
@@ -403,7 +433,7 @@ describe('CommentEntry Component', () => {
403
433
  });
404
434
 
405
435
  it('should discard changes when cancel is clicked', async () => {
406
- render(<CommentEntry {...defaultProps} />);
436
+ renderWithProviders(<CommentEntry {...defaultProps} />);
407
437
 
408
438
  await userEvent.click(screen.getByText('Edit'));
409
439
  const textarea = screen.getByRole('textbox');
@@ -419,7 +449,7 @@ describe('CommentEntry Component', () => {
419
449
 
420
450
  it('should not call onUpdate when cancel is clicked', async () => {
421
451
  const onUpdate = vi.fn();
422
- render(<CommentEntry {...defaultProps} onUpdate={onUpdate} />);
452
+ renderWithProviders(<CommentEntry {...defaultProps} onUpdate={onUpdate} />);
423
453
 
424
454
  await userEvent.click(screen.getByText('Edit'));
425
455
  await userEvent.click(screen.getByText('Cancel'));
@@ -428,40 +458,18 @@ describe('CommentEntry Component', () => {
428
458
  });
429
459
  });
430
460
 
431
- describe('Ref Management', () => {
432
- it('should call onCommentRef with element on mount', () => {
433
- const onCommentRef = vi.fn();
434
- render(<CommentEntry {...defaultProps} onCommentRef={onCommentRef} />);
435
-
436
- expect(onCommentRef).toHaveBeenCalledWith(
437
- 'comment-1',
438
- expect.any(HTMLDivElement)
439
- );
440
- });
441
-
442
- it('should call onCommentRef with null on unmount', () => {
443
- const onCommentRef = vi.fn();
444
- const { unmount } = render(
445
- <CommentEntry {...defaultProps} onCommentRef={onCommentRef} />
446
- );
447
-
448
- onCommentRef.mockClear();
449
- unmount();
450
-
451
- expect(onCommentRef).toHaveBeenCalledWith('comment-1', null);
452
- });
453
- });
461
+ // Ref Management tests removed - CommentEntry uses forwardRef but doesn't emit ref-update events
454
462
 
455
463
  describe('Styling and Appearance', () => {
456
464
  it('should have proper border and padding styles', () => {
457
- const { container } = render(<CommentEntry {...defaultProps} />);
465
+ const { container } = renderWithProviders(<CommentEntry {...defaultProps} />);
458
466
 
459
467
  const commentDiv = container.firstChild as HTMLElement;
460
468
  expect(commentDiv).toHaveClass('semiont-annotation-entry');
461
469
  });
462
470
 
463
471
  it('should have hover styles when not focused', () => {
464
- const { container } = render(<CommentEntry {...defaultProps} />);
472
+ const { container } = renderWithProviders(<CommentEntry {...defaultProps} />);
465
473
 
466
474
  const commentDiv = container.firstChild as HTMLElement;
467
475
  expect(commentDiv).toHaveClass('semiont-annotation-entry');
@@ -469,7 +477,7 @@ describe('CommentEntry Component', () => {
469
477
  });
470
478
 
471
479
  it('should support dark mode classes', () => {
472
- const { container } = render(<CommentEntry {...defaultProps} />);
480
+ const { container } = renderWithProviders(<CommentEntry {...defaultProps} />);
473
481
 
474
482
  const commentDiv = container.firstChild as HTMLElement;
475
483
  expect(commentDiv).toHaveClass('semiont-annotation-entry');
@@ -477,7 +485,7 @@ describe('CommentEntry Component', () => {
477
485
 
478
486
  it('should render comment text with whitespace preserved', () => {
479
487
  mockGetCommentText.mockReturnValue('Line 1\nLine 2\nLine 3');
480
- const { container } = render(<CommentEntry {...defaultProps} />);
488
+ const { container } = renderWithProviders(<CommentEntry {...defaultProps} />);
481
489
 
482
490
  // Look for the comment text in the annotation entry
483
491
  const commentContent = container.querySelector('.semiont-annotation-entry__body');
@@ -488,7 +496,7 @@ describe('CommentEntry Component', () => {
488
496
  describe('Edge Cases', () => {
489
497
  it('should handle empty comment text', () => {
490
498
  mockGetCommentText.mockReturnValue('');
491
- const { container } = render(<CommentEntry {...defaultProps} />);
499
+ const { container } = renderWithProviders(<CommentEntry {...defaultProps} />);
492
500
 
493
501
  // Check that the annotation entry exists even with empty text
494
502
  const commentDiv = container.querySelector('.semiont-annotation-entry');
@@ -499,7 +507,7 @@ describe('CommentEntry Component', () => {
499
507
  mockGetAnnotationExactText.mockReturnValue('');
500
508
 
501
509
  expect(() => {
502
- render(<CommentEntry {...defaultProps} />);
510
+ renderWithProviders(<CommentEntry {...defaultProps} />);
503
511
  }).not.toThrow();
504
512
  });
505
513
 
@@ -508,7 +516,7 @@ describe('CommentEntry Component', () => {
508
516
  const commentWithoutTimestamp = rest as Annotation;
509
517
 
510
518
  expect(() => {
511
- render(
519
+ renderWithProviders(
512
520
  <CommentEntry {...defaultProps} comment={commentWithoutTimestamp} />
513
521
  );
514
522
  }).not.toThrow();
@@ -518,7 +526,7 @@ describe('CommentEntry Component', () => {
518
526
  const longText = 'This is a very long comment that exceeds the typical length and should test text wrapping and display handling. '.repeat(5);
519
527
  mockGetCommentText.mockReturnValue(longText);
520
528
 
521
- render(
529
+ renderWithProviders(
522
530
  <CommentEntry {...defaultProps} comment={mockCommentStates.withLongText} />
523
531
  );
524
532
 
@@ -529,7 +537,7 @@ describe('CommentEntry Component', () => {
529
537
  });
530
538
 
531
539
  it('should handle rapid edit/cancel cycles', async () => {
532
- render(<CommentEntry {...defaultProps} />);
540
+ renderWithProviders(<CommentEntry {...defaultProps} />);
533
541
 
534
542
  for (let i = 0; i < 3; i++) {
535
543
  await userEvent.click(screen.getByText('Edit'));
@@ -543,7 +551,7 @@ describe('CommentEntry Component', () => {
543
551
 
544
552
  describe('Accessibility', () => {
545
553
  it('should have proper ARIA attributes for textarea', async () => {
546
- render(<CommentEntry {...defaultProps} />);
554
+ renderWithProviders(<CommentEntry {...defaultProps} />);
547
555
 
548
556
  await userEvent.click(screen.getByText('Edit'));
549
557
  const textarea = screen.getByRole('textbox');
@@ -552,7 +560,7 @@ describe('CommentEntry Component', () => {
552
560
  });
553
561
 
554
562
  it('should be keyboard navigable', async () => {
555
- render(<CommentEntry {...defaultProps} />);
563
+ renderWithProviders(<CommentEntry {...defaultProps} />);
556
564
 
557
565
  const editButton = screen.getByText('Edit');
558
566
  editButton.focus();
@@ -561,7 +569,7 @@ describe('CommentEntry Component', () => {
561
569
  });
562
570
 
563
571
  it('should support keyboard interaction for save/cancel', async () => {
564
- render(<CommentEntry {...defaultProps} />);
572
+ renderWithProviders(<CommentEntry {...defaultProps} />);
565
573
 
566
574
  await userEvent.click(screen.getByText('Edit'));
567
575