@redacto.io/consent-sdk-react 3.4.0 → 4.0.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redacto.io/consent-sdk-react",
3
- "version": "3.4.0",
3
+ "version": "4.0.0",
4
4
  "main": "dist/index.cjs",
5
5
  "module": "dist/index.mjs",
6
6
  "types": "dist/index.d.ts",
@@ -22,30 +22,10 @@ import type { ConsentContent } from "./api/types";
22
22
  vi.mock("./api", () => ({
23
23
  fetchConsentContent: vi.fn(),
24
24
  submitConsentEvent: vi.fn(),
25
- submitGuardianInfo: vi.fn(),
26
- fetchTTSAudioUrls: vi.fn().mockResolvedValue({
27
- code: 200,
28
- status: "success",
29
- detail: {
30
- uuid: "notice-123",
31
- language: "en",
32
- notice_text_audio_url: "",
33
- additional_text_audio_url: "",
34
- notice_summary_audio_url: "",
35
- confirm_button_text_audio_url: "",
36
- decline_button_text_audio_url: "",
37
- privacy_policy_prefix_text_audio_url: "",
38
- privacy_policy_anchor_text_audio_url: "",
39
- purpose_section_heading_audio_url: "",
40
- notice_banner_heading_audio_url: "",
41
- purposes_audio: {
42
- "purpose-1": { name_audio_url: "", description_audio_url: "" },
43
- "purpose-2": { name_audio_url: "", description_audio_url: "" },
44
- },
45
- data_elements_audio: {},
46
- },
47
- }),
48
25
  clearApiCache: vi.fn(),
26
+ fetchTTSAudioUrls: vi.fn(),
27
+ initiateGuardianVerification: vi.fn(),
28
+ verifyGuardianStatus: vi.fn(),
49
29
  }));
50
30
 
51
31
  // Mock jwt-decode
@@ -67,16 +47,36 @@ vi.mock("./injectStyles", () => ({
67
47
  injectCheckboxStyles: vi.fn(),
68
48
  }));
69
49
 
