@redacto.io/consent-sdk-react 10.0.0-beta.8 → 10.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.
Files changed (262) hide show
  1. package/NOTICE.md +17 -0
  2. package/README.md +141 -3
  3. package/dist/chunk-FEGTW73Q.mjs +182 -0
  4. package/dist/{chunk-6A665OPR.mjs → chunk-OGL6OC3A.mjs} +4 -0
  5. package/dist/fonts.d.mts +35 -0
  6. package/dist/fonts.d.ts +35 -0
  7. package/dist/fonts.js +183 -0
  8. package/dist/fonts.mjs +10 -0
  9. package/dist/index.d.mts +41 -4
  10. package/dist/index.d.ts +41 -4
  11. package/dist/index.js +881 -416
  12. package/dist/index.mjs +502 -253
  13. package/dist/montserrat.generated-NBTECYRW.mjs +22 -0
  14. package/dist/privacy-center.d.mts +51 -11
  15. package/dist/privacy-center.d.ts +51 -11
  16. package/dist/privacy-center.js +252 -136
  17. package/dist/privacy-center.mjs +251 -137
  18. package/dist/types-6Aj7faxL.d.mts +57 -0
  19. package/dist/types-6Aj7faxL.d.ts +57 -0
  20. package/package.json +10 -1
  21. package/.turbo/turbo-build.log +0 -22
  22. package/.turbo/turbo-test.log +0 -203
  23. package/.turbo/turbo-verify-pack.log +0 -6
  24. package/CHANGELOG.md +0 -343
  25. package/src/RedactoNoticeAssisted/RedactoNoticeAssisted.test.tsx +0 -934
  26. package/src/RedactoNoticeAssisted/RedactoNoticeAssisted.tsx +0 -2160
  27. package/src/RedactoNoticeAssisted/api/index.ts +0 -275
  28. package/src/RedactoNoticeAssisted/api/types.ts +0 -182
  29. package/src/RedactoNoticeAssisted/assets/redacto-logo.png +0 -0
  30. package/src/RedactoNoticeAssisted/constants.ts +0 -51
  31. package/src/RedactoNoticeAssisted/i18n.ts +0 -1408
  32. package/src/RedactoNoticeAssisted/index.ts +0 -1
  33. package/src/RedactoNoticeAssisted/injectStyles.ts +0 -251
  34. package/src/RedactoNoticeAssisted/styles.ts +0 -730
  35. package/src/RedactoNoticeAssisted/types.ts +0 -26
  36. package/src/RedactoNoticeAssisted/useMediaQuery.ts +0 -36
  37. package/src/RedactoNoticeConsent/RedactoNoticeConsent.test.tsx +0 -1152
  38. package/src/RedactoNoticeConsent/RedactoNoticeConsent.tsx +0 -4258
  39. package/src/RedactoNoticeConsent/api/index.ts +0 -512
  40. package/src/RedactoNoticeConsent/api/types.ts +0 -469
  41. package/src/RedactoNoticeConsent/assets/redacto-logo.png +0 -0
  42. package/src/RedactoNoticeConsent/components/DigiLockerCallback.tsx +0 -602
  43. package/src/RedactoNoticeConsent/components/index.ts +0 -7
  44. package/src/RedactoNoticeConsent/constants.ts +0 -20
  45. package/src/RedactoNoticeConsent/index.ts +0 -1
  46. package/src/RedactoNoticeConsent/injectStyles.ts +0 -151
  47. package/src/RedactoNoticeConsent/language-codes.ts +0 -95
  48. package/src/RedactoNoticeConsent/services/storage.ts +0 -122
  49. package/src/RedactoNoticeConsent/styles.ts +0 -409
  50. package/src/RedactoNoticeConsent/types.ts +0 -54
  51. package/src/RedactoNoticeConsent/useMediaQuery.ts +0 -36
  52. package/src/RedactoNoticeConsentInline/RedactoNoticeConsentInline.test.tsx +0 -369
  53. package/src/RedactoNoticeConsentInline/RedactoNoticeConsentInline.tsx +0 -687
  54. package/src/RedactoNoticeConsentInline/api/index.ts +0 -106
  55. package/src/RedactoNoticeConsentInline/api/types.ts +0 -183
  56. package/src/RedactoNoticeConsentInline/assets/redacto-logo.png +0 -0
  57. package/src/RedactoNoticeConsentInline/index.ts +0 -1
  58. package/src/RedactoNoticeConsentInline/injectStyles.ts +0 -40
  59. package/src/RedactoNoticeConsentInline/styles.ts +0 -397
  60. package/src/RedactoNoticeConsentInline/types.ts +0 -51
  61. package/src/RedactoNoticeConsentInline/useMediaQuery.ts +0 -36
  62. package/src/RedactoPrivacyCenter/NotificationsPage.tsx +0 -182
  63. package/src/RedactoPrivacyCenter/PrivacyCenterActivityPage.tsx +0 -19
  64. package/src/RedactoPrivacyCenter/PrivacyCenterCaseDetailsPage.tsx +0 -156
  65. package/src/RedactoPrivacyCenter/PrivacyCenterConsentManagerPage.tsx +0 -17
  66. package/src/RedactoPrivacyCenter/PrivacyCenterFormPage.tsx +0 -79
  67. package/src/RedactoPrivacyCenter/PrivacyCenterHomePage.tsx +0 -36
  68. package/src/RedactoPrivacyCenter/PrivacyCenterLayout.tsx +0 -177
  69. package/src/RedactoPrivacyCenter/PrivacyCenterMyReceiptPage.tsx +0 -26
  70. package/src/RedactoPrivacyCenter/RedactoPrivacyCenter.tsx +0 -58
  71. package/src/RedactoPrivacyCenter/api/actions.ts +0 -695
  72. package/src/RedactoPrivacyCenter/api/client.ts +0 -911
  73. package/src/RedactoPrivacyCenter/api/config.ts +0 -33
  74. package/src/RedactoPrivacyCenter/api/fetcher.ts +0 -260
  75. package/src/RedactoPrivacyCenter/api/orgBranding.ts +0 -16
  76. package/src/RedactoPrivacyCenter/api/upload.ts +0 -93
  77. package/src/RedactoPrivacyCenter/assets/character.svg +0 -31
  78. package/src/RedactoPrivacyCenter/assets/fileAttach.svg +0 -9
  79. package/src/RedactoPrivacyCenter/assets/greenTick.svg +0 -4
  80. package/src/RedactoPrivacyCenter/assets/mail.svg +0 -3
  81. package/src/RedactoPrivacyCenter/assets/poweredByRedacto.svg +0 -9
  82. package/src/RedactoPrivacyCenter/assets/sentImg.svg +0 -9
  83. package/src/RedactoPrivacyCenter/assets/uploadFileIcon.svg +0 -9
  84. package/src/RedactoPrivacyCenter/components/ActivitiesList.tsx +0 -252
  85. package/src/RedactoPrivacyCenter/components/ActivityTimeline.tsx +0 -239
  86. package/src/RedactoPrivacyCenter/components/AppShell/AppShell.tsx +0 -268
  87. package/src/RedactoPrivacyCenter/components/AppShell/MobileNavDrawer.tsx +0 -218
  88. package/src/RedactoPrivacyCenter/components/AppShell/NavItem.tsx +0 -205
  89. package/src/RedactoPrivacyCenter/components/AppShell/PageHeader.tsx +0 -107
  90. package/src/RedactoPrivacyCenter/components/AppShell/SidebarNav.tsx +0 -100
  91. package/src/RedactoPrivacyCenter/components/AppShell/TopAppBar.tsx +0 -339
  92. package/src/RedactoPrivacyCenter/components/AppShell/UserMenu.tsx +0 -245
  93. package/src/RedactoPrivacyCenter/components/AppShell/hooks/useIsTruncated.ts +0 -28
  94. package/src/RedactoPrivacyCenter/components/AppShell/hooks/useKeyboardShortcut.ts +0 -40
  95. package/src/RedactoPrivacyCenter/components/AppShell/hooks/useSidebarState.ts +0 -62
  96. package/src/RedactoPrivacyCenter/components/AppShell/usePrivacyCenterExitAction.ts +0 -36
  97. package/src/RedactoPrivacyCenter/components/CaseDetails/AttachmentCard.tsx +0 -38
  98. package/src/RedactoPrivacyCenter/components/CaseDetails/ChatBubble.tsx +0 -31
  99. package/src/RedactoPrivacyCenter/components/CaseDetails/DocumentRequestCard.tsx +0 -79
  100. package/src/RedactoPrivacyCenter/components/CaseDetails/FinalizedTooltipWrapper.tsx +0 -25
  101. package/src/RedactoPrivacyCenter/components/CaseDetails/MessageComposer.tsx +0 -96
  102. package/src/RedactoPrivacyCenter/components/CaseDetails/MessageItem.tsx +0 -97
  103. package/src/RedactoPrivacyCenter/components/CaseDetails/MessagesTab.tsx +0 -156
  104. package/src/RedactoPrivacyCenter/components/CaseDetails/RequestDetailsTab.tsx +0 -171
  105. package/src/RedactoPrivacyCenter/components/CaseDetails/StatusEvent.tsx +0 -23
  106. package/src/RedactoPrivacyCenter/components/CaseDetails/useFileUpload.ts +0 -116
  107. package/src/RedactoPrivacyCenter/components/CaseDetails/useMessagePolling.ts +0 -43
  108. package/src/RedactoPrivacyCenter/components/CaseDetails/utils.ts +0 -71
  109. package/src/RedactoPrivacyCenter/components/CaseDetailsModal.tsx +0 -469
  110. package/src/RedactoPrivacyCenter/components/CaseHistory.tsx +0 -426
  111. package/src/RedactoPrivacyCenter/components/ConsentManager.tsx +0 -1206
  112. package/src/RedactoPrivacyCenter/components/ConsentManagerCells.tsx +0 -218
  113. package/src/RedactoPrivacyCenter/components/ConsentManagerColumns.tsx +0 -138
  114. package/src/RedactoPrivacyCenter/components/ConsentManagerConstants.ts +0 -7
  115. package/src/RedactoPrivacyCenter/components/ConsentManagerExpandedRow.tsx +0 -179
  116. package/src/RedactoPrivacyCenter/components/ConsentManagerGroupedColumns.tsx +0 -318
  117. package/src/RedactoPrivacyCenter/components/ConsentManagerProductCards.tsx +0 -333
  118. package/src/RedactoPrivacyCenter/components/ConsentManagerPurposeCards.tsx +0 -102
  119. package/src/RedactoPrivacyCenter/components/ConsentManagerTableSkeleton.tsx +0 -159
  120. package/src/RedactoPrivacyCenter/components/DataRequest/CorrectionDataSection.tsx +0 -189
  121. package/src/RedactoPrivacyCenter/components/DataRequest/GrievanceDataSection.tsx +0 -118
  122. package/src/RedactoPrivacyCenter/components/DataRequest/NominationDataSection.tsx +0 -66
  123. package/src/RedactoPrivacyCenter/components/DataRequest/ProductSelectionSection.tsx +0 -222
  124. package/src/RedactoPrivacyCenter/components/DataRequest/PurposeSelectionSection.tsx +0 -331
  125. package/src/RedactoPrivacyCenter/components/Form.tsx +0 -1796
  126. package/src/RedactoPrivacyCenter/components/GrievanceTable.tsx +0 -174
  127. package/src/RedactoPrivacyCenter/components/GrievanceTablePagination.tsx +0 -337
  128. package/src/RedactoPrivacyCenter/components/ModifyConsentModal.tsx +0 -334
  129. package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationBell.tsx +0 -173
  130. package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationCTAButton.tsx +0 -18
  131. package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationDropdown.tsx +0 -54
  132. package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationDropdownItem.tsx +0 -57
  133. package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationIcon.tsx +0 -19
  134. package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationListPanel.tsx +0 -92
  135. package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationRefreshButton.tsx +0 -20
  136. package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationRow.tsx +0 -58
  137. package/src/RedactoPrivacyCenter/components/NotificationHub/StatCard.tsx +0 -10
  138. package/src/RedactoPrivacyCenter/components/NotificationHub/useNotificationActions.ts +0 -112
  139. package/src/RedactoPrivacyCenter/components/NotificationHub/utils.ts +0 -27
  140. package/src/RedactoPrivacyCenter/components/Pagination.tsx +0 -140
  141. package/src/RedactoPrivacyCenter/components/PrivacyCenterEmptyState.tsx +0 -76
  142. package/src/RedactoPrivacyCenter/components/PrivacyCenterSessionExpired.tsx +0 -97
  143. package/src/RedactoPrivacyCenter/components/ReceiptsList.tsx +0 -454
  144. package/src/RedactoPrivacyCenter/components/RequestSubmit.tsx +0 -270
  145. package/src/RedactoPrivacyCenter/components/SelectUser.tsx +0 -95
  146. package/src/RedactoPrivacyCenter/components/SelectUserGate.tsx +0 -187
  147. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/constants.tsx +0 -25
  148. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/types.ts +0 -21
  149. package/src/RedactoPrivacyCenter/components/caseHistoryColumns.tsx +0 -159
  150. package/src/RedactoPrivacyCenter/components/constant.ts +0 -52
  151. package/src/RedactoPrivacyCenter/components/groupHelpers.ts +0 -119
  152. package/src/RedactoPrivacyCenter/components/receiptColumns.tsx +0 -203
  153. package/src/RedactoPrivacyCenter/components/types.ts +0 -123
  154. package/src/RedactoPrivacyCenter/components/utils.tsx +0 -10
  155. package/src/RedactoPrivacyCenter/config/routes.ts +0 -9
  156. package/src/RedactoPrivacyCenter/config/table.ts +0 -25
  157. package/src/RedactoPrivacyCenter/context/AuthContext.tsx +0 -160
  158. package/src/RedactoPrivacyCenter/context/BaseUrlContext.tsx +0 -47
  159. package/src/RedactoPrivacyCenter/context/BrandingContext.tsx +0 -52
  160. package/src/RedactoPrivacyCenter/context/DataAvailabilityContext.session-expired.test.tsx +0 -92
  161. package/src/RedactoPrivacyCenter/context/DataAvailabilityContext.tsx +0 -181
  162. package/src/RedactoPrivacyCenter/context/LocaleContext.tsx +0 -11
  163. package/src/RedactoPrivacyCenter/context/NavigationContext.tsx +0 -96
  164. package/src/RedactoPrivacyCenter/context/OrganizationContext.tsx +0 -30
  165. package/src/RedactoPrivacyCenter/context/PageChromeContext.tsx +0 -80
  166. package/src/RedactoPrivacyCenter/context/ProfileSwitchContext.tsx +0 -15
  167. package/src/RedactoPrivacyCenter/context/types.ts +0 -19
  168. package/src/RedactoPrivacyCenter/lib/api.ts +0 -759
  169. package/src/RedactoPrivacyCenter/lib/consent-manager/utils.tsx +0 -67
  170. package/src/RedactoPrivacyCenter/lib/constants.ts +0 -276
  171. package/src/RedactoPrivacyCenter/lib/i18n.ts +0 -81
  172. package/src/RedactoPrivacyCenter/lib/locales.ts +0 -102
  173. package/src/RedactoPrivacyCenter/lib/queries.ts +0 -141
  174. package/src/RedactoPrivacyCenter/lib/types.ts +0 -876
  175. package/src/RedactoPrivacyCenter/lib/useCurrentLanguage.ts +0 -37
  176. package/src/RedactoPrivacyCenter/lib/utils.refresh-circuit.test.ts +0 -127
  177. package/src/RedactoPrivacyCenter/lib/utils.refresh-storage.test.ts +0 -85
  178. package/src/RedactoPrivacyCenter/lib/utils.session-expired.test.ts +0 -109
  179. package/src/RedactoPrivacyCenter/lib/utils.ts +0 -783
  180. package/src/RedactoPrivacyCenter/locales/as/translation.json +0 -342
  181. package/src/RedactoPrivacyCenter/locales/bn/translation.json +0 -342
  182. package/src/RedactoPrivacyCenter/locales/brx/translation.json +0 -342
  183. package/src/RedactoPrivacyCenter/locales/doi/translation.json +0 -342
  184. package/src/RedactoPrivacyCenter/locales/en/translation.json +0 -357
  185. package/src/RedactoPrivacyCenter/locales/gom/translation.json +0 -342
  186. package/src/RedactoPrivacyCenter/locales/gu/translation.json +0 -342
  187. package/src/RedactoPrivacyCenter/locales/hi/translation.json +0 -345
  188. package/src/RedactoPrivacyCenter/locales/kn/translation.json +0 -342
  189. package/src/RedactoPrivacyCenter/locales/ks/translation.json +0 -342
  190. package/src/RedactoPrivacyCenter/locales/mai/translation.json +0 -342
  191. package/src/RedactoPrivacyCenter/locales/ml/translation.json +0 -342
  192. package/src/RedactoPrivacyCenter/locales/mni-Mtei/translation.json +0 -342
  193. package/src/RedactoPrivacyCenter/locales/mr/translation.json +0 -342
  194. package/src/RedactoPrivacyCenter/locales/ne/translation.json +0 -342
  195. package/src/RedactoPrivacyCenter/locales/or/translation.json +0 -342
  196. package/src/RedactoPrivacyCenter/locales/pa/translation.json +0 -342
  197. package/src/RedactoPrivacyCenter/locales/sa/translation.json +0 -342
  198. package/src/RedactoPrivacyCenter/locales/sat/translation.json +0 -342
  199. package/src/RedactoPrivacyCenter/locales/sd/translation.json +0 -342
  200. package/src/RedactoPrivacyCenter/locales/ta/translation.json +0 -342
  201. package/src/RedactoPrivacyCenter/locales/te/translation.json +0 -342
  202. package/src/RedactoPrivacyCenter/locales/ur/translation.json +0 -342
  203. package/src/RedactoPrivacyCenter/styles/applyBrandingTheme.ts +0 -66
  204. package/src/RedactoPrivacyCenter/styles/base.css +0 -117
  205. package/src/RedactoPrivacyCenter/styles/injectStyles.ts +0 -3108
  206. package/src/RedactoPrivacyCenter/styles/pcStyles.ts +0 -831
  207. package/src/RedactoPrivacyCenter/styles/responsive.ts +0 -138
  208. package/src/RedactoPrivacyCenter/styles/variables.css +0 -96
  209. package/src/RedactoPrivacyCenter/table/AdvancedTable.tsx +0 -387
  210. package/src/RedactoPrivacyCenter/table/AdvancedTablePagination.tsx +0 -468
  211. package/src/RedactoPrivacyCenter/table/index.ts +0 -2
  212. package/src/RedactoPrivacyCenter/types/images.d.ts +0 -29
  213. package/src/RedactoPrivacyCenter/ui/Footer.tsx +0 -69
  214. package/src/RedactoPrivacyCenter/ui/FormBlockWrapper.tsx +0 -54
  215. package/src/RedactoPrivacyCenter/ui/PCAccordion.tsx +0 -93
  216. package/src/RedactoPrivacyCenter/ui/PCActionIcon.tsx +0 -47
  217. package/src/RedactoPrivacyCenter/ui/PCAvatar.test.tsx +0 -83
  218. package/src/RedactoPrivacyCenter/ui/PCAvatar.tsx +0 -93
  219. package/src/RedactoPrivacyCenter/ui/PCBadge.tsx +0 -48
  220. package/src/RedactoPrivacyCenter/ui/PCButton.tsx +0 -77
  221. package/src/RedactoPrivacyCenter/ui/PCCheckbox.tsx +0 -55
  222. package/src/RedactoPrivacyCenter/ui/PCCollapse.tsx +0 -107
  223. package/src/RedactoPrivacyCenter/ui/PCEmpty.tsx +0 -35
  224. package/src/RedactoPrivacyCenter/ui/PCInput.tsx +0 -132
  225. package/src/RedactoPrivacyCenter/ui/PCLoader.tsx +0 -31
  226. package/src/RedactoPrivacyCenter/ui/PCModal.tsx +0 -106
  227. package/src/RedactoPrivacyCenter/ui/PCPopover.tsx +0 -206
  228. package/src/RedactoPrivacyCenter/ui/PCSelect.tsx +0 -433
  229. package/src/RedactoPrivacyCenter/ui/PCTable.tsx +0 -149
  230. package/src/RedactoPrivacyCenter/ui/PCText.tsx +0 -41
  231. package/src/RedactoPrivacyCenter/ui/PCTextarea.tsx +0 -43
  232. package/src/RedactoPrivacyCenter/ui/PCTitle.tsx +0 -37
  233. package/src/RedactoPrivacyCenter/ui/PCTooltip.tsx +0 -121
  234. package/src/RedactoPrivacyCenter/ui/SimpleBar.tsx +0 -25
  235. package/src/RedactoPrivacyCenter/ui/StatusBadge.tsx +0 -53
  236. package/src/RedactoPrivacyCenter/ui/index.ts +0 -18
  237. package/src/RedactoPrivacyCenter/utils/cn.ts +0 -28
  238. package/src/RedactoPrivacyCenter/utils/date.ts +0 -48
  239. package/src/RedactoPrivacyCenter/utils/status.ts +0 -111
  240. package/src/RedactoPrivacyCenter/utils/toast.ts +0 -130
  241. package/src/index.ts +0 -19
  242. package/src/privacy-center.ts +0 -113
  243. package/src/shared/api-errors.ts +0 -180
  244. package/src/shared/notice-consent-status.ts +0 -190
  245. package/src/types/css.d.ts +0 -4
  246. package/src/types/images.d.ts +0 -19
  247. package/src/types/react-jsx.d.ts +0 -26
  248. package/tests/DataAvailabilityContext.test.tsx +0 -123
  249. package/tests/Form.test.tsx +0 -1287
  250. package/tests/ManageConsentErrorMapping.test.tsx +0 -85
  251. package/tests/ProductSelectionSection.test.tsx +0 -328
  252. package/tests/RequestSubmit.test.tsx +0 -135
  253. package/tests/RevocablePurpose.test.tsx +0 -146
  254. package/tests/RevokeWarningMessage.test.tsx +0 -151
  255. package/tests/consentClient.test.ts +0 -217
  256. package/tests/manageConsentServer.test.ts +0 -38
  257. package/tests/mocks.ts +0 -257
  258. package/tests/resolveContactFromJwt.test.ts +0 -44
  259. package/tests/setup.ts +0 -16
  260. package/tsconfig.json +0 -16
  261. package/tsup.config.ts +0 -31
  262. package/vitest.config.ts +0 -16
