@redacto.io/consent-sdk-react 10.0.0-beta.9 → 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.
- package/NOTICE.md +17 -0
- package/README.md +141 -3
- package/dist/chunk-FEGTW73Q.mjs +182 -0
- package/dist/fonts.d.mts +35 -0
- package/dist/fonts.d.ts +35 -0
- package/dist/fonts.js +183 -0
- package/dist/fonts.mjs +10 -0
- package/dist/index.d.mts +12 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.js +724 -365
- package/dist/index.mjs +346 -201
- package/dist/montserrat.generated-NBTECYRW.mjs +22 -0
- package/dist/privacy-center.d.mts +0 -1
- package/dist/privacy-center.d.ts +0 -1
- package/dist/privacy-center.js +2 -66
- package/dist/privacy-center.mjs +2 -66
- package/dist/types-6Aj7faxL.d.mts +57 -0
- package/dist/types-6Aj7faxL.d.ts +57 -0
- package/package.json +10 -1
- package/.turbo/turbo-build.log +0 -22
- package/.turbo/turbo-test.log +0 -203
- package/.turbo/turbo-verify-pack.log +0 -6
- package/CHANGELOG.md +0 -353
- package/src/RedactoNoticeAssisted/RedactoNoticeAssisted.test.tsx +0 -934
- package/src/RedactoNoticeAssisted/RedactoNoticeAssisted.tsx +0 -2160
- package/src/RedactoNoticeAssisted/api/index.ts +0 -275
- package/src/RedactoNoticeAssisted/api/types.ts +0 -182
- package/src/RedactoNoticeAssisted/assets/redacto-logo.png +0 -0
- package/src/RedactoNoticeAssisted/constants.ts +0 -51
- package/src/RedactoNoticeAssisted/i18n.ts +0 -1408
- package/src/RedactoNoticeAssisted/index.ts +0 -1
- package/src/RedactoNoticeAssisted/injectStyles.ts +0 -251
- package/src/RedactoNoticeAssisted/styles.ts +0 -730
- package/src/RedactoNoticeAssisted/types.ts +0 -26
- package/src/RedactoNoticeAssisted/useMediaQuery.ts +0 -36
- package/src/RedactoNoticeConsent/RedactoNoticeConsent.test.tsx +0 -1152
- package/src/RedactoNoticeConsent/RedactoNoticeConsent.tsx +0 -4308
- package/src/RedactoNoticeConsent/api/index.ts +0 -542
- package/src/RedactoNoticeConsent/api/sandbox.ts +0 -150
- package/src/RedactoNoticeConsent/api/types.ts +0 -515
- package/src/RedactoNoticeConsent/assets/redacto-logo.png +0 -0
- package/src/RedactoNoticeConsent/components/DigiLockerCallback.tsx +0 -602
- package/src/RedactoNoticeConsent/components/index.ts +0 -7
- package/src/RedactoNoticeConsent/constants.ts +0 -20
- package/src/RedactoNoticeConsent/index.ts +0 -1
- package/src/RedactoNoticeConsent/injectStyles.ts +0 -151
- package/src/RedactoNoticeConsent/language-codes.ts +0 -95
- package/src/RedactoNoticeConsent/services/storage.ts +0 -122
- package/src/RedactoNoticeConsent/styles.ts +0 -409
- package/src/RedactoNoticeConsent/types.ts +0 -80
- package/src/RedactoNoticeConsent/useMediaQuery.ts +0 -36
- package/src/RedactoNoticeConsentInline/RedactoNoticeConsentInline.test.tsx +0 -369
- package/src/RedactoNoticeConsentInline/RedactoNoticeConsentInline.tsx +0 -687
- package/src/RedactoNoticeConsentInline/api/index.ts +0 -106
- package/src/RedactoNoticeConsentInline/api/types.ts +0 -183
- package/src/RedactoNoticeConsentInline/assets/redacto-logo.png +0 -0
- package/src/RedactoNoticeConsentInline/index.ts +0 -1
- package/src/RedactoNoticeConsentInline/injectStyles.ts +0 -40
- package/src/RedactoNoticeConsentInline/styles.ts +0 -397
- package/src/RedactoNoticeConsentInline/types.ts +0 -51
- package/src/RedactoNoticeConsentInline/useMediaQuery.ts +0 -36
- package/src/RedactoPrivacyCenter/NotificationsPage.tsx +0 -182
- package/src/RedactoPrivacyCenter/PrivacyCenterActivityPage.tsx +0 -19
- package/src/RedactoPrivacyCenter/PrivacyCenterCaseDetailsPage.tsx +0 -156
- package/src/RedactoPrivacyCenter/PrivacyCenterConsentManagerPage.tsx +0 -17
- package/src/RedactoPrivacyCenter/PrivacyCenterFormPage.tsx +0 -79
- package/src/RedactoPrivacyCenter/PrivacyCenterHomePage.tsx +0 -36
- package/src/RedactoPrivacyCenter/PrivacyCenterLayout.tsx +0 -177
- package/src/RedactoPrivacyCenter/PrivacyCenterMyReceiptPage.tsx +0 -26
- package/src/RedactoPrivacyCenter/RedactoPrivacyCenter.tsx +0 -93
- package/src/RedactoPrivacyCenter/api/actions.ts +0 -695
- package/src/RedactoPrivacyCenter/api/client.ts +0 -1003
- package/src/RedactoPrivacyCenter/api/config.ts +0 -33
- package/src/RedactoPrivacyCenter/api/fetcher.ts +0 -275
- package/src/RedactoPrivacyCenter/api/orgBranding.ts +0 -16
- package/src/RedactoPrivacyCenter/api/upload.ts +0 -93
- package/src/RedactoPrivacyCenter/assets/character.svg +0 -31
- package/src/RedactoPrivacyCenter/assets/fileAttach.svg +0 -9
- package/src/RedactoPrivacyCenter/assets/greenTick.svg +0 -4
- package/src/RedactoPrivacyCenter/assets/mail.svg +0 -3
- package/src/RedactoPrivacyCenter/assets/poweredByRedacto.svg +0 -9
- package/src/RedactoPrivacyCenter/assets/sentImg.svg +0 -9
- package/src/RedactoPrivacyCenter/assets/uploadFileIcon.svg +0 -9
- package/src/RedactoPrivacyCenter/components/ActivitiesList.tsx +0 -252
- package/src/RedactoPrivacyCenter/components/ActivityTimeline.tsx +0 -239
- package/src/RedactoPrivacyCenter/components/AppShell/AppShell.tsx +0 -268
- package/src/RedactoPrivacyCenter/components/AppShell/MobileNavDrawer.tsx +0 -218
- package/src/RedactoPrivacyCenter/components/AppShell/NavItem.tsx +0 -205
- package/src/RedactoPrivacyCenter/components/AppShell/PageHeader.tsx +0 -107
- package/src/RedactoPrivacyCenter/components/AppShell/SidebarNav.tsx +0 -100
- package/src/RedactoPrivacyCenter/components/AppShell/TopAppBar.tsx +0 -339
- package/src/RedactoPrivacyCenter/components/AppShell/UserMenu.tsx +0 -245
- package/src/RedactoPrivacyCenter/components/AppShell/hooks/useIsTruncated.ts +0 -28
- package/src/RedactoPrivacyCenter/components/AppShell/hooks/useKeyboardShortcut.ts +0 -40
- package/src/RedactoPrivacyCenter/components/AppShell/hooks/useSidebarState.ts +0 -62
- package/src/RedactoPrivacyCenter/components/AppShell/usePrivacyCenterExitAction.ts +0 -36
- package/src/RedactoPrivacyCenter/components/CaseDetails/AttachmentCard.tsx +0 -38
- package/src/RedactoPrivacyCenter/components/CaseDetails/ChatBubble.tsx +0 -31
- package/src/RedactoPrivacyCenter/components/CaseDetails/DocumentRequestCard.tsx +0 -79
- package/src/RedactoPrivacyCenter/components/CaseDetails/FinalizedTooltipWrapper.tsx +0 -25
- package/src/RedactoPrivacyCenter/components/CaseDetails/MessageComposer.tsx +0 -96
- package/src/RedactoPrivacyCenter/components/CaseDetails/MessageItem.tsx +0 -97
- package/src/RedactoPrivacyCenter/components/CaseDetails/MessagesTab.tsx +0 -156
- package/src/RedactoPrivacyCenter/components/CaseDetails/RequestDetailsTab.tsx +0 -171
- package/src/RedactoPrivacyCenter/components/CaseDetails/StatusEvent.tsx +0 -23
- package/src/RedactoPrivacyCenter/components/CaseDetails/useFileUpload.ts +0 -116
- package/src/RedactoPrivacyCenter/components/CaseDetails/useMessagePolling.ts +0 -43
- package/src/RedactoPrivacyCenter/components/CaseDetails/utils.ts +0 -71
- package/src/RedactoPrivacyCenter/components/CaseDetailsModal.tsx +0 -469
- package/src/RedactoPrivacyCenter/components/CaseHistory.tsx +0 -426
- package/src/RedactoPrivacyCenter/components/ConsentManager.tsx +0 -1206
- package/src/RedactoPrivacyCenter/components/ConsentManagerCells.tsx +0 -218
- package/src/RedactoPrivacyCenter/components/ConsentManagerColumns.tsx +0 -138
- package/src/RedactoPrivacyCenter/components/ConsentManagerConstants.ts +0 -7
- package/src/RedactoPrivacyCenter/components/ConsentManagerExpandedRow.tsx +0 -179
- package/src/RedactoPrivacyCenter/components/ConsentManagerGroupedColumns.tsx +0 -318
- package/src/RedactoPrivacyCenter/components/ConsentManagerProductCards.tsx +0 -333
- package/src/RedactoPrivacyCenter/components/ConsentManagerPurposeCards.tsx +0 -102
- package/src/RedactoPrivacyCenter/components/ConsentManagerTableSkeleton.tsx +0 -159
- package/src/RedactoPrivacyCenter/components/DataRequest/CorrectionDataSection.tsx +0 -189
- package/src/RedactoPrivacyCenter/components/DataRequest/GrievanceDataSection.tsx +0 -118
- package/src/RedactoPrivacyCenter/components/DataRequest/NominationDataSection.tsx +0 -66
- package/src/RedactoPrivacyCenter/components/DataRequest/ProductSelectionSection.tsx +0 -222
- package/src/RedactoPrivacyCenter/components/DataRequest/PurposeSelectionSection.tsx +0 -331
- package/src/RedactoPrivacyCenter/components/Form.tsx +0 -1821
- package/src/RedactoPrivacyCenter/components/GrievanceTable.tsx +0 -174
- package/src/RedactoPrivacyCenter/components/GrievanceTablePagination.tsx +0 -337
- package/src/RedactoPrivacyCenter/components/ModifyConsentModal.tsx +0 -334
- package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationBell.tsx +0 -173
- package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationCTAButton.tsx +0 -18
- package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationDropdown.tsx +0 -54
- package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationDropdownItem.tsx +0 -57
- package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationIcon.tsx +0 -19
- package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationListPanel.tsx +0 -92
- package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationRefreshButton.tsx +0 -20
- package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationRow.tsx +0 -58
- package/src/RedactoPrivacyCenter/components/NotificationHub/StatCard.tsx +0 -10
- package/src/RedactoPrivacyCenter/components/NotificationHub/useNotificationActions.ts +0 -112
- package/src/RedactoPrivacyCenter/components/NotificationHub/utils.ts +0 -27
- package/src/RedactoPrivacyCenter/components/Pagination.tsx +0 -140
- package/src/RedactoPrivacyCenter/components/PrivacyCenterEmptyState.tsx +0 -76
- package/src/RedactoPrivacyCenter/components/PrivacyCenterSessionExpired.tsx +0 -97
- package/src/RedactoPrivacyCenter/components/ReceiptsList.tsx +0 -454
- package/src/RedactoPrivacyCenter/components/RequestSubmit.tsx +0 -270
- package/src/RedactoPrivacyCenter/components/SelectUser.tsx +0 -95
- package/src/RedactoPrivacyCenter/components/SelectUserGate.tsx +0 -195
- package/src/RedactoPrivacyCenter/components/SideMenuBarPC/constants.tsx +0 -25
- package/src/RedactoPrivacyCenter/components/SideMenuBarPC/types.ts +0 -21
- package/src/RedactoPrivacyCenter/components/caseHistoryColumns.tsx +0 -159
- package/src/RedactoPrivacyCenter/components/constant.ts +0 -52
- package/src/RedactoPrivacyCenter/components/groupHelpers.ts +0 -155
- package/src/RedactoPrivacyCenter/components/receiptColumns.tsx +0 -203
- package/src/RedactoPrivacyCenter/components/types.ts +0 -123
- package/src/RedactoPrivacyCenter/components/utils.tsx +0 -10
- package/src/RedactoPrivacyCenter/config/routes.ts +0 -9
- package/src/RedactoPrivacyCenter/config/table.ts +0 -25
- package/src/RedactoPrivacyCenter/context/AuthContext.tsx +0 -252
- package/src/RedactoPrivacyCenter/context/BaseUrlContext.tsx +0 -47
- package/src/RedactoPrivacyCenter/context/BrandingContext.tsx +0 -52
- package/src/RedactoPrivacyCenter/context/DataAvailabilityContext.session-expired.test.tsx +0 -92
- package/src/RedactoPrivacyCenter/context/DataAvailabilityContext.tsx +0 -181
- package/src/RedactoPrivacyCenter/context/LocaleContext.tsx +0 -11
- package/src/RedactoPrivacyCenter/context/NavigationContext.tsx +0 -96
- package/src/RedactoPrivacyCenter/context/OrganizationContext.tsx +0 -30
- package/src/RedactoPrivacyCenter/context/PageChromeContext.tsx +0 -80
- package/src/RedactoPrivacyCenter/context/ProfileSwitchContext.tsx +0 -15
- package/src/RedactoPrivacyCenter/context/types.ts +0 -57
- package/src/RedactoPrivacyCenter/lib/api.ts +0 -759
- package/src/RedactoPrivacyCenter/lib/consent-manager/utils.tsx +0 -67
- package/src/RedactoPrivacyCenter/lib/constants.ts +0 -283
- package/src/RedactoPrivacyCenter/lib/i18n.ts +0 -81
- package/src/RedactoPrivacyCenter/lib/locales.ts +0 -102
- package/src/RedactoPrivacyCenter/lib/queries.ts +0 -141
- package/src/RedactoPrivacyCenter/lib/sandbox.ts +0 -104
- package/src/RedactoPrivacyCenter/lib/types.ts +0 -896
- package/src/RedactoPrivacyCenter/lib/useCurrentLanguage.ts +0 -37
- package/src/RedactoPrivacyCenter/lib/utils.refresh-circuit.test.ts +0 -127
- package/src/RedactoPrivacyCenter/lib/utils.refresh-storage.test.ts +0 -85
- package/src/RedactoPrivacyCenter/lib/utils.session-expired.test.ts +0 -109
- package/src/RedactoPrivacyCenter/lib/utils.ts +0 -798
- package/src/RedactoPrivacyCenter/locales/as/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/bn/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/brx/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/doi/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/en/translation.json +0 -357
- package/src/RedactoPrivacyCenter/locales/gom/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/gu/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/hi/translation.json +0 -345
- package/src/RedactoPrivacyCenter/locales/kn/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/ks/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/mai/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/ml/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/mni-Mtei/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/mr/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/ne/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/or/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/pa/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/sa/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/sat/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/sd/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/ta/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/te/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/ur/translation.json +0 -342
- package/src/RedactoPrivacyCenter/styles/applyBrandingTheme.ts +0 -66
- package/src/RedactoPrivacyCenter/styles/base.css +0 -117
- package/src/RedactoPrivacyCenter/styles/injectStyles.ts +0 -3108
- package/src/RedactoPrivacyCenter/styles/pcStyles.ts +0 -831
- package/src/RedactoPrivacyCenter/styles/responsive.ts +0 -138
- package/src/RedactoPrivacyCenter/styles/variables.css +0 -96
- package/src/RedactoPrivacyCenter/table/AdvancedTable.tsx +0 -387
- package/src/RedactoPrivacyCenter/table/AdvancedTablePagination.tsx +0 -468
- package/src/RedactoPrivacyCenter/table/index.ts +0 -2
- package/src/RedactoPrivacyCenter/types/images.d.ts +0 -29
- package/src/RedactoPrivacyCenter/ui/Footer.tsx +0 -69
- package/src/RedactoPrivacyCenter/ui/FormBlockWrapper.tsx +0 -54
- package/src/RedactoPrivacyCenter/ui/PCAccordion.tsx +0 -93
- package/src/RedactoPrivacyCenter/ui/PCActionIcon.tsx +0 -47
- package/src/RedactoPrivacyCenter/ui/PCAvatar.test.tsx +0 -83
- package/src/RedactoPrivacyCenter/ui/PCAvatar.tsx +0 -93
- package/src/RedactoPrivacyCenter/ui/PCBadge.tsx +0 -48
- package/src/RedactoPrivacyCenter/ui/PCButton.tsx +0 -77
- package/src/RedactoPrivacyCenter/ui/PCCheckbox.tsx +0 -55
- package/src/RedactoPrivacyCenter/ui/PCCollapse.tsx +0 -107
- package/src/RedactoPrivacyCenter/ui/PCEmpty.tsx +0 -35
- package/src/RedactoPrivacyCenter/ui/PCInput.tsx +0 -132
- package/src/RedactoPrivacyCenter/ui/PCLoader.tsx +0 -31
- package/src/RedactoPrivacyCenter/ui/PCModal.tsx +0 -106
- package/src/RedactoPrivacyCenter/ui/PCPopover.tsx +0 -206
- package/src/RedactoPrivacyCenter/ui/PCSelect.tsx +0 -433
- package/src/RedactoPrivacyCenter/ui/PCTable.tsx +0 -149
- package/src/RedactoPrivacyCenter/ui/PCText.tsx +0 -41
- package/src/RedactoPrivacyCenter/ui/PCTextarea.tsx +0 -43
- package/src/RedactoPrivacyCenter/ui/PCTitle.tsx +0 -37
- package/src/RedactoPrivacyCenter/ui/PCTooltip.tsx +0 -121
- package/src/RedactoPrivacyCenter/ui/SimpleBar.tsx +0 -25
- package/src/RedactoPrivacyCenter/ui/StatusBadge.tsx +0 -53
- package/src/RedactoPrivacyCenter/ui/index.ts +0 -18
- package/src/RedactoPrivacyCenter/utils/cn.ts +0 -28
- package/src/RedactoPrivacyCenter/utils/date.ts +0 -48
- package/src/RedactoPrivacyCenter/utils/status.ts +0 -111
- package/src/RedactoPrivacyCenter/utils/toast.ts +0 -130
- package/src/index.ts +0 -19
- package/src/privacy-center.ts +0 -113
- package/src/shared/api-errors.ts +0 -180
- package/src/shared/notice-consent-status.ts +0 -190
- package/src/shared/sandbox.ts +0 -6
- package/src/types/css.d.ts +0 -4
- package/src/types/images.d.ts +0 -19
- package/src/types/react-jsx.d.ts +0 -26
- package/tests/DataAvailabilityContext.test.tsx +0 -123
- package/tests/Form.test.tsx +0 -1424
- package/tests/ManageConsentErrorMapping.test.tsx +0 -85
- package/tests/ProductSelectionSection.test.tsx +0 -328
- package/tests/RequestSubmit.test.tsx +0 -135
- package/tests/RevocablePurpose.test.tsx +0 -146
- package/tests/RevokeWarningMessage.test.tsx +0 -151
- package/tests/consentClient.test.ts +0 -217
- package/tests/manageConsentServer.test.ts +0 -38
- package/tests/mocks.ts +0 -257
- package/tests/resolveContactFromJwt.test.ts +0 -44
- package/tests/setup.ts +0 -16
- package/tsconfig.json +0 -16
- package/tsup.config.ts +0 -31
- 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
|
-
});
|