50
+ // Mock storage service
51
+ vi.mock("./services/storage", () => ({
52
+ saveConsentState: vi.fn(),
53
+ getConsentState: vi.fn(() => null),
54
+ clearConsentState: vi.fn(),
55
+ updateConsentState: vi.fn(),
56
+ hasPendingVerification: vi.fn(() => false),
57
+ getVerificationResult: vi.fn(() => null),
58
+ getVerificationReference: vi.fn(() => null),
59
+ }));
60
+
61
+ // Mock window.open for popup tests
62
+ const mockWindowOpen = vi.fn(() => ({
63
+ closed: false,
64
+ focus: vi.fn(),
65
+ close: vi.fn(),
66
+ }));
67
+ Object.defineProperty(window, 'open', {
68
+ writable: true,
69
+ value: mockWindowOpen,
70
+ });
71
+
70
72
  describe("RedactoNoticeConsent", () => {
71
- const mockedApi = vi.mocked(api, { partial: true });
72
- beforeEach(() => {
73
- vi.clearAllMocks();
73
+ const mockedApi = vi.mocked(api);
74
+
75
+ const setupCommonMocks = () => {
74
76
  // Mock API calls synchronously to avoid act() warnings
75
- mockedApi.fetchConsentContent.mockReturnValue(
76
- Promise.resolve(mockConsentContent)
77
- );
78
- mockedApi.submitConsentEvent.mockReturnValue(Promise.resolve(undefined));
79
- mockedApi.submitGuardianInfo.mockReturnValue(Promise.resolve(undefined));
77
+ mockedApi.fetchConsentContent.mockResolvedValue(mockConsentContent);
78
+ mockedApi.submitConsentEvent.mockResolvedValue(undefined);
79
+ // Mock TTS audio URLs API
80
80
  mockedApi.fetchTTSAudioUrls.mockResolvedValue({
81
81
  code: 200,
82
82
  status: "success",
@@ -99,6 +99,35 @@ describe("RedactoNoticeConsent", () => {
99
99
  data_elements_audio: {},
100
100
  },
101
101
  });
102
+ // Mock guardian verification APIs (DigiLocker flow)
103
+ mockedApi.initiateGuardianVerification.mockResolvedValue({
104
+ success: true,
105
+ already_verified: false,
106
+ session_token: "gvs_test-session-token",
107
+ digilocker_redirect_url: "https://digilocker.example.com/authorize?session=xyz",
108
+ expires_at: new Date(Date.now() + 600000).toISOString(),
109
+ guardian_info_uuid: "test-guardian-uuid",
110
+ });
111
+ mockedApi.verifyGuardianStatus.mockResolvedValue({
112
+ status: "verified" as const,
113
+ guardian_info_uuid: "test-guardian-uuid",
114
+ verification_reference: "VER_ABC123",
115
+ guardian_details: {
116
+ verified_name: "John Doe",
117
+ is_guardian_adult: true,
118
+ verified_at: new Date().toISOString(),
119
+ verification_reference: "VER_ABC123",
120
+ },
121
+ error: null,
122
+ error_code: null,
123
+ can_retry: false,
124
+ });
125
+ };
126
+
127
+ beforeEach(() => {
128
+ vi.resetAllMocks();
129
+ mockWindowOpen.mockClear();
130
+ setupCommonMocks();
102
131
  });
103
132
 
104
133
  afterEach(() => {
@@ -113,7 +142,7 @@ describe("RedactoNoticeConsent", () => {
113
142
  });
114
143
 
115
144
  it("renders consent content after loading", async () => {
116
- act(() => {
145
+ await act(async () => {
117
146
  render(<RedactoNoticeConsent {...defaultProps} />);
118
147
  });
119
148
  await waitFor(
@@ -125,8 +154,6 @@ describe("RedactoNoticeConsent", () => {
125
154
  expect(
126
155
  screen.getByRole("link", { name: /Privacy Policy/i })
127
156
  ).toBeInTheDocument();
128
- expect(screen.getByText("[Vendors List]")).toBeInTheDocument();
129
- expect(screen.getByText("Manage What You Share")).toBeInTheDocument();
130
157
  expect(screen.getByText("Marketing")).toBeInTheDocument();
131
158
  },
132
159
  { timeout: 3000 }
@@ -169,7 +196,9 @@ describe("RedactoNoticeConsent", () => {
169
196
  });
170
197
 
171
198
  it("handles language selection", async () => {
172
- render(<RedactoNoticeConsent {...defaultProps} />);
199
+ await act(async () => {
200
+ render(<RedactoNoticeConsent {...defaultProps} />);
201
+ });
173
202
  await waitFor(
174
203
  () => {
175
204
  expect(screen.getByRole("button", { name: "en" })).toBeInTheDocument();
@@ -271,7 +300,9 @@ describe("RedactoNoticeConsent", () => {
271
300
  });
272
301
 
273
302
  it("disables required data element checkboxes", async () => {
274
- render(<RedactoNoticeConsent {...defaultProps} />);
303
+ await act(async () => {
304
+ render(<RedactoNoticeConsent {...defaultProps} />);
305
+ });
275
306
  await waitFor(
276
307
  () => {
277
308
  expect(screen.getByText("Marketing")).toBeInTheDocument();
@@ -314,7 +345,9 @@ describe("RedactoNoticeConsent", () => {
314
345
  });
315
346
 
316
347
  it("calls submitConsentEvent on accept", async () => {
317
- render(<RedactoNoticeConsent {...defaultProps} />);
348
+ await act(async () => {
349
+ render(<RedactoNoticeConsent {...defaultProps} />);
350
+ });
318
351
  await waitFor(
319
352
  () => {
320
353
  expect(screen.getByText("Accept")).toBeInTheDocument();
@@ -362,6 +395,7 @@ describe("RedactoNoticeConsent", () => {
362
395
  ],
363
396
  declined: false,
364
397
  meta_data: undefined,
398
+ guardianVerificationReference: undefined,
365
399
  signal: expect.any(AbortSignal),
366
400
  });
367
401
  expect(defaultProps.onAccept).toHaveBeenCalled();
@@ -441,30 +475,10 @@ describe("RedactoNoticeConsent", () => {
441
475
 
442
476
  describe("reconsent functionality", () => {
443
477
  beforeEach(() => {
478
+ vi.resetAllMocks();
479
+ mockWindowOpen.mockClear();
480
+ setupCommonMocks();
444
481
  mockedApi.fetchConsentContent.mockResolvedValue(mockReconsentContent);
445
- mockedApi.submitConsentEvent.mockResolvedValue(undefined);
446
- mockedApi.fetchTTSAudioUrls.mockResolvedValue({
447
- code: 200,
448
- status: "success",
449
- detail: {
450
- uuid: "notice-123",
451
- language: "en",
452
- notice_text_audio_url: "",
453
- additional_text_audio_url: "",
454
- notice_summary_audio_url: "",
455
- confirm_button_text_audio_url: "",
456
- decline_button_text_audio_url: "",
457
- privacy_policy_prefix_text_audio_url: "",
458
- privacy_policy_anchor_text_audio_url: "",
459
- purpose_section_heading_audio_url: "",
460
- notice_banner_heading_audio_url: "",
461
- purposes_audio: {
462
- "purpose-1": { name_audio_url: "", description_audio_url: "" },
463
- "purpose-2": { name_audio_url: "", description_audio_url: "" },
464
- },
465
- data_elements_audio: {},
466
- },
467
- });
468
482
  });
469
483
 
470
484
  it("shows reconsent UI with visual indicators for different consent states", async () => {
@@ -499,9 +513,11 @@ describe("RedactoNoticeConsent", () => {
499
513
  });
500
514
 
501
515
  it("passes validate_against parameter to API", async () => {
502
- render(
503
- <RedactoNoticeConsent {...defaultProps} validateAgainst="required" />
504
- );
516
+ await act(async () => {
517
+ render(
518
+ <RedactoNoticeConsent {...defaultProps} validateAgainst="required" />
519
+ );
520
+ });
505
521
  await waitFor(
506
522
  () => {
507
523
  expect(api.fetchConsentContent).toHaveBeenCalledWith({
@@ -521,7 +537,9 @@ describe("RedactoNoticeConsent", () => {
521
537
  });
522
538
 
523
539
  it("defaults to validate_against 'all' when not specified", async () => {
524
- render(<RedactoNoticeConsent {...defaultProps} />);
540
+ await act(async () => {
541
+ render(<RedactoNoticeConsent {...defaultProps} />);
542
+ });
525
543
  await waitFor(
526
544
  () => {
527
545
  expect(api.fetchConsentContent).toHaveBeenCalledWith({
@@ -596,9 +614,10 @@ describe("RedactoNoticeConsent", () => {
596
614
 
597
615
  describe("age verification and minor consent functionality", () => {
598
616
  beforeEach(() => {
617
+ vi.resetAllMocks();
618
+ mockWindowOpen.mockClear();
619
+ setupCommonMocks();
599
620
  mockedApi.fetchConsentContent.mockResolvedValue(mockMinorConsentContent);
600
- mockedApi.submitConsentEvent.mockResolvedValue(undefined);
601
- mockedApi.submitGuardianInfo.mockResolvedValue(undefined);
602
621
  });
603
622
 
604
623
  it("shows age verification when is_minor is true", async () => {
@@ -653,11 +672,30 @@ describe("RedactoNoticeConsent", () => {
653
672
  screen.queryByText("Age Verification Required")
654
673
  ).not.toBeInTheDocument();
655
674
  expect(
656
- screen.queryByText("Guardian Information Required")
675
+ screen.queryByText("Guardian Verification")
657
676
  ).not.toBeInTheDocument();
658
677
  },
659
678
  { timeout: 3000 }
660
679
  );
680
+
681
+ // Verify the adult can submit consent without guardian verification
682
+ const acceptButton = screen.getByText("Accept");
683
+ fireEvent.click(acceptButton);
684
+
685
+ await waitFor(
686
+ () => {
687
+ expect(mockedApi.submitConsentEvent).toHaveBeenCalledWith(
688
+ expect.objectContaining({
689
+ accessToken: "mock-token",
690
+ noticeUuid: "notice-123",
691
+ declined: false,
692
+ guardianVerificationReference: undefined,
693
+ })
694
+ );
695
+ expect(defaultProps.onAccept).toHaveBeenCalled();
696
+ },
697
+ { timeout: 3000 }
698
+ );
661
699
  });
662
700
 
663
701
  it("shows guardian form when user clicks No (under 18)", async () => {
@@ -678,7 +716,7 @@ describe("RedactoNoticeConsent", () => {
678
716
  await waitFor(
679
717
  () => {
680
718
  expect(
681
- screen.getByText("Guardian Information Required")
719
+ screen.getByText("Guardian Verification")
682
720
  ).toBeInTheDocument();
683
721
  expect(
684
722
  screen.queryByText("Age Verification Required")
@@ -688,7 +726,7 @@ describe("RedactoNoticeConsent", () => {
688
726
  );
689
727
  });
690
728
 
691
- it("completes full minor flow: age verification -> guardian form -> consent", async () => {
729
+ it("completes full minor flow: age verification -> guardian form -> verification screen", async () => {
692
730
  render(<RedactoNoticeConsent {...defaultProps} />);
693
731
  await waitFor(
694
732
  () => {
@@ -707,35 +745,33 @@ describe("RedactoNoticeConsent", () => {
707
745
  await waitFor(
708
746
  () => {
709
747
  expect(
710
- screen.getByText("Guardian Information Required")
748
+ screen.getByText("Guardian Verification")
711
749
  ).toBeInTheDocument();
712
750
  expect(
713
- screen.getByText(/As you are under the age of consent/i)
751
+ screen.getByText(/a legal guardian must verify/i)
714
752
  ).toBeInTheDocument();
715
753
  },
716
754
  { timeout: 3000 }
717
755
  );
718
756
 
719
757
  // Fill out guardian form
720
- const nameInput = screen.getByLabelText(/Name of Guardian/i);
721
- const contactInput = screen.getByLabelText(/Contact of Guardian/i);
722
- const relationshipInput = screen.getByLabelText(
723
- /Relationship to Guardian/i
724
- );
725
-
758
+ const nameInput = screen.getByLabelText(/Guardian's Full Name/i);
759
+ const contactInput = screen.getByLabelText(/Guardian's Phone Number/i);
726
760
  fireEvent.change(nameInput, { target: { value: "John Doe" } });
727
761
  fireEvent.change(contactInput, { target: { value: "john@example.com" } });
728
- fireEvent.change(relationshipInput, { target: { value: "Parent" } });
762
+ // Select "Father" from the custom dropdown
763
+ const relationshipDropdown = screen.getByRole("combobox", { name: /Relationship/i });
764
+ fireEvent.click(relationshipDropdown);
765
+ fireEvent.click(screen.getByRole("option", { name: "Father" }));
729
766
 
730
- const nextButton = screen.getByText("Next");
767
+ const nextButton = screen.getByText("Verify via DigiLocker");
731
768
  fireEvent.click(nextButton);
732
769
 
733
- // Should transition to consent form
770
+ // Should transition to verification screen (guardian form text should be gone)
734
771
  await waitFor(
735
772
  () => {
736
- expect(screen.getByText("Privacy Notice")).toBeInTheDocument();
737
773
  expect(
738
- screen.queryByText("Guardian Information Required")
774
+ screen.queryByText(/a legal guardian must verify/i)
739
775
  ).not.toBeInTheDocument();
740
776
  },
741
777
  { timeout: 3000 }
@@ -759,17 +795,17 @@ describe("RedactoNoticeConsent", () => {
759
795
 
760
796
  await waitFor(
761
797
  () => {
762
- expect(screen.getByAltText("Redacto Logo")).toBeInTheDocument();
798
+ expect(screen.getByAltText("Logo")).toBeInTheDocument();
763
799
  expect(
764
- screen.getByLabelText(/Name of Guardian/i)
800
+ screen.getByLabelText(/Guardian's Full Name/i)
765
801
  ).toBeInTheDocument();
766
802
  expect(
767
- screen.getByLabelText(/Contact of Guardian/i)
803
+ screen.getByLabelText(/Guardian's Phone Number/i)
768
804
  ).toBeInTheDocument();
769
805
  expect(
770
- screen.getByLabelText(/Relationship to Guardian/i)
806
+ screen.getByRole("combobox", { name: /Relationship/i })
771
807
  ).toBeInTheDocument();
772
- expect(screen.getByText("Next")).toBeInTheDocument();
808
+ expect(screen.getByText("Verify via DigiLocker")).toBeInTheDocument();
773
809
  expect(screen.getByText("Cancel")).toBeInTheDocument();
774
810
  },
775
811
  { timeout: 3000 }
@@ -794,13 +830,13 @@ describe("RedactoNoticeConsent", () => {
794
830
  await waitFor(
795
831
  () => {
796
832
  expect(
797
- screen.getByText("Guardian Information Required")
833
+ screen.getByText("Guardian Verification")
798
834
  ).toBeInTheDocument();
799
835
  },
800
836
  { timeout: 3000 }
801
837
  );
802
838
 
803
- const nextButton = screen.getByText("Next");
839
+ const nextButton = screen.getByText("Verify via DigiLocker");
804
840
  fireEvent.click(nextButton);
805
841
 
806
842
  await waitFor(
@@ -819,7 +855,7 @@ describe("RedactoNoticeConsent", () => {
819
855
  );
820
856
  });
821
857
 
822
- it("transitions to consent form after filling guardian form", async () => {
858
+ it("transitions to verification screen after filling guardian form", async () => {
823
859
  render(<RedactoNoticeConsent {...defaultProps} />);
824
860
  await waitFor(
825
861
  () => {
@@ -837,32 +873,30 @@ describe("RedactoNoticeConsent", () => {
837
873
  await waitFor(
838
874
  () => {
839
875
  expect(
840
- screen.getByText("Guardian Information Required")
876
+ screen.getByText("Guardian Verification")
841
877
  ).toBeInTheDocument();
842
878
  },
843
879
  { timeout: 3000 }
844
880
  );
845
881
 
846
882
  // Fill out guardian form
847
- const nameInput = screen.getByLabelText(/Name of Guardian/i);
848
- const contactInput = screen.getByLabelText(/Contact of Guardian/i);
849
- const relationshipInput = screen.getByLabelText(
850
- /Relationship to Guardian/i
851
- );
852
-
883
+ const nameInput = screen.getByLabelText(/Guardian's Full Name/i);
884
+ const contactInput = screen.getByLabelText(/Guardian's Phone Number/i);
853
885
  fireEvent.change(nameInput, { target: { value: "John Doe" } });
854
886
  fireEvent.change(contactInput, { target: { value: "john@example.com" } });
855
- fireEvent.change(relationshipInput, { target: { value: "Parent" } });
887
+ // Select "Father" from the custom dropdown
888
+ const relationshipDropdown = screen.getByRole("combobox", { name: /Relationship/i });
889
+ fireEvent.click(relationshipDropdown);
890
+ fireEvent.click(screen.getByRole("option", { name: "Father" }));
856
891
 
857
- const nextButton = screen.getByText("Next");
892
+ const nextButton = screen.getByText("Verify via DigiLocker");
858
893
  fireEvent.click(nextButton);
859
894
 
860
- // Should transition to consent form
895
+ // Should transition to verification screen (guardian form fields should be gone)
861
896
  await waitFor(
862
897
  () => {
863
- expect(screen.getByText("Privacy Notice")).toBeInTheDocument();
864
898
  expect(
865
- screen.queryByText("Guardian Information Required")
899
+ screen.queryByLabelText(/Guardian's Full Name/i)
866
900
  ).not.toBeInTheDocument();
867
901
  },
868
902
  { timeout: 3000 }
@@ -887,24 +921,20 @@ describe("RedactoNoticeConsent", () => {
887
921
  await waitFor(
888
922
  () => {
889
923
  expect(
890
- screen.getByText("Guardian Information Required")
924
+ screen.getByText("Guardian Verification")
891
925
  ).toBeInTheDocument();
892
926
  },
893
927
  { timeout: 3000 }
894
928
  );
895
929
 
896
- // Fill fields with only whitespace
897
- const nameInput = screen.getByLabelText(/Name of Guardian/i);
898
- const contactInput = screen.getByLabelText(/Contact of Guardian/i);
899
- const relationshipInput = screen.getByLabelText(
900
- /Relationship to Guardian/i
901
- );
930
+ // Fill fields with only whitespace (leave relationship empty — don't select anything)
931
+ const nameInput = screen.getByLabelText(/Guardian's Full Name/i);
932
+ const contactInput = screen.getByLabelText(/Guardian's Phone Number/i);
902
933
 
903
934
  fireEvent.change(nameInput, { target: { value: " " } });
904
935
  fireEvent.change(contactInput, { target: { value: "\t\t" } });
905
- fireEvent.change(relationshipInput, { target: { value: "\n\n" } });
906
936
 
907
- const nextButton = screen.getByText("Next");
937
+ const nextButton = screen.getByText("Verify via DigiLocker");
908
938
  fireEvent.click(nextButton);
909
939
 
910
940
  // Should still show validation errors for whitespace-only input
@@ -942,14 +972,14 @@ describe("RedactoNoticeConsent", () => {
942
972
  await waitFor(
943
973
  () => {
944
974
  expect(
945
- screen.getByText("Guardian Information Required")
975
+ screen.getByText("Guardian Verification")
946
976
  ).toBeInTheDocument();
947
977
  },
948
978
  { timeout: 3000 }
949
979
  );
950
980
 
951
981
  // Click next with empty fields to trigger errors
952
- const nextButton = screen.getByText("Next");
982
+ const nextButton = screen.getByText("Verify via DigiLocker");
953
983
  fireEvent.click(nextButton);
954
984
 
955
985
  await waitFor(
@@ -962,7 +992,7 @@ describe("RedactoNoticeConsent", () => {
962
992
  );
963
993
 
964
994
  // Start typing in name field
965
- const nameInput = screen.getByLabelText(/Name of Guardian/i);
995
+ const nameInput = screen.getByLabelText(/Guardian's Full Name/i);
966
996
  fireEvent.change(nameInput, { target: { value: "J" } });
967
997
 
968
998
  // Error should be cleared
@@ -994,7 +1024,7 @@ describe("RedactoNoticeConsent", () => {
994
1024
  await waitFor(
995
1025
  () => {
996
1026
  expect(
997
- screen.getByText("Guardian Information Required")
1027
+ screen.getByText("Guardian Verification")
998
1028
  ).toBeInTheDocument();
999
1029
  },
1000
1030
  { timeout: 3000 }
@@ -1029,13 +1059,13 @@ describe("RedactoNoticeConsent", () => {
1029
1059
  await waitFor(
1030
1060
  () => {
1031
1061
  expect(
1032
- screen.getByText("Guardian Information Required")
1062
+ screen.getByText("Guardian Verification")
1033
1063
  ).toBeInTheDocument();
1034
1064
  },
1035
1065
  { timeout: 3000 }
1036
1066
  );
1037
1067
 
1038
- const contactInput = screen.getByLabelText(/Contact of Guardian/i);
1068
+ const contactInput = screen.getByLabelText(/Guardian's Phone Number/i);
1039
1069
 
1040
1070
  // Focus on contact field and type
1041
1071
  contactInput.focus();
@@ -1053,12 +1083,65 @@ describe("RedactoNoticeConsent", () => {
1053
1083
  expect(contactInput).toHaveValue("john@example.com");
1054
1084
 
1055
1085
  // Test other fields maintain focus too
1056
- const nameInput = screen.getByLabelText(/Name of Guardian/i);
1086
+ const nameInput = screen.getByLabelText(/Guardian's Full Name/i);
1057
1087
  nameInput.focus();
1058
1088
  fireEvent.change(nameInput, { target: { value: "J" } });
1059
1089
 
1060
1090
  expect(nameInput).toHaveFocus();
1061
1091
  expect(nameInput).toHaveValue("J");
1062
1092
  });
1093
+
1094
+ it("calls initiateGuardianVerification with correct params", async () => {
1095
+ render(<RedactoNoticeConsent {...defaultProps} />);
1096
+ await waitFor(
1097
+ () => {
1098
+ expect(
1099
+ screen.getByText("Age Verification Required")
1100
+ ).toBeInTheDocument();
1101
+ },
1102
+ { timeout: 3000 }
1103
+ );
1104
+
1105
+ // Navigate to guardian form
1106
+ const noButton = screen.getByText("No, I am under 18");
1107
+ fireEvent.click(noButton);
1108
+
1109
+ await waitFor(
1110
+ () => {
1111
+ expect(
1112
+ screen.getByText("Guardian Verification")
1113
+ ).toBeInTheDocument();
1114
+ },
1115
+ { timeout: 3000 }
1116
+ );
1117
+
1118
+ // Fill out guardian form
1119
+ const nameInput = screen.getByLabelText(/Guardian's Full Name/i);
1120
+ const contactInput = screen.getByLabelText(/Guardian's Phone Number/i);
1121
+ fireEvent.change(nameInput, { target: { value: "John Doe" } });
1122
+ fireEvent.change(contactInput, { target: { value: "john@example.com" } });
1123
+ // Select "Father" from the custom dropdown
1124
+ const relationshipDropdown = screen.getByRole("combobox", { name: /Relationship/i });
1125
+ fireEvent.click(relationshipDropdown);
1126
+ fireEvent.click(screen.getByRole("option", { name: "Father" }));
1127
+
1128
+ const nextButton = screen.getByText("Verify via DigiLocker");
1129
+ fireEvent.click(nextButton);
1130
+
1131
+ // Verify initiateGuardianVerification was called with correct params
1132
+ await waitFor(
1133
+ () => {
1134
+ expect(api.initiateGuardianVerification).toHaveBeenCalledWith(
1135
+ expect.objectContaining({
1136
+ accessToken: "mock-token",
1137
+ guardianName: "John Doe",
1138
+ guardianContact: "john@example.com",
1139
+ guardianRelationship: "Father",
1140
+ })
1141
+ );
1142
+ },
1143
+ { timeout: 3000 }
1144
+ );
1145
+ });
1063
1146
  });
1064
1147
  });