|
1 | | -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; |
| 1 | +import { describe, it, expect, vi, beforeEach, afterEach, expectTypeOf } from 'vitest'; |
2 | 2 | import { renderHook } from '@testing-library/react'; |
3 | 3 | import { sendEvent, useEmbed, EmbedActions } from './hook'; |
4 | 4 |
|
@@ -232,22 +232,6 @@ describe('useEmbed', () => { |
232 | 232 | expect(result.current.embedRef.current).toBeNull(); |
233 | 233 | expect(result.current.actions).toBeDefined(); |
234 | 234 | }); |
235 | | - |
236 | | - it('exposes all action methods', () => { |
237 | | - const { result } = renderHook(() => useEmbed()); |
238 | | - |
239 | | - const expectedActions: (keyof EmbedActions)[] = [ |
240 | | - 'goTo', |
241 | | - 'selectTool', |
242 | | - 'createField', |
243 | | - 'clearFields', |
244 | | - 'getDocumentContent', |
245 | | - 'submit', |
246 | | - ]; |
247 | | - expectedActions.forEach((action) => { |
248 | | - expect(typeof result.current.actions[action]).toBe('function'); |
249 | | - }); |
250 | | - }); |
251 | 235 | }); |
252 | 236 |
|
253 | 237 | describe('actions without ref attached', () => { |
@@ -393,3 +377,175 @@ describe('useEmbed', () => { |
393 | 377 | expect(result.current.actions.submit).toBe(initialActions.submit); |
394 | 378 | }); |
395 | 379 | }); |
| 380 | + |
| 381 | +describe('Type assertions', () => { |
| 382 | + // These types are intentionally inlined to act as a "frozen" contract. |
| 383 | + // If the actual types change, these tests will fail at compile time. |
| 384 | + |
| 385 | + type ExpectedToolType = 'TEXT' | 'BOXED_TEXT' | 'CHECKBOX' | 'PICTURE' | 'SIGNATURE'; |
| 386 | + |
| 387 | + type ExpectedBaseFieldOptions = { |
| 388 | + page: number; |
| 389 | + x: number; |
| 390 | + y: number; |
| 391 | + width: number; |
| 392 | + height: number; |
| 393 | + }; |
| 394 | + |
| 395 | + type ExpectedTextFieldOptions = ExpectedBaseFieldOptions & { |
| 396 | + type: 'TEXT' | 'BOXED_TEXT'; |
| 397 | + value?: string; |
| 398 | + }; |
| 399 | + |
| 400 | + type ExpectedCheckboxFieldOptions = ExpectedBaseFieldOptions & { |
| 401 | + type: 'CHECKBOX'; |
| 402 | + value?: 'checked' | 'unchecked'; |
| 403 | + }; |
| 404 | + |
| 405 | + type ExpectedPictureFieldOptions = ExpectedBaseFieldOptions & { |
| 406 | + type: 'PICTURE'; |
| 407 | + value?: string; |
| 408 | + }; |
| 409 | + |
| 410 | + type ExpectedSignatureFieldOptions = ExpectedBaseFieldOptions & { |
| 411 | + type: 'SIGNATURE'; |
| 412 | + value?: string; |
| 413 | + }; |
| 414 | + |
| 415 | + type ExpectedCreateFieldOptions = |
| 416 | + | ExpectedTextFieldOptions |
| 417 | + | ExpectedCheckboxFieldOptions |
| 418 | + | ExpectedPictureFieldOptions |
| 419 | + | ExpectedSignatureFieldOptions; |
| 420 | + |
| 421 | + type ExpectedErrorResult = { |
| 422 | + success: false; |
| 423 | + error: { code: string; message: string }; |
| 424 | + }; |
| 425 | + |
| 426 | + type ExpectedSuccessResult<TData = undefined> = TData extends undefined |
| 427 | + ? { success: true } |
| 428 | + : { success: true; data: TData }; |
| 429 | + |
| 430 | + type ExpectedActionResult<TData = undefined> = ExpectedSuccessResult<TData> | ExpectedErrorResult; |
| 431 | + |
| 432 | + describe('EmbedActions', () => { |
| 433 | + it('goTo accepts { page: number } and returns ActionResult', () => { |
| 434 | + expectTypeOf<EmbedActions['goTo']>().parameter(0).toEqualTypeOf<{ page: number }>(); |
| 435 | + expectTypeOf<EmbedActions['goTo']>().returns.resolves.toExtend<ExpectedActionResult>(); |
| 436 | + }); |
| 437 | + |
| 438 | + it('selectTool accepts ToolType | null and returns ActionResult', () => { |
| 439 | + expectTypeOf<EmbedActions['selectTool']>().parameter(0).toEqualTypeOf<ExpectedToolType | null>(); |
| 440 | + expectTypeOf<EmbedActions['selectTool']>().returns.resolves.toExtend<ExpectedActionResult>(); |
| 441 | + }); |
| 442 | + |
| 443 | + it('createField accepts CreateFieldOptions and returns ActionResult with field_id', () => { |
| 444 | + expectTypeOf<EmbedActions['createField']>().parameter(0).toEqualTypeOf<ExpectedCreateFieldOptions>(); |
| 445 | + expectTypeOf<EmbedActions['createField']>().returns.resolves.toExtend< |
| 446 | + ExpectedActionResult<{ field_id: string }> |
| 447 | + >(); |
| 448 | + }); |
| 449 | + |
| 450 | + it('clearFields accepts optional { fieldIds?, page? } and returns ActionResult with cleared_count', () => { |
| 451 | + expectTypeOf<EmbedActions['clearFields']>() |
| 452 | + .parameter(0) |
| 453 | + .toEqualTypeOf<{ fieldIds?: string[]; page?: number } | undefined>(); |
| 454 | + expectTypeOf<EmbedActions['clearFields']>().returns.resolves.toExtend< |
| 455 | + ExpectedActionResult<{ cleared_count: number }> |
| 456 | + >(); |
| 457 | + }); |
| 458 | + |
| 459 | + it('getDocumentContent requires { extractionMode } and returns ActionResult with document content', () => { |
| 460 | + expectTypeOf<EmbedActions['getDocumentContent']>().parameter(0).toEqualTypeOf<{ |
| 461 | + extractionMode: 'auto' | 'ocr'; |
| 462 | + }>(); |
| 463 | + expectTypeOf<EmbedActions['getDocumentContent']>().returns.resolves.toExtend< |
| 464 | + ExpectedActionResult<{ name: string; pages: { page: number; content: string }[] }> |
| 465 | + >(); |
| 466 | + }); |
| 467 | + |
| 468 | + it('submit requires { downloadCopyOnDevice } and returns ActionResult', () => { |
| 469 | + expectTypeOf<EmbedActions['submit']>().parameter(0).toEqualTypeOf<{ downloadCopyOnDevice: boolean }>(); |
| 470 | + expectTypeOf<EmbedActions['submit']>().returns.resolves.toExtend<ExpectedActionResult>(); |
| 471 | + }); |
| 472 | + }); |
| 473 | + |
| 474 | + describe('createField discriminated union', () => { |
| 475 | + it('TEXT field options are accepted', () => { |
| 476 | + const textField = { |
| 477 | + type: 'TEXT' as const, |
| 478 | + page: 1, |
| 479 | + x: 0, |
| 480 | + y: 0, |
| 481 | + width: 100, |
| 482 | + height: 20, |
| 483 | + value: 'hello', |
| 484 | + }; |
| 485 | + expectTypeOf(textField).toExtend<ExpectedCreateFieldOptions>(); |
| 486 | + }); |
| 487 | + |
| 488 | + it('BOXED_TEXT field options are accepted', () => { |
| 489 | + const boxedTextField = { |
| 490 | + type: 'BOXED_TEXT' as const, |
| 491 | + page: 1, |
| 492 | + x: 0, |
| 493 | + y: 0, |
| 494 | + width: 100, |
| 495 | + height: 20, |
| 496 | + value: 'hello', |
| 497 | + }; |
| 498 | + expectTypeOf(boxedTextField).toExtend<ExpectedCreateFieldOptions>(); |
| 499 | + }); |
| 500 | + |
| 501 | + it('CHECKBOX field accepts only checked/unchecked values', () => { |
| 502 | + const checkedField = { |
| 503 | + type: 'CHECKBOX' as const, |
| 504 | + page: 1, |
| 505 | + x: 0, |
| 506 | + y: 0, |
| 507 | + width: 20, |
| 508 | + height: 20, |
| 509 | + value: 'checked' as const, |
| 510 | + }; |
| 511 | + expectTypeOf(checkedField).toExtend<ExpectedCreateFieldOptions>(); |
| 512 | + |
| 513 | + const uncheckedField = { |
| 514 | + type: 'CHECKBOX' as const, |
| 515 | + page: 1, |
| 516 | + x: 0, |
| 517 | + y: 0, |
| 518 | + width: 20, |
| 519 | + height: 20, |
| 520 | + value: 'unchecked' as const, |
| 521 | + }; |
| 522 | + expectTypeOf(uncheckedField).toExtend<ExpectedCreateFieldOptions>(); |
| 523 | + }); |
| 524 | + |
| 525 | + it('PICTURE field options are accepted', () => { |
| 526 | + const pictureField = { |
| 527 | + type: 'PICTURE' as const, |
| 528 | + page: 1, |
| 529 | + x: 0, |
| 530 | + y: 0, |
| 531 | + width: 100, |
| 532 | + height: 100, |
| 533 | + value: 'data:image/png;base64,...', |
| 534 | + }; |
| 535 | + expectTypeOf(pictureField).toExtend<ExpectedCreateFieldOptions>(); |
| 536 | + }); |
| 537 | + |
| 538 | + it('SIGNATURE field options are accepted', () => { |
| 539 | + const signatureField = { |
| 540 | + type: 'SIGNATURE' as const, |
| 541 | + page: 1, |
| 542 | + x: 0, |
| 543 | + y: 0, |
| 544 | + width: 150, |
| 545 | + height: 50, |
| 546 | + value: 'John Doe', |
| 547 | + }; |
| 548 | + expectTypeOf(signatureField).toExtend<ExpectedCreateFieldOptions>(); |
| 549 | + }); |
| 550 | + }); |
| 551 | +}); |
0 commit comments