|
5 | 5 | import type { WebViewProps } from '@papi/core'; |
6 | 6 | import type { SerializedVerseRef } from '@sillsdev/scripture'; |
7 | 7 | import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; |
8 | | -import type { InterlinearData } from 'paratext-9-types'; |
| 8 | +import React from 'react'; |
| 9 | +import type { InterlinearData } from 'parsers/paratext-9/paratext-9-types'; |
9 | 10 |
|
10 | 11 | /** Stub InterlinearData returned by the mocked parser. Matches shape the WebView displays. */ |
11 | 12 | const stubInterlinearData: InterlinearData = { |
@@ -202,6 +203,21 @@ describe('InterlinearizerWebView', () => { |
202 | 203 | expect(screen.getByText(/paratext-9/i)).toBeInTheDocument(); |
203 | 204 | }); |
204 | 205 |
|
| 206 | + it('Analyses view shows empty JSON pre when createAnalyses returns undefined', async () => { |
| 207 | + mockCreateAnalyses.mockReturnValueOnce(undefined); |
| 208 | + |
| 209 | + const { container } = await renderWebView(); |
| 210 | + fireEvent.click(screen.getByRole('radio', { name: /^analyses$/i })); |
| 211 | + await waitFor(() => { |
| 212 | + expect(screen.getByText(/^Analyses \(JSON\):$/)).toBeInTheDocument(); |
| 213 | + }); |
| 214 | + |
| 215 | + const jsonPre = container.querySelector('pre'); |
| 216 | + expect(jsonPre).toBeInTheDocument(); |
| 217 | + expect(jsonPre).toBeEmptyDOMElement(); |
| 218 | + expect(jsonPre).not.toHaveTextContent('undefined'); |
| 219 | + }); |
| 220 | + |
205 | 221 | it('renders empty JSON pre when jsonToShow is undefined (converter returns undefined)', async () => { |
206 | 222 | mockConvert.mockResolvedValueOnce(undefined); |
207 | 223 |
|
@@ -243,4 +259,153 @@ describe('InterlinearizerWebView', () => { |
243 | 259 | expect(jsonPre).toBeInTheDocument(); |
244 | 260 | expect(jsonPre).toBeEmptyDOMElement(); |
245 | 261 | }); |
| 262 | + |
| 263 | + describe('handleJsonViewModeKeyDown', () => { |
| 264 | + it('ArrowRight moves to next mode and updates selection', async () => { |
| 265 | + await renderWebView(); |
| 266 | + const radiogroup = screen.getByRole('radiogroup', { name: /json view mode/i }); |
| 267 | + expect(screen.getByText(/^InterlinearData \(JSON\):$/)).toBeInTheDocument(); |
| 268 | + |
| 269 | + await act(async () => { |
| 270 | + fireEvent.keyDown(radiogroup, { key: 'ArrowRight' }); |
| 271 | + }); |
| 272 | + |
| 273 | + expect(screen.getByText(/^Interlinearization \(JSON\):$/)).toBeInTheDocument(); |
| 274 | + expect(screen.getByRole('radio', { name: /^interlinearization$/i })).toHaveAttribute( |
| 275 | + 'aria-checked', |
| 276 | + 'true', |
| 277 | + ); |
| 278 | + }); |
| 279 | + |
| 280 | + it('ArrowDown moves to next mode', async () => { |
| 281 | + await renderWebView(); |
| 282 | + const radiogroup = screen.getByRole('radiogroup', { name: /json view mode/i }); |
| 283 | + |
| 284 | + await act(async () => { |
| 285 | + fireEvent.keyDown(radiogroup, { key: 'ArrowDown' }); |
| 286 | + }); |
| 287 | + expect(screen.getByText(/^Interlinearization \(JSON\):$/)).toBeInTheDocument(); |
| 288 | + |
| 289 | + await act(async () => { |
| 290 | + fireEvent.keyDown(radiogroup, { key: 'ArrowDown' }); |
| 291 | + }); |
| 292 | + expect(screen.getByText(/^Analyses \(JSON\):$/)).toBeInTheDocument(); |
| 293 | + }); |
| 294 | + |
| 295 | + it('ArrowRight from last mode (Analyses) wraps to first (InterlinearData)', async () => { |
| 296 | + await renderWebView(); |
| 297 | + const radiogroup = screen.getByRole('radiogroup', { name: /json view mode/i }); |
| 298 | + fireEvent.click(screen.getByRole('radio', { name: /^analyses$/i })); |
| 299 | + expect(screen.getByText(/^Analyses \(JSON\):$/)).toBeInTheDocument(); |
| 300 | + |
| 301 | + await act(async () => { |
| 302 | + fireEvent.keyDown(radiogroup, { key: 'ArrowRight' }); |
| 303 | + }); |
| 304 | + |
| 305 | + expect(screen.getByText(/^InterlinearData \(JSON\):$/)).toBeInTheDocument(); |
| 306 | + expect(screen.getByRole('radio', { name: /^interlineardata$/i })).toHaveAttribute( |
| 307 | + 'aria-checked', |
| 308 | + 'true', |
| 309 | + ); |
| 310 | + }); |
| 311 | + |
| 312 | + it('ArrowLeft moves to previous mode', async () => { |
| 313 | + await renderWebView(); |
| 314 | + const radiogroup = screen.getByRole('radiogroup', { name: /json view mode/i }); |
| 315 | + fireEvent.click(screen.getByRole('radio', { name: /^analyses$/i })); |
| 316 | + expect(screen.getByText(/^Analyses \(JSON\):$/)).toBeInTheDocument(); |
| 317 | + |
| 318 | + await act(async () => { |
| 319 | + fireEvent.keyDown(radiogroup, { key: 'ArrowLeft' }); |
| 320 | + }); |
| 321 | + |
| 322 | + expect(screen.getByText(/^Interlinearization \(JSON\):$/)).toBeInTheDocument(); |
| 323 | + expect(screen.getByRole('radio', { name: /^interlinearization$/i })).toHaveAttribute( |
| 324 | + 'aria-checked', |
| 325 | + 'true', |
| 326 | + ); |
| 327 | + }); |
| 328 | + |
| 329 | + it('ArrowUp moves to previous mode', async () => { |
| 330 | + await renderWebView(); |
| 331 | + const radiogroup = screen.getByRole('radiogroup', { name: /json view mode/i }); |
| 332 | + fireEvent.click(screen.getByRole('radio', { name: /^interlinearization$/i })); |
| 333 | + |
| 334 | + await act(async () => { |
| 335 | + fireEvent.keyDown(radiogroup, { key: 'ArrowUp' }); |
| 336 | + }); |
| 337 | + |
| 338 | + expect(screen.getByText(/^InterlinearData \(JSON\):$/)).toBeInTheDocument(); |
| 339 | + expect(screen.getByRole('radio', { name: /^interlineardata$/i })).toHaveAttribute( |
| 340 | + 'aria-checked', |
| 341 | + 'true', |
| 342 | + ); |
| 343 | + }); |
| 344 | + |
| 345 | + it('ArrowLeft from first mode (InterlinearData) wraps to last (Analyses)', async () => { |
| 346 | + await renderWebView(); |
| 347 | + const radiogroup = screen.getByRole('radiogroup', { name: /json view mode/i }); |
| 348 | + expect(screen.getByText(/^InterlinearData \(JSON\):$/)).toBeInTheDocument(); |
| 349 | + |
| 350 | + await act(async () => { |
| 351 | + fireEvent.keyDown(radiogroup, { key: 'ArrowLeft' }); |
| 352 | + }); |
| 353 | + |
| 354 | + expect(screen.getByText(/^Analyses \(JSON\):$/)).toBeInTheDocument(); |
| 355 | + expect(screen.getByRole('radio', { name: /^analyses$/i })).toHaveAttribute( |
| 356 | + 'aria-checked', |
| 357 | + 'true', |
| 358 | + ); |
| 359 | + }); |
| 360 | + |
| 361 | + it('non-arrow key does not change mode', async () => { |
| 362 | + await renderWebView(); |
| 363 | + const radiogroup = screen.getByRole('radiogroup', { name: /json view mode/i }); |
| 364 | + expect(screen.getByText(/^InterlinearData \(JSON\):$/)).toBeInTheDocument(); |
| 365 | + |
| 366 | + fireEvent.keyDown(radiogroup, { key: 'a' }); |
| 367 | + fireEvent.keyDown(radiogroup, { key: 'Enter' }); |
| 368 | + expect(screen.getByText(/^InterlinearData \(JSON\):$/)).toBeInTheDocument(); |
| 369 | + expect(screen.getByRole('radio', { name: /^interlineardata$/i })).toHaveAttribute( |
| 370 | + 'aria-checked', |
| 371 | + 'true', |
| 372 | + ); |
| 373 | + }); |
| 374 | + |
| 375 | + it('moves focus to the newly selected radio on arrow key', async () => { |
| 376 | + await renderWebView(); |
| 377 | + const radiogroup = screen.getByRole('radiogroup', { name: /json view mode/i }); |
| 378 | + const interlinearizationRadio = screen.getByRole('radio', { |
| 379 | + name: /^interlinearization$/i, |
| 380 | + }); |
| 381 | + |
| 382 | + await act(async () => { |
| 383 | + fireEvent.keyDown(radiogroup, { key: 'ArrowRight' }); |
| 384 | + }); |
| 385 | + |
| 386 | + expect(document.activeElement).toBe(interlinearizationRadio); |
| 387 | + }); |
| 388 | + |
| 389 | + it('does nothing when current view mode is not in JSON_VIEW_MODES (idx === -1)', async () => { |
| 390 | + const setJsonViewMode = jest.fn(); |
| 391 | + let useStateCallCount = 0; |
| 392 | + const useStateSpy = jest.spyOn(React, 'useState').mockImplementation(() => { |
| 393 | + useStateCallCount += 1; |
| 394 | + return useStateCallCount === 1 ? ['invalid', setJsonViewMode] : [undefined, jest.fn()]; |
| 395 | + }); |
| 396 | + |
| 397 | + try { |
| 398 | + await renderWebView(); |
| 399 | + const radiogroup = screen.getByRole('radiogroup', { name: /json view mode/i }); |
| 400 | + |
| 401 | + await act(async () => { |
| 402 | + fireEvent.keyDown(radiogroup, { key: 'ArrowRight' }); |
| 403 | + }); |
| 404 | + |
| 405 | + expect(setJsonViewMode).not.toHaveBeenCalled(); |
| 406 | + } finally { |
| 407 | + useStateSpy.mockRestore(); |
| 408 | + } |
| 409 | + }); |
| 410 | + }); |
246 | 411 | }); |
0 commit comments