@redacto.io/consent-sdk-react 9.2.0-beta.1 → 9.2.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/CHANGELOG.md +24 -0
- package/dist/privacy-center.d.mts +6 -10
- package/dist/privacy-center.d.ts +6 -10
- package/dist/privacy-center.js +208 -850
- package/dist/privacy-center.mjs +208 -850
- package/package.json +1 -1
- package/src/RedactoPrivacyCenter/components/DataRequest/ProductSelectionSection.tsx +4 -4
- package/src/RedactoPrivacyCenter/components/Form.tsx +214 -866
- package/src/RedactoPrivacyCenter/components/RequestSubmit.tsx +44 -219
- package/src/RedactoPrivacyCenter/components/types.ts +14 -20
- package/src/RedactoPrivacyCenter/lib/api.ts +23 -58
- package/src/RedactoPrivacyCenter/lib/types.ts +0 -31
- package/src/RedactoPrivacyCenter/locales/as/translation.json +2 -2
- package/src/RedactoPrivacyCenter/locales/bho/translation.json +2 -2
- package/src/RedactoPrivacyCenter/locales/bn/translation.json +2 -2
- package/src/RedactoPrivacyCenter/locales/brx/translation.json +2 -2
- package/src/RedactoPrivacyCenter/locales/doi/translation.json +2 -2
- package/src/RedactoPrivacyCenter/locales/en/translation.json +2 -2
- package/src/RedactoPrivacyCenter/locales/gom/translation.json +2 -2
- package/src/RedactoPrivacyCenter/locales/gu/translation.json +2 -2
- package/src/RedactoPrivacyCenter/locales/hi/translation.json +2 -2
- package/src/RedactoPrivacyCenter/locales/kn/translation.json +2 -2
- package/src/RedactoPrivacyCenter/locales/ks/translation.json +2 -2
- package/src/RedactoPrivacyCenter/locales/mai/translation.json +2 -2
- package/src/RedactoPrivacyCenter/locales/ml/translation.json +2 -2
- package/src/RedactoPrivacyCenter/locales/mni-Mtei/translation.json +2 -2
- package/src/RedactoPrivacyCenter/locales/mr/translation.json +2 -2
- package/src/RedactoPrivacyCenter/locales/ne/translation.json +2 -2
- package/src/RedactoPrivacyCenter/locales/or/translation.json +2 -2
- package/src/RedactoPrivacyCenter/locales/pa/translation.json +2 -2
- package/src/RedactoPrivacyCenter/locales/sa/translation.json +2 -2
- package/src/RedactoPrivacyCenter/locales/sat/translation.json +2 -2
- package/src/RedactoPrivacyCenter/locales/sd/translation.json +2 -2
- package/src/RedactoPrivacyCenter/locales/ta/translation.json +2 -2
- package/src/RedactoPrivacyCenter/locales/te/translation.json +2 -2
- package/src/RedactoPrivacyCenter/locales/ur/translation.json +2 -2
- package/tests/Form.test.tsx +123 -149
- package/tests/ProductSelectionSection.test.tsx +39 -4
- package/tests/RequestSubmit.test.tsx +27 -187
|
@@ -4,18 +4,13 @@ import { describe, it, expect, vi } from 'vitest';
|
|
|
4
4
|
// react-i18next is an optional peer dep that is not installed in this
|
|
5
5
|
// package's node_modules. Stub it with a minimal `useTranslation` that
|
|
6
6
|
// resolves the en/translation.json keys we use here, with interpolation
|
|
7
|
-
//
|
|
7
|
+
// support.
|
|
8
8
|
vi.mock('react-i18next', () => {
|
|
9
9
|
const en: Record<string, string> = {
|
|
10
10
|
requestSubmittedSuccessfully: 'Request Submitted Successfully',
|
|
11
11
|
thankYouForSubmittingYourRequest: 'Thank you for submitting your request',
|
|
12
12
|
caseID: 'Case ID',
|
|
13
13
|
saveThisID: 'Please save this ID for future reference',
|
|
14
|
-
casesCreatedCount_one: '{{count}} case created',
|
|
15
|
-
casesCreatedCount_other: '{{count}} cases created',
|
|
16
|
-
someRequestsFailed: 'Some requests failed',
|
|
17
|
-
retry: 'Retry',
|
|
18
|
-
retrying: 'Retrying...',
|
|
19
14
|
confirmationSentTo: 'Confirmation sent to',
|
|
20
15
|
processRequest: 'We will process your request as per laws',
|
|
21
16
|
backToHome: 'Back to homepage',
|
|
@@ -29,14 +24,7 @@ vi.mock('react-i18next', () => {
|
|
|
29
24
|
return value === undefined || value === null ? '' : String(value);
|
|
30
25
|
});
|
|
31
26
|
const t = (key: string, opts?: Record<string, unknown>) => {
|
|
32
|
-
const
|
|
33
|
-
const pluralKey =
|
|
34
|
-
count !== undefined
|
|
35
|
-
? count === 1
|
|
36
|
-
? `${key}_one`
|
|
37
|
-
: `${key}_other`
|
|
38
|
-
: key;
|
|
39
|
-
const template = en[pluralKey] ?? en[key] ?? key;
|
|
27
|
+
const template = en[key] ?? key;
|
|
40
28
|
return interpolate(template, opts);
|
|
41
29
|
};
|
|
42
30
|
return {
|
|
@@ -50,191 +38,72 @@ vi.mock('lucide-react', () => ({
|
|
|
50
38
|
}));
|
|
51
39
|
|
|
52
40
|
import RequestSubmit from '../src/RedactoPrivacyCenter/components/RequestSubmit';
|
|
53
|
-
import type {
|
|
41
|
+
import type { RequestSubmitResult } from '../src/RedactoPrivacyCenter/components/types';
|
|
54
42
|
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
...overrides,
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
const singleSucceeded: BatchCaseSubmitResult = {
|
|
64
|
-
succeeded: [
|
|
65
|
-
{
|
|
66
|
-
group_key: '',
|
|
67
|
-
product_uuid: '',
|
|
68
|
-
product_name: '',
|
|
69
|
-
case_id: 'CASE-ONLY',
|
|
70
|
-
uuid: 'uuid-only',
|
|
71
|
-
},
|
|
72
|
-
],
|
|
73
|
-
failed: [],
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const multiSucceeded: BatchCaseSubmitResult = {
|
|
77
|
-
succeeded: [
|
|
78
|
-
{
|
|
79
|
-
group_key: 'prod-1',
|
|
80
|
-
product_uuid: 'prod-1',
|
|
81
|
-
product_name: 'Acme Loyalty',
|
|
82
|
-
case_id: 'CASE-1',
|
|
83
|
-
uuid: 'uuid-1',
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
group_key: 'prod-2',
|
|
87
|
-
product_uuid: 'prod-2',
|
|
88
|
-
product_name: 'Acme Rewards',
|
|
89
|
-
case_id: 'CASE-2',
|
|
90
|
-
uuid: 'uuid-2',
|
|
91
|
-
},
|
|
92
|
-
],
|
|
93
|
-
failed: [],
|
|
43
|
+
const productScopedResult: RequestSubmitResult = {
|
|
44
|
+
case_id: 'CASE-PROD',
|
|
45
|
+
uuid: 'uuid-prod',
|
|
46
|
+
product_name: 'Acme Loyalty',
|
|
47
|
+
product_uuid: 'prod-1',
|
|
94
48
|
};
|
|
95
49
|
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
product_name: 'Working Product',
|
|
102
|
-
case_id: 'CASE-OK',
|
|
103
|
-
uuid: 'uuid-ok',
|
|
104
|
-
},
|
|
105
|
-
],
|
|
106
|
-
failed: [
|
|
107
|
-
{
|
|
108
|
-
group_key: 'prod-fail',
|
|
109
|
-
product_uuid: 'prod-fail',
|
|
110
|
-
product_name: 'Broken Product',
|
|
111
|
-
error: 'Network unreachable',
|
|
112
|
-
},
|
|
113
|
-
],
|
|
50
|
+
const orgLevelResult: RequestSubmitResult = {
|
|
51
|
+
case_id: 'CASE-ORG',
|
|
52
|
+
uuid: 'uuid-org',
|
|
53
|
+
product_name: '',
|
|
54
|
+
product_uuid: '',
|
|
114
55
|
};
|
|
115
56
|
|
|
116
57
|
describe('RequestSubmit', () => {
|
|
117
|
-
it('renders
|
|
58
|
+
it('renders the case id and product label for a product-scoped result', () => {
|
|
118
59
|
render(
|
|
119
60
|
<RequestSubmit
|
|
120
|
-
result={
|
|
61
|
+
result={productScopedResult}
|
|
121
62
|
contact="alice@example.com"
|
|
122
63
|
onCreateRequest={vi.fn()}
|
|
123
64
|
/>,
|
|
124
65
|
);
|
|
125
66
|
|
|
126
|
-
// Single-case branch: one "Case ID: ..." row, no plural header, no
|
|
127
|
-
// failure block.
|
|
128
67
|
expect(
|
|
129
68
|
screen.getByText('Request Submitted Successfully'),
|
|
130
69
|
).toBeInTheDocument();
|
|
131
|
-
expect(screen.getByText(/Case ID:\s*CASE-
|
|
132
|
-
expect(screen.
|
|
133
|
-
expect(
|
|
134
|
-
screen.queryByText('Some requests failed'),
|
|
135
|
-
).not.toBeInTheDocument();
|
|
136
|
-
expect(screen.queryByRole('button', { name: 'Retry' })).toBeNull();
|
|
70
|
+
expect(screen.getByText(/Case ID:\s*CASE-PROD/)).toBeInTheDocument();
|
|
71
|
+
expect(screen.getByText('Acme Loyalty')).toBeInTheDocument();
|
|
137
72
|
});
|
|
138
73
|
|
|
139
|
-
it('
|
|
74
|
+
it('omits the product label when the result has no product anchor', () => {
|
|
140
75
|
render(
|
|
141
76
|
<RequestSubmit
|
|
142
|
-
result={
|
|
77
|
+
result={orgLevelResult}
|
|
143
78
|
contact="alice@example.com"
|
|
144
79
|
onCreateRequest={vi.fn()}
|
|
145
80
|
/>,
|
|
146
81
|
);
|
|
147
82
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
expect(screen.getByText('Acme Loyalty')).toBeInTheDocument();
|
|
152
|
-
expect(screen.getByText('Acme Rewards')).toBeInTheDocument();
|
|
153
|
-
expect(screen.getByText(/Case ID:\s*CASE-1/)).toBeInTheDocument();
|
|
154
|
-
expect(screen.getByText(/Case ID:\s*CASE-2/)).toBeInTheDocument();
|
|
155
|
-
// No failure block when all succeeded.
|
|
156
|
-
expect(
|
|
157
|
-
screen.queryByText('Some requests failed'),
|
|
158
|
-
).not.toBeInTheDocument();
|
|
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();
|
|
159
86
|
});
|
|
160
87
|
|
|
161
|
-
it('
|
|
88
|
+
it('shows the contact in the confirmation line', () => {
|
|
162
89
|
render(
|
|
163
90
|
<RequestSubmit
|
|
164
|
-
result={
|
|
91
|
+
result={productScopedResult}
|
|
165
92
|
contact="alice@example.com"
|
|
166
93
|
onCreateRequest={vi.fn()}
|
|
167
|
-
onRetry={vi.fn()}
|
|
168
94
|
/>,
|
|
169
95
|
);
|
|
170
96
|
|
|
171
|
-
// Succeeded row still present.
|
|
172
|
-
expect(screen.getByText('Working Product')).toBeInTheDocument();
|
|
173
|
-
expect(screen.getByText(/Case ID:\s*CASE-OK/)).toBeInTheDocument();
|
|
174
|
-
|
|
175
|
-
// Failure block with product name, error message, retry button.
|
|
176
|
-
expect(screen.getByText('Some requests failed')).toBeInTheDocument();
|
|
177
|
-
expect(screen.getByText('Broken Product')).toBeInTheDocument();
|
|
178
|
-
expect(screen.getByText('Network unreachable')).toBeInTheDocument();
|
|
179
97
|
expect(
|
|
180
|
-
screen.
|
|
98
|
+
screen.getByText(/Confirmation sent to:\s*alice@example\.com/),
|
|
181
99
|
).toBeInTheDocument();
|
|
182
100
|
});
|
|
183
101
|
|
|
184
|
-
it('does not render retry buttons when onRetry is not provided', () => {
|
|
185
|
-
render(
|
|
186
|
-
<RequestSubmit
|
|
187
|
-
result={partialResult}
|
|
188
|
-
contact="alice@example.com"
|
|
189
|
-
onCreateRequest={vi.fn()}
|
|
190
|
-
/>,
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
expect(screen.getByText('Some requests failed')).toBeInTheDocument();
|
|
194
|
-
expect(screen.queryByRole('button', { name: 'Retry' })).toBeNull();
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it('invokes onRetry with the failed entry group_key when retry is clicked', () => {
|
|
198
|
-
const onRetry = vi.fn();
|
|
199
|
-
render(
|
|
200
|
-
<RequestSubmit
|
|
201
|
-
result={partialResult}
|
|
202
|
-
contact="alice@example.com"
|
|
203
|
-
onCreateRequest={vi.fn()}
|
|
204
|
-
onRetry={onRetry}
|
|
205
|
-
/>,
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
fireEvent.click(screen.getByRole('button', { name: 'Retry' }));
|
|
209
|
-
|
|
210
|
-
expect(onRetry).toHaveBeenCalledTimes(1);
|
|
211
|
-
expect(onRetry).toHaveBeenCalledWith('prod-fail');
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
it('shows the Retrying… label and disables the button for rows in retryingGroupKeys', () => {
|
|
215
|
-
const onRetry = vi.fn();
|
|
216
|
-
render(
|
|
217
|
-
<RequestSubmit
|
|
218
|
-
result={partialResult}
|
|
219
|
-
contact="alice@example.com"
|
|
220
|
-
onCreateRequest={vi.fn()}
|
|
221
|
-
onRetry={onRetry}
|
|
222
|
-
retryingGroupKeys={new Set(['prod-fail'])}
|
|
223
|
-
/>,
|
|
224
|
-
);
|
|
225
|
-
|
|
226
|
-
const button = screen.getByRole('button', {
|
|
227
|
-
name: /Retrying/,
|
|
228
|
-
}) as HTMLButtonElement;
|
|
229
|
-
expect(button).toBeInTheDocument();
|
|
230
|
-
expect(button.disabled).toBe(true);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
102
|
it('invokes onCreateRequest when "Back to homepage" is clicked', () => {
|
|
234
103
|
const onCreateRequest = vi.fn();
|
|
235
104
|
render(
|
|
236
105
|
<RequestSubmit
|
|
237
|
-
result={
|
|
106
|
+
result={productScopedResult}
|
|
238
107
|
contact="alice@example.com"
|
|
239
108
|
onCreateRequest={onCreateRequest}
|
|
240
109
|
/>,
|
|
@@ -251,7 +120,7 @@ describe('RequestSubmit', () => {
|
|
|
251
120
|
it('renders the erasure-revoke notice when revokeConsentOnFulfilment is true', () => {
|
|
252
121
|
render(
|
|
253
122
|
<RequestSubmit
|
|
254
|
-
result={
|
|
123
|
+
result={productScopedResult}
|
|
255
124
|
contact="alice@example.com"
|
|
256
125
|
onCreateRequest={vi.fn()}
|
|
257
126
|
revokeConsentOnFulfilment
|
|
@@ -263,33 +132,4 @@ describe('RequestSubmit', () => {
|
|
|
263
132
|
screen.getByText(/Marketing, Analytics/),
|
|
264
133
|
).toBeInTheDocument();
|
|
265
134
|
});
|
|
266
|
-
|
|
267
|
-
it('renders nothing in the case-list area when succeeded is empty (failures-only)', () => {
|
|
268
|
-
// Edge case: every product failed. Header still shows the success
|
|
269
|
-
// banner (parent decides whether to render); the case-list area is
|
|
270
|
-
// omitted and the failure block is the only middle content.
|
|
271
|
-
const failuresOnly = makeResult({
|
|
272
|
-
failed: [
|
|
273
|
-
{
|
|
274
|
-
group_key: 'prod-a',
|
|
275
|
-
product_uuid: 'prod-a',
|
|
276
|
-
product_name: 'Product A',
|
|
277
|
-
error: 'Timeout',
|
|
278
|
-
},
|
|
279
|
-
],
|
|
280
|
-
});
|
|
281
|
-
render(
|
|
282
|
-
<RequestSubmit
|
|
283
|
-
result={failuresOnly}
|
|
284
|
-
contact="alice@example.com"
|
|
285
|
-
onCreateRequest={vi.fn()}
|
|
286
|
-
onRetry={vi.fn()}
|
|
287
|
-
/>,
|
|
288
|
-
);
|
|
289
|
-
|
|
290
|
-
expect(screen.queryByText(/Case ID:/)).not.toBeInTheDocument();
|
|
291
|
-
expect(screen.queryByText(/cases created/)).not.toBeInTheDocument();
|
|
292
|
-
expect(screen.getByText('Some requests failed')).toBeInTheDocument();
|
|
293
|
-
expect(screen.getByText('Product A')).toBeInTheDocument();
|
|
294
|
-
});
|
|
295
135
|
});
|