@@ -1,85 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
-
3
- import { manageConsentServer, ConsentApiError } from '../src/RedactoPrivacyCenter/api/actions';
4
- import { ApiError } from '../src/RedactoPrivacyCenter/api/fetcher';
5
- import type { createConsentClient } from '../src/RedactoPrivacyCenter/api/client';
6
-
7
- type ConsentClient = ReturnType<typeof createConsentClient>;
8
-
9
- // Build a mock consent client whose `manageConsent` rejects with the supplied
10
- // error. Only `manageConsent` is exercised by `manageConsentServer`, so the
11
- // rest of the surface is a cast-through stub.
12
- const makeClient = (error: unknown): ConsentClient =>
13
- ({
14
- manageConsent: () => Promise.reject(error),
15
- } as unknown as ConsentClient);
16
-
17
- const baseParams = {
18
- organizationId: 'org-1',
19
- workspaceId: 'ws-1',
20
- purposeId: 'purpose-1',
21
- contact: 'user@example.com',
22
- action: 'revoke' as const,
23
- authToken: 'token-abc',
24
- };
25
-
26
- describe('manageConsentServer error mapping — purpose_not_revocable', () => {
27
- // The consent-server 400 is rendered by a Ninja wrapper as
28
- // { code: 400, status, detail: { message, code: 'purpose_not_revocable' } }.
29
- // `privacyCenterApiFetch` preserves the parsed `detail` on the ApiError and
30
- // sets ApiError.code only from the TOP-LEVEL string code — which here is the
31
- // integer 400 (dropped, so ApiError.code is undefined). The app-level
32
- // discriminator therefore lives in `error.detail.code`.
33
- const realBackody400 = () =>
34
- new ApiError(
35
- `[400] ${JSON.stringify(
36
- { message: 'This purpose cannot be revoked.', code: 'purpose_not_revocable' },
37
- null,
38
- 2,
39
- )}`,
40
- { status: 400, code: undefined, detail: { message: 'This purpose cannot be revoked.', code: 'purpose_not_revocable' } },
41
- );
42
-
43
- it('maps the real backend 400 body (code nested in detail) to PURPOSE_NOT_REVOCABLE', async () => {
44
- await expect(
45
- manageConsentServer({ ...baseParams, consentClient: makeClient(realBackody400()) }),
46
- ).rejects.toMatchObject({
47
- name: 'ConsentApiError',
48
- code: 'PURPOSE_NOT_REVOCABLE',
49
- });
50
- });
51
-
52
- it('throws a ConsentApiError instance carrying the original message', async () => {
53
- const error = realBackody400();
54
- try {
55
- await manageConsentServer({ ...baseParams, consentClient: makeClient(error) });
56
- throw new Error('expected manageConsentServer to reject');
57
- } catch (caught) {
58
- expect(caught).toBeInstanceOf(ConsentApiError);
59
- expect((caught as ConsentApiError).code).toBe('PURPOSE_NOT_REVOCABLE');
60
- expect((caught as ConsentApiError).message).toContain('purpose_not_revocable');
61
- }
62
- });
63
-
64
- it('still maps when the string code is surfaced at the top level (error.code)', async () => {
65
- // Defensive: if a backend/wrapper ever exposes the string code at the top
66
- // level, the original `error?.code === 'purpose_not_revocable'` arm must
67
- // continue to fire.
68
- const error = new ApiError('[400] purpose is not revocable', {
69
- status: 400,
70
- code: 'purpose_not_revocable',
71
- });
72
- await expect(
73
- manageConsentServer({ ...baseParams, consentClient: makeClient(error) }),
74
- ).rejects.toMatchObject({ code: 'PURPOSE_NOT_REVOCABLE' });
75
- });
76
-
77
- it('does not misclassify an unrelated error as PURPOSE_NOT_REVOCABLE', async () => {
78
- const error = new ApiError('[500] internal server error', { status: 500 });
79
- // Unrelated non-auth errors are swallowed to null by handleServerActionError,
80
- // so the call resolves rather than throwing a ConsentApiError.
81
- await expect(
82
- manageConsentServer({ ...baseParams, consentClient: makeClient(error) }),
83
- ).resolves.toBeNull();
84
- });
85
- });
@@ -1,328 +0,0 @@
1
- import { render, screen, fireEvent } from '@testing-library/react';
2
- import { describe, it, expect, vi } from 'vitest';
3
-
4
- vi.mock('react-i18next', () => {
5
- const en: Record<string, string> = {
6
- activePurposes_one: '{{count}} purpose',
7
- activePurposes_other: '{{count}} purposes',
8
- noProductsAvailable: 'No products available',
9
- noProductsAvailableDescription: 'You have no consents to manage yet.',
10
- nominationChipSuffix: '· {{nominator}}',
11
- selectProductsForRequest:
12
- 'Select a product for your {{requestType}} request',
13
- selectProductsForRequestGeneric: 'Select a product to continue',
14
- };
15
- const interpolate = (template: string, vars?: Record<string, unknown>) =>
16
- template.replace(/\{\{(\w+)\}\}/g, (_match, key: string) => {
17
- const value = vars?.[key];
18
- return value === undefined || value === null ? '' : String(value);
19
- });
20
- const t = (key: string, opts?: Record<string, unknown>) => {
21
- const count = typeof opts?.count === 'number' ? opts.count : undefined;
22
- const pluralKey =
23
- count !== undefined
24
- ? count === 1
25
- ? `${key}_one`
26
- : `${key}_other`
27
- : key;
28
- const template = en[pluralKey] ?? en[key] ?? key;
29
- return interpolate(template, opts);
30
- };
31
- return {
32
- useTranslation: () => ({ t, i18n: { language: 'en' } }),
33
- };
34
- });
35
-
36
- import {
37
- ProductSelectionSection,
38
- productGroupKey,
39
- } from '../src/RedactoPrivacyCenter/components/DataRequest/ProductSelectionSection';
40
- import type {
41
- FormProductGroup,
42
- NominatorInfo,
43
- } from '../src/RedactoPrivacyCenter/lib/types';
44
-
45
- const makeGroup = (
46
- overrides: Partial<FormProductGroup> = {},
47
- ): FormProductGroup => ({
48
- product_uuid: 'prod-1',
49
- product_name: 'Acme Loyalty',
50
- product_description: null,
51
- nominator: null,
52
- purposes: [],
53
- ...overrides,
54
- });
55
-
56
- const makeNominator = (overrides: Partial<NominatorInfo> = {}): NominatorInfo => ({
57
- org_user_id: 'org-user-1',
58
- name: 'Jane Doe',
59
- uuid: 'nom-uuid-1',
60
- email: 'jane@example.com',
61
- ...overrides,
62
- });
63
-
64
- describe('ProductSelectionSection', () => {
65
- it('renders direct groups as pill chips in a wrapping row', () => {
66
- const direct = makeGroup({
67
- product_uuid: 'prod-direct',
68
- product_name: 'Acme Loyalty',
69
- purposes: [
70
- {
71
- uuid: 'p1',
72
- data_elements: [{ uuid: 'de1', selected: true }],
73
- } as never,
74
- {
75
- uuid: 'p2',
76
- data_elements: [{ uuid: 'de2', selected: true }],
77
- } as never,
78
- ],
79
- });
80
- const second = makeGroup({
81
- product_uuid: 'prod-direct-2',
82
- product_name: 'Acme Rewards',
83
- });
84
-
85
- render(
86
- <ProductSelectionSection
87
- directGroups={[direct, second]}
88
- nominatedGroups={[]}
89
- selectedProductKey={null}
90
- onSelect={vi.fn()}
91
- />,
92
- );
93
-
94
- expect(screen.getByText('Acme Loyalty')).toBeInTheDocument();
95
- expect(screen.getByText('Acme Rewards')).toBeInTheDocument();
96
- expect(screen.getByTestId('product-selection-chips')).toHaveClass(
97
- 'pc-product-select-chips',
98
- );
99
- expect(screen.getByText('2 purposes')).toBeInTheDocument();
100
- });
101
-
102
- it('chip count reflects total raisable purposes regardless of status', () => {
103
- // Per TECH-4089, withdrawn purposes still appear in the picker (revoking
104
- // consent is not deletion — DP must still be able to raise requests for
105
- // them). The chip label is a plain count of raisable purposes — no
106
- // active/withdrawn distinction is surfaced in the label.
107
- const direct = makeGroup({
108
- product_uuid: 'prod-mixed',
109
- product_name: 'Acme Loyalty',
110
- purposes: [
111
- {
112
- uuid: 'p-active',
113
- data_elements: [{ uuid: 'de-a', selected: true }],
114
- status: 'ACTIVE',
115
- } as never,
116
- {
117
- uuid: 'p-withdrawn',
118
- data_elements: [{ uuid: 'de-w', selected: false }],
119
- status: 'WITHDRAW',
120
- } as never,
121
- ],
122
- });
123
-
124
- render(
125
- <ProductSelectionSection
126
- directGroups={[direct]}
127
- nominatedGroups={[]}
128
- selectedProductKey={null}
129
- onSelect={vi.fn()}
130
- />,
131
- );
132
-
133
- expect(screen.getByText('2 purposes')).toBeInTheDocument();
134
- expect(screen.queryByText(/active/)).not.toBeInTheDocument();
135
- });
136
-
137
- it('marks the selected chip with aria-selected and selected style class', () => {
138
- const direct = makeGroup({
139
- product_uuid: 'prod-direct',
140
- product_name: 'Acme Loyalty',
141
- });
142
-
143
- render(
144
- <ProductSelectionSection
145
- directGroups={[direct]}
146
- nominatedGroups={[]}
147
- selectedProductKey={productGroupKey(direct)}
148
- onSelect={vi.fn()}
149
- />,
150
- );
151
-
152
- const chip = screen.getByTestId('product-chip-prod-direct');
153
- expect(chip).toHaveClass('pc-product-select-chip');
154
- expect(chip).toHaveClass('pc-product-select-chip--selected');
155
- expect(chip).toHaveAttribute('aria-selected', 'true');
156
- });
157
-
158
- it('renders nominated groups with an inline nominator suffix on the chip', () => {
159
- const nominated = makeGroup({
160
- product_uuid: 'prod-nom',
161
- product_name: 'Partner Bank',
162
- nominator: makeNominator({ name: 'Alice Johnson' }),
163
- purposes: [
164
- {
165
- uuid: 'p1',
166
- data_elements: [{ uuid: 'de1', selected: true }],
167
- } as never,
168
- ],
169
- });
170
- const otherDirect = makeGroup({
171
- product_uuid: 'prod-direct',
172
- product_name: 'Acme Loyalty',
173
- });
174
-
175
- render(
176
- <ProductSelectionSection
177
- directGroups={[otherDirect]}
178
- nominatedGroups={[nominated]}
179
- selectedProductKey={null}
180
- onSelect={vi.fn()}
181
- />,
182
- );
183
-
184
- expect(screen.getByText('Partner Bank')).toBeInTheDocument();
185
- expect(screen.getByText('· Alice Johnson')).toBeInTheDocument();
186
- expect(screen.getByText('1 purpose')).toBeInTheDocument();
187
- });
188
-
189
- it('falls back to nominator email in the chip suffix when name is null', () => {
190
- const nominated = makeGroup({
191
- product_uuid: 'prod-nom',
192
- product_name: 'Partner Bank',
193
- nominator: makeNominator({ name: null, email: 'fallback@example.com' }),
194
- });
195
- const otherDirect = makeGroup({ product_uuid: 'prod-direct' });
196
-
197
- render(
198
- <ProductSelectionSection
199
- directGroups={[otherDirect]}
200
- nominatedGroups={[nominated]}
201
- selectedProductKey={null}
202
- onSelect={vi.fn()}
203
- />,
204
- );
205
-
206
- expect(screen.getByText('· fallback@example.com')).toBeInTheDocument();
207
- });
208
-
209
- it('invokes onSelect with the correct key when a chip is clicked', () => {
210
- const direct = makeGroup({
211
- product_uuid: 'prod-direct',
212
- product_name: 'Acme Loyalty',
213
- });
214
- const nominated = makeGroup({
215
- product_uuid: 'prod-nom',
216
- product_name: 'Partner Bank',
217
- nominator: makeNominator(),
218
- });
219
- const onSelect = vi.fn();
220
-
221
- render(
222
- <ProductSelectionSection
223
- directGroups={[direct]}
224
- nominatedGroups={[nominated]}
225
- selectedProductKey={null}
226
- onSelect={onSelect}
227
- />,
228
- );
229
-
230
- fireEvent.click(screen.getByTestId('product-chip-prod-direct'));
231
- fireEvent.click(screen.getByTestId(`product-chip-${productGroupKey(nominated)}`));
232
-
233
- expect(onSelect).toHaveBeenCalledWith(
234
- productGroupKey(direct),
235
- expect.objectContaining({ product_uuid: 'prod-direct' }),
236
- );
237
- expect(onSelect).toHaveBeenCalledWith(
238
- productGroupKey(nominated),
239
- expect.objectContaining({ product_uuid: 'prod-nom' }),
240
- );
241
- expect(onSelect).toHaveBeenCalledTimes(2);
242
- });
243
-
244
- it('clears selection when the already-selected chip is clicked again', () => {
245
- const direct = makeGroup({
246
- product_uuid: 'prod-direct',
247
- product_name: 'Acme Loyalty',
248
- });
249
- const second = makeGroup({
250
- product_uuid: 'prod-direct-2',
251
- product_name: 'Acme Rewards',
252
- });
253
- const onSelect = vi.fn();
254
-
255
- render(
256
- <ProductSelectionSection
257
- directGroups={[direct, second]}
258
- nominatedGroups={[]}
259
- selectedProductKey={productGroupKey(direct)}
260
- onSelect={onSelect}
261
- />,
262
- );
263
-
264
- fireEvent.click(screen.getByTestId('product-chip-prod-direct'));
265
-
266
- expect(onSelect).toHaveBeenCalledWith(null, null);
267
- });
268
-
269
- it('auto-selects and locks the only available group', () => {
270
- const only = makeGroup({
271
- product_uuid: 'prod-only',
272
- product_name: 'Sole Product',
273
- });
274
- const onSelect = vi.fn();
275
-
276
- render(
277
- <ProductSelectionSection
278
- directGroups={[only]}
279
- nominatedGroups={[]}
280
- selectedProductKey={null}
281
- onSelect={onSelect}
282
- />,
283
- );
284
-
285
- expect(onSelect).toHaveBeenCalledTimes(1);
286
- expect(onSelect).toHaveBeenCalledWith(
287
- productGroupKey(only),
288
- expect.objectContaining({ product_uuid: 'prod-only' }),
289
- );
290
-
291
- const chip = screen.getByTestId('product-chip-prod-only') as HTMLButtonElement;
292
- expect(chip).toHaveAttribute('aria-selected', 'true');
293
- expect(chip.disabled).toBe(true);
294
- });
295
-
296
- it('renders an empty state when both arrays are empty', () => {
297
- render(
298
- <ProductSelectionSection
299
- directGroups={[]}
300
- nominatedGroups={[]}
301
- selectedProductKey={null}
302
- onSelect={vi.fn()}
303
- />,
304
- );
305
-
306
- expect(screen.getByText('No products available')).toBeInTheDocument();
307
- expect(
308
- screen.getByText('You have no consents to manage yet.'),
309
- ).toBeInTheDocument();
310
- expect(screen.queryByRole('option')).not.toBeInTheDocument();
311
- });
312
-
313
- it('distinguishes direct vs nominated chips on the same product via key helper', () => {
314
- const direct = makeGroup({
315
- product_uuid: 'prod-shared',
316
- product_name: 'Shared Product',
317
- });
318
- const nominated = makeGroup({
319
- product_uuid: 'prod-shared',
320
- product_name: 'Shared Product',
321
- nominator: makeNominator({ uuid: 'nom-uuid-x' }),
322
- });
323
-
324
- expect(productGroupKey(direct)).toBe('prod-shared');
325
- expect(productGroupKey(nominated)).toBe('prod-shared:nom-uuid-x');
326
- expect(productGroupKey(direct)).not.toBe(productGroupKey(nominated));
327
- });
328
- });
@@ -1,135 +0,0 @@
1
- import { render, screen, fireEvent } from '@testing-library/react';
2
- import { describe, it, expect, vi } from 'vitest';
3
-
4
- // react-i18next is an optional peer dep that is not installed in this
5
- // package's node_modules. Stub it with a minimal `useTranslation` that
6
- // resolves the en/translation.json keys we use here, with interpolation
7
- // support.
8
- vi.mock('react-i18next', () => {
9
- const en: Record<string, string> = {
10
- requestSubmittedSuccessfully: 'Request Submitted Successfully',
11
- thankYouForSubmittingYourRequest: 'Thank you for submitting your request',
12
- caseID: 'Case ID',
13
- saveThisID: 'Please save this ID for future reference',
14
- confirmationSentTo: 'Confirmation sent to',
15
- processRequest: 'We will process your request as per laws',
16
- backToHome: 'Back to homepage',
17
- yourDataIsSafeWithUs: 'Your data is safe with us',
18
- erasureConsentRevokeNotice:
19
- 'Your consent for {{purposes}} will be revoked once fulfilled.',
20
- };
21
- const interpolate = (template: string, vars?: Record<string, unknown>) =>
22
- template.replace(/\{\{(\w+)\}\}/g, (_match, key: string) => {
23
- const value = vars?.[key];
24
- return value === undefined || value === null ? '' : String(value);
25
- });
26
- const t = (key: string, opts?: Record<string, unknown>) => {
27
- const template = en[key] ?? key;
28
- return interpolate(template, opts);
29
- };
30
- return {
31
- useTranslation: () => ({ t, i18n: { language: 'en' } }),
32
- };
33
- });
34
-
35
- // `lucide-react` is also a peer dep — stub the icon used by RequestSubmit.
36
- vi.mock('lucide-react', () => ({
37
- Mail: () => null,
38
- }));
39
-
40
- import RequestSubmit from '../src/RedactoPrivacyCenter/components/RequestSubmit';
41
- import type { RequestSubmitResult } from '../src/RedactoPrivacyCenter/components/types';
42
-
43
- const productScopedResult: RequestSubmitResult = {
44
- case_id: 'CASE-PROD',
45
- uuid: 'uuid-prod',
46
- product_name: 'Acme Loyalty',
47
- product_uuid: 'prod-1',
48
- };
49
-
50
- const orgLevelResult: RequestSubmitResult = {
51
- case_id: 'CASE-ORG',
52
- uuid: 'uuid-org',
53
- product_name: '',
54
- product_uuid: '',
55
- };
56
-
57
- describe('RequestSubmit', () => {
58
- it('renders the case id and product label for a product-scoped result', () => {
59
- render(
60
- <RequestSubmit
61
- result={productScopedResult}
62
- contact="alice@example.com"
63
- onCreateRequest={vi.fn()}
64
- />,
65
- );
66
-
67
- expect(
68
- screen.getByText('Request Submitted Successfully'),
69
- ).toBeInTheDocument();
70
- expect(screen.getByText(/Case ID:\s*CASE-PROD/)).toBeInTheDocument();
71
- expect(screen.getByText('Acme Loyalty')).toBeInTheDocument();
72
- });
73
-
74
- it('omits the product label when the result has no product anchor', () => {
75
- render(
76
- <RequestSubmit
77
- result={orgLevelResult}
78
- contact="alice@example.com"
79
- onCreateRequest={vi.fn()}
80
- />,
81
- );
82
-
83
- expect(screen.getByText(/Case ID:\s*CASE-ORG/)).toBeInTheDocument();
84
- // Empty product_name means the product line is not rendered.
85
- expect(screen.queryByText('Acme Loyalty')).not.toBeInTheDocument();
86
- });
87
-
88
- it('shows the contact in the confirmation line', () => {
89
- render(
90
- <RequestSubmit
91
- result={productScopedResult}
92
- contact="alice@example.com"
93
- onCreateRequest={vi.fn()}
94
- />,
95
- );
96
-
97
- expect(
98
- screen.getByText(/Confirmation sent to:\s*alice@example\.com/),
99
- ).toBeInTheDocument();
100
- });
101
-
102
- it('invokes onCreateRequest when "Back to homepage" is clicked', () => {
103
- const onCreateRequest = vi.fn();
104
- render(
105
- <RequestSubmit
106
- result={productScopedResult}
107
- contact="alice@example.com"
108
- onCreateRequest={onCreateRequest}
109
- />,
110
- );
111
-
112
- // Two rendered "Back to homepage" buttons exist for the responsive
113
- // mobile/desktop variants. Clicking either should fire the handler.
114
- const buttons = screen.getAllByRole('button', { name: 'Back to homepage' });
115
- expect(buttons.length).toBeGreaterThanOrEqual(1);
116
- fireEvent.click(buttons[0]);
117
- expect(onCreateRequest).toHaveBeenCalledTimes(1);
118
- });
119
-
120
- it('renders the erasure-revoke notice when revokeConsentOnFulfilment is true', () => {
121
- render(
122
- <RequestSubmit
123
- result={productScopedResult}
124
- contact="alice@example.com"
125
- onCreateRequest={vi.fn()}
126
- revokeConsentOnFulfilment
127
- revokedPurposeNames="Marketing, Analytics"
128
- />,
129
- );
130
-
131
- expect(
132
- screen.getByText(/Marketing, Analytics/),
133
- ).toBeInTheDocument();
134
- });
135
- });
@@ -1,146 +0,0 @@
1
- import { render, screen, fireEvent } from '@testing-library/react';
2
- import { describe, it, expect, vi } from 'vitest';
3
-
4
- // react-i18next is an optional peer dep that is not installed in this
5
- // package's node_modules. Stub it with a minimal `useTranslation` that
6
- // resolves the keys used by the components under test, honouring the inline
7
- // English fallback passed as the 2nd argument (mirrors the real i18next
8
- // behaviour when a key is missing).
9
- vi.mock('react-i18next', () => {
10
- const en: Record<string, string> = {
11
- modifyButton: 'Modify',
12
- modifyConsent: 'Modify Consent',
13
- closeModal: 'Close',
14
- cancel: 'Cancel',
15
- revokeConsent: 'Revoke Consent',
16
- revokeConsentWarning: 'If you revoke this consent, you may lose access.',
17
- aboutToModifyConsent: 'You are about to modify this consent.',
18
- noDataElementsAvailable: 'No data elements available',
19
- purposeNotRevocable: "This purpose can't be withdrawn directly.",
20
- noActionsAvailable: 'No actions available for this record.',
21
- };
22
- const interpolate = (template: string, vars?: Record<string, unknown>) =>
23
- template.replace(/\{\{(\w+)\}\}/g, (_match, key: string) => {
24
- const value = vars?.[key];
25
- return value === undefined || value === null ? '' : String(value);
26
- });
27
- // Supports the `t(key, fallbackString)` and `t(key, opts)` call shapes used
28
- // across the components. When the key is unknown, fall back to the inline
29
- // English string if one was supplied, otherwise echo the key.
30
- const t = (key: string, second?: unknown, opts?: Record<string, unknown>) => {
31
- const fallback = typeof second === 'string' ? second : undefined;
32
- const interpolationOpts =
33
- typeof second === 'object' && second !== null
34
- ? (second as Record<string, unknown>)
35
- : opts;
36
- const template = en[key] ?? fallback ?? key;
37
- return interpolate(template, interpolationOpts);
38
- };
39
- return {
40
- useTranslation: () => ({ t, i18n: { language: 'en' } }),
41
- };
42
- });
43
-
44
- // Icon peer deps — stub the icons used by the modal.
45
- vi.mock('lucide-react', () => ({
46
- ChevronDownIcon: () => null,
47
- X: () => null,
48
- Smartphone: () => null,
49
- LaptopMinimal: () => null,
50
- }));
51
-
52
- vi.mock('react-icons/pi', () => ({
53
- PiWarningCircleBold: () => null,
54
- }));
55
-
56
- import { renderModifyButton } from '../src/RedactoPrivacyCenter/components/ConsentManagerCells';
57
- import { ModifyConsentModal } from '../src/RedactoPrivacyCenter/components/ModifyConsentModal';
58
- import { NavigationProvider } from '../src/RedactoPrivacyCenter/context/NavigationContext';
59
- import { ConsentStatusEnum } from '../src/RedactoPrivacyCenter/lib/types';
60
- import type { ConsentModalRecord } from '../src/RedactoPrivacyCenter/lib/types';
61
-
62
- const makeRecord = (
63
- overrides: Partial<ConsentModalRecord> = {},
64
- ): ConsentModalRecord => ({
65
- purpose_uuid: 'purpose-1',
66
- name: 'Marketing analytics',
67
- description: 'Improve product recommendations',
68
- status: ConsentStatusEnum.ACTIVE,
69
- given_date: '2026-01-01T00:00:00Z',
70
- valid_till: null,
71
- method: 'WEB',
72
- data_elements: [],
73
- link_reason: null,
74
- product_uuid: 'prod-1',
75
- product_name: 'Acme Loyalty',
76
- product_description: null,
77
- nominator: null,
78
- ...overrides,
79
- });
80
-
81
- // A TFunction-compatible stub for the pure render helper (which receives `t`
82
- // directly rather than via the react-i18next mock). Resolves the handful of
83
- // keys it uses and honours the inline English fallback like real i18next.
84
- const tStubKeys: Record<string, string> = {
85
- modifyButton: 'Modify',
86
- };
87
- const tStub = ((key: string, second?: unknown) =>
88
- tStubKeys[key] ??
89
- (typeof second === 'string' ? second : key)) as unknown as Parameters<
90
- typeof renderModifyButton
91
- >[2];
92
-
93
- describe('non-revocable purpose', () => {
94
- it('renders the modify/withdraw control disabled for a non-revocable ACTIVE purpose', () => {
95
- const record = makeRecord({ revocable: false });
96
- render(<>{renderModifyButton(record, vi.fn(), tStub)}</>);
97
-
98
- const button = screen.getByRole('button', { name: 'Modify' });
99
- expect(button).toBeDisabled();
100
- expect(button).toHaveAttribute(
101
- 'title',
102
- "This purpose can't be withdrawn directly.",
103
- );
104
- });
105
-
106
- it('does not invoke onModify when the disabled non-revocable control is clicked', () => {
107
- const onModify = vi.fn();
108
- const record = makeRecord({ revocable: false });
109
- render(<>{renderModifyButton(record, onModify, tStub)}</>);
110
-
111
- fireEvent.click(screen.getByRole('button', { name: 'Modify' }));
112
- expect(onModify).not.toHaveBeenCalled();
113
- });
114
- });
115
-
116
- describe('revocable purpose (regression)', () => {
117
- it('renders the modify control enabled when revocable is absent (older backends)', () => {
118
- const record = makeRecord(); // no `revocable` field
119
- const onModify = vi.fn();
120
- render(<>{renderModifyButton(record, onModify, tStub)}</>);
121
-
122
- const button = screen.getByRole('button', { name: 'Modify' });
123
- expect(button).not.toBeDisabled();
124
- fireEvent.click(button);
125
- expect(onModify).toHaveBeenCalledTimes(1);
126
- });
127
-
128
- it('offers the revoke action in the modal for a revocable ACTIVE record', () => {
129
- const record = makeRecord({ revocable: true });
130
- render(
131
- <NavigationProvider>
132
- <ModifyConsentModal
133
- isOpen
134
- onClose={vi.fn()}
135
- selectedRecord={record}
136
- isLoading={false}
137
- onManageConsent={vi.fn()}
138
- />
139
- </NavigationProvider>,
140
- );
141
-
142
- expect(
143
- screen.getByRole('button', { name: 'Revoke Consent' }),
144
- ).toBeInTheDocument();
145
- });
146
- });