@block-auth.io/angular 0.2.9 → 0.3.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/.turbo/turbo-build.log +40 -37
- package/.turbo/turbo-lint.log +4 -4
- package/.turbo/turbo-test.log +102 -1241
- package/.turbo/turbo-typecheck.log +19 -19
- package/CHANGELOG.md +120 -0
- package/package.json +3 -3
- package/src/lib/components/txns/internals/permissions-required-modal.component.test.ts +9 -0
- package/src/lib/components/txns/internals/permissions-required-modal.component.ts +3 -3
- package/src/lib/components/txns/internals/phone-input.component.test.ts +119 -0
- package/src/lib/components/txns/internals/phone-input.component.ts +301 -0
- package/src/lib/components/txns/internals/solicited-data-modal.component.test.ts +561 -4
- package/src/lib/components/txns/internals/solicited-data-modal.component.ts +324 -129
- package/src/lib/components/txns/internals/txn-type.component.ts +2 -2
- package/src/lib/components/txns/internals/txn.component.test.ts +345 -0
- package/src/lib/components/txns/internals/txn.component.ts +157 -74
- package/src/lib/components/txns/txns-overview.component.test.ts +270 -2
- package/src/lib/components/txns/txns-overview.component.ts +153 -23
- package/src/lib/primitives/arrow-icon.component.test.ts +13 -0
- package/src/lib/primitives/arrow-icon.component.ts +8 -1
- package/src/public-api.ts +1 -0
|
@@ -48,60 +48,62 @@ interface TxnPreParams {
|
|
|
48
48
|
class="bas-flex bas-h-full bas-w-full bas-flex-col bas-items-center bas-justify-between"
|
|
49
49
|
>
|
|
50
50
|
<div
|
|
51
|
-
class="bal-ba-
|
|
51
|
+
class="bal-ba-glass-rise bas-flex bas-w-full bas-flex-col bas-gap-8 bas-pt-2"
|
|
52
52
|
>
|
|
53
|
-
<div
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
>
|
|
53
|
+
<div class="bas-relative bas-flex bas-w-full">
|
|
54
|
+
<button
|
|
55
|
+
type="button"
|
|
56
|
+
id="btn-txn-permissions-required"
|
|
57
|
+
class="bal-ba-glass-btn"
|
|
58
|
+
(click)="permissionsRequiredClicked.emit()"
|
|
59
|
+
>
|
|
60
|
+
<span class="bal-ba-glass-btn-label">
|
|
62
61
|
{{ permissionsRequiredTxtResolved() }}
|
|
63
|
-
</
|
|
64
|
-
</
|
|
65
|
-
<ba-arrow-icon direction="left" />
|
|
62
|
+
</span>
|
|
63
|
+
</button>
|
|
66
64
|
</div>
|
|
67
65
|
|
|
68
|
-
<div
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
class="bas-flex bas-h-full bas-w-full bas-items-end bas-justify-center"
|
|
66
|
+
<div class="bas-relative bas-flex bas-w-full">
|
|
67
|
+
<button
|
|
68
|
+
type="button"
|
|
69
|
+
id="btn-txn-solicited-data"
|
|
70
|
+
[disabled]="!hasSolicitedData()"
|
|
71
|
+
[class]="
|
|
72
|
+
'bal-ba-glass-btn' +
|
|
73
|
+
(showSolicitedArrows() ? ' bal-ba-glass-btn-pending' : '') +
|
|
74
|
+
(acceptHint() === 'solicitedData' ? ' bal-ba-head-shake' : '')
|
|
75
|
+
"
|
|
76
|
+
(click)="onSolicitedDataClick()"
|
|
80
77
|
>
|
|
81
|
-
<
|
|
82
|
-
[class]="
|
|
83
|
-
'bas-m-0 bas-text-center bas-text-lg bas-transition-colors ' +
|
|
84
|
-
(hasSolicitedData()
|
|
85
|
-
? 'bas-font-medium bas-text-[var(--bal-primary-900)]'
|
|
86
|
-
: 'bas-text-slate-400')
|
|
87
|
-
"
|
|
88
|
-
>
|
|
78
|
+
<span class="bal-ba-glass-btn-label">
|
|
89
79
|
{{ solicitedDataTxtResolved() }}
|
|
90
|
-
</
|
|
91
|
-
</
|
|
92
|
-
<ba-arrow-
|
|
80
|
+
</span>
|
|
81
|
+
</button>
|
|
82
|
+
<span class="bal-ba-glass-btn-arrow bal-ba-glass-btn-arrow-left">
|
|
83
|
+
<ba-arrow-icon
|
|
84
|
+
direction="right"
|
|
85
|
+
[visible]="showSolicitedArrows()"
|
|
86
|
+
/>
|
|
87
|
+
</span>
|
|
88
|
+
<span class="bal-ba-glass-btn-arrow bal-ba-glass-btn-arrow-right">
|
|
89
|
+
<ba-arrow-icon direction="left" [visible]="showSolicitedArrows()" />
|
|
90
|
+
</span>
|
|
93
91
|
</div>
|
|
94
92
|
</div>
|
|
95
93
|
|
|
96
|
-
@if (!showLifetime()) {
|
|
97
94
|
<button
|
|
98
95
|
type="button"
|
|
99
96
|
id="btn-configure-session-time"
|
|
100
|
-
class="bal-ba-
|
|
97
|
+
class="bal-ba-glass-link bal-ba-glass-rise bas-flex bas-w-full bas-cursor-pointer bas-flex-row bas-items-end bas-justify-center bas-gap-2 bas-border-0 bas-bg-transparent bas-text-[--bal-primary-700] hover:bas-text-[--bal-primary-900]"
|
|
101
98
|
(click)="showLifetime.set(true)"
|
|
102
99
|
>
|
|
103
100
|
<p class="bas-m-0 bas-text-center bas-text-base">
|
|
104
101
|
{{ configureSessionTimeTxtResolved() }}
|
|
102
|
+
@if (isLifetimeConfigured()) {
|
|
103
|
+
<span id="txn-lifetime-value" class="bal-ba-glass-link-value">{{
|
|
104
|
+
lifetimeTimeValue()
|
|
105
|
+
}}</span>
|
|
106
|
+
}
|
|
105
107
|
</p>
|
|
106
108
|
<svg
|
|
107
109
|
class="bas-h-5 bas-w-5"
|
|
@@ -119,37 +121,81 @@ interface TxnPreParams {
|
|
|
119
121
|
/>
|
|
120
122
|
</svg>
|
|
121
123
|
</button>
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
124
|
+
|
|
125
|
+
@if (showLifetime()) {
|
|
126
|
+
<div
|
|
127
|
+
class="bal-ba-glass-backdrop bal-ba-backdrop-blur-base bal-ba-backdrop-blur-sm bas-absolute bas-inset-0 bas-z-30 bas-box-border bas-h-full bas-w-full"
|
|
128
|
+
(click)="closeLifetime()"
|
|
129
|
+
></div>
|
|
130
|
+
<div
|
|
131
|
+
class="bal-ba-glass-popup-slot bas-absolute bas-inset-x-0 bas-top-44 bas-z-40"
|
|
132
|
+
>
|
|
133
|
+
<div
|
|
134
|
+
role="dialog"
|
|
135
|
+
id="txn-lifetime-modal"
|
|
136
|
+
[attr.aria-label]="configureSessionTimeTxtResolved()"
|
|
137
|
+
class="bal-ba-glass-panel bal-ba-glass-pop bal-ba-glass-lifetime bas-box-border bas-w-[334px] bas-p-4"
|
|
138
|
+
>
|
|
139
|
+
<div class="bal-ba-glass-modal-head">
|
|
140
|
+
<button
|
|
141
|
+
type="button"
|
|
142
|
+
id="txn-lifetime-close"
|
|
143
|
+
[attr.aria-label]="t('close')"
|
|
144
|
+
class="bal-ba-glass-close"
|
|
145
|
+
(click)="closeLifetime()"
|
|
146
|
+
>
|
|
147
|
+
<svg
|
|
148
|
+
viewBox="0 0 10 10"
|
|
149
|
+
fill="none"
|
|
150
|
+
stroke="currentColor"
|
|
151
|
+
stroke-width="1.6"
|
|
152
|
+
stroke-linecap="round"
|
|
153
|
+
aria-hidden="true"
|
|
154
|
+
>
|
|
155
|
+
<path d="M2 2l6 6M8 2 2 8" />
|
|
156
|
+
</svg>
|
|
157
|
+
</button>
|
|
158
|
+
</div>
|
|
159
|
+
<ba-lifetime-slider-base
|
|
160
|
+
id="txn-lifetime-slider"
|
|
161
|
+
[sliderValue]="lifetimeSign()"
|
|
162
|
+
[step]="1"
|
|
163
|
+
[min]="minLifetime()"
|
|
164
|
+
[max]="maxLifetime()"
|
|
165
|
+
[timeInputValue]="lifetimeTimeValue()"
|
|
166
|
+
[isOpen]="isTimePickerOpen()"
|
|
167
|
+
[isTimePickerInsideModal]="true"
|
|
168
|
+
modalId="txns-overview"
|
|
169
|
+
timePickerDropdownDirection="down"
|
|
170
|
+
classNames="bal-ba-glass-lifetime-body"
|
|
171
|
+
timePickerContainerClassNames="bal-ba-glass-time bas-flex bas-w-full bas-items-center bas-justify-center"
|
|
172
|
+
sliderContainerClassNames="bal-ba-glass-slider"
|
|
173
|
+
(sliderValueChanged)="onLifetimeValueChanged($event)"
|
|
174
|
+
(timeChanged)="onLifetimeValueChanged($event)"
|
|
175
|
+
(isOpenChange)="isTimePickerOpen.set($event)"
|
|
176
|
+
(toggleTimerDropdown)="toggleTimerDropdown()"
|
|
177
|
+
/>
|
|
178
|
+
<button
|
|
179
|
+
type="button"
|
|
180
|
+
id="txn-lifetime-accept"
|
|
181
|
+
class="bal-ba-glass-action bal-ba-glass-action-primary"
|
|
182
|
+
(click)="closeLifetime()"
|
|
183
|
+
>
|
|
184
|
+
{{ acceptTxtResolved() }}
|
|
185
|
+
</button>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
141
188
|
}
|
|
142
189
|
|
|
143
190
|
<div
|
|
144
|
-
class="bal-ba-
|
|
191
|
+
class="bal-ba-glass-rise bas-flex bas-w-full bas-flex-col bas-gap-8 bas-pb-4"
|
|
145
192
|
>
|
|
146
193
|
<div
|
|
147
194
|
data-testid="txn-pptc-toggle"
|
|
148
195
|
[class]="
|
|
149
|
-
'
|
|
150
|
-
(pptcAccepted()
|
|
151
|
-
|
|
152
|
-
: 'bal-ba-hover-ring-primary-200')
|
|
196
|
+
'bal-ba-glass-consent bas-group bas-box-border bas-flex bas-h-fit bas-w-full bas-flex-row bas-items-center bas-gap-3 bas-p-[5px] bas-pl-[9px]' +
|
|
197
|
+
(pptcAccepted() ? ' bal-ba-glass-consent-checked' : '') +
|
|
198
|
+
(acceptHint() === 'privacyPolicy' ? ' bal-ba-head-shake' : '')
|
|
153
199
|
"
|
|
154
200
|
(click)="pptcAccepted.set(!pptcAccepted())"
|
|
155
201
|
>
|
|
@@ -206,7 +252,7 @@ interface TxnPreParams {
|
|
|
206
252
|
<button
|
|
207
253
|
type="button"
|
|
208
254
|
id="btn-txn-reject"
|
|
209
|
-
class="bal-ba-
|
|
255
|
+
class="bal-ba-glass-action bal-ba-glass-action-reject bas-group bas-box-border bas-flex bas-h-[42px] bas-w-full bas-cursor-pointer bas-flex-row bas-items-center bas-justify-center bas-gap-2 bas-px-2.5 bas-py-0"
|
|
210
256
|
(click)="onTxnRejectClicked()"
|
|
211
257
|
>
|
|
212
258
|
<span
|
|
@@ -215,19 +261,28 @@ interface TxnPreParams {
|
|
|
215
261
|
{{ rejectTxtResolved() }}
|
|
216
262
|
</span>
|
|
217
263
|
</button>
|
|
218
|
-
<
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
(
|
|
264
|
+
<div
|
|
265
|
+
data-testid="txn-accept-wrapper"
|
|
266
|
+
class="bas-flex bas-w-full"
|
|
267
|
+
(mouseenter)="showAcceptHint.set(true)"
|
|
268
|
+
(mouseleave)="showAcceptHint.set(false)"
|
|
269
|
+
(focusin)="showAcceptHint.set(true)"
|
|
270
|
+
(focusout)="showAcceptHint.set(false)"
|
|
224
271
|
>
|
|
225
|
-
<
|
|
226
|
-
|
|
272
|
+
<button
|
|
273
|
+
type="button"
|
|
274
|
+
id="btn-txn-accept"
|
|
275
|
+
[disabled]="isAcceptDisabled()"
|
|
276
|
+
class="bal-ba-glass-action bal-ba-glass-action-accept bas-group bas-box-border bas-flex bas-h-[42px] bas-w-full bas-cursor-pointer bas-flex-row bas-items-center bas-justify-center bas-gap-2 bas-px-2.5 bas-py-0"
|
|
277
|
+
(click)="onTxnAcceptClicked()"
|
|
227
278
|
>
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
279
|
+
<span
|
|
280
|
+
class="bal-ba-transition-2 bas-text-white bas-text-center bas-text-base bas-font-medium bas-flex bas-h-full bas-items-center"
|
|
281
|
+
>
|
|
282
|
+
{{ acceptTxtResolved() }}
|
|
283
|
+
</span>
|
|
284
|
+
</button>
|
|
285
|
+
</div>
|
|
231
286
|
</div>
|
|
232
287
|
</div>
|
|
233
288
|
</div>
|
|
@@ -245,6 +300,7 @@ export class TxnComponent {
|
|
|
245
300
|
configureSessionTimeTxt = input<string>();
|
|
246
301
|
hasSolicitedData = input(false);
|
|
247
302
|
allRequiredFilled = input(true);
|
|
303
|
+
solicitedDataPending = input<boolean>();
|
|
248
304
|
closeTxns = input<() => void>(() => {});
|
|
249
305
|
/** Session lifetime in seconds (default 3600). */
|
|
250
306
|
lifetime = input(3600);
|
|
@@ -252,6 +308,8 @@ export class TxnComponent {
|
|
|
252
308
|
selectedTxnPreParams = input<TxnPreParams>({});
|
|
253
309
|
/** User-provided parameter values for solicited data. */
|
|
254
310
|
userParamValues = input<Record<string, string>>({});
|
|
311
|
+
grantedParams = input<string[]>([]);
|
|
312
|
+
revokedParams = input<string[]>([]);
|
|
255
313
|
/** Callback when an error occurs during accept validation. */
|
|
256
314
|
addError = input<(messageSlug: string) => void>(() => {});
|
|
257
315
|
|
|
@@ -292,6 +350,21 @@ export class TxnComponent {
|
|
|
292
350
|
|
|
293
351
|
protected readonly pptcAccepted = signal(false);
|
|
294
352
|
protected readonly showLifetime = signal(false);
|
|
353
|
+
protected readonly showAcceptHint = signal(false);
|
|
354
|
+
protected readonly isAcceptDisabled = computed(
|
|
355
|
+
() => !this.pptcAccepted() || !this.allRequiredFilled(),
|
|
356
|
+
);
|
|
357
|
+
protected readonly acceptHint = computed<
|
|
358
|
+
'privacyPolicy' | 'solicitedData' | null
|
|
359
|
+
>(() => {
|
|
360
|
+
if (!this.showAcceptHint() || !this.isAcceptDisabled()) return null;
|
|
361
|
+
return this.pptcAccepted() ? 'solicitedData' : 'privacyPolicy';
|
|
362
|
+
});
|
|
363
|
+
protected readonly showSolicitedArrows = computed(
|
|
364
|
+
() =>
|
|
365
|
+
this.solicitedDataPending() ??
|
|
366
|
+
(this.hasSolicitedData() && !this.allRequiredFilled()),
|
|
367
|
+
);
|
|
295
368
|
|
|
296
369
|
private readonly lifetimeData = computed(
|
|
297
370
|
() => this.selectedTxnPreParams().lifetime ?? {},
|
|
@@ -308,6 +381,7 @@ export class TxnComponent {
|
|
|
308
381
|
// en sync.
|
|
309
382
|
protected readonly lifetimeTimeValue = signal(getTimeBySec(this.lifetime()));
|
|
310
383
|
protected readonly isTimePickerOpen = signal(false);
|
|
384
|
+
protected readonly isLifetimeConfigured = signal(false);
|
|
311
385
|
|
|
312
386
|
private autoCloseTimeout: ReturnType<typeof setTimeout> | undefined;
|
|
313
387
|
|
|
@@ -335,6 +409,12 @@ export class TxnComponent {
|
|
|
335
409
|
onLifetimeValueChanged(value: number): void {
|
|
336
410
|
this.lifetimeSign.set(value);
|
|
337
411
|
this.lifetimeTimeValue.set(getTimeBySec(value));
|
|
412
|
+
this.isLifetimeConfigured.set(true);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
closeLifetime(): void {
|
|
416
|
+
this.isTimePickerOpen.set(false);
|
|
417
|
+
this.showLifetime.set(false);
|
|
338
418
|
}
|
|
339
419
|
|
|
340
420
|
openPrivacyPolicy(): void {
|
|
@@ -364,10 +444,13 @@ export class TxnComponent {
|
|
|
364
444
|
if (!preParams) return this.addError()('no_transactions_params_found');
|
|
365
445
|
if (!preParams.appKey) return this.addError()('no_app_key_found');
|
|
366
446
|
if (!preParams.to) return this.addError()('no_contract_address_found');
|
|
447
|
+
this.client.state.$ui.setKey('acceptTxnError', null);
|
|
367
448
|
this.client.state.$ui.setKey('isAcceptingTxn', true);
|
|
368
449
|
this.client.txns.accept({
|
|
369
450
|
lifetime: this.lifetimeSign(),
|
|
370
451
|
userParams: this.userParamValues(),
|
|
452
|
+
grantedParams: this.grantedParams(),
|
|
453
|
+
revokedParams: this.revokedParams(),
|
|
371
454
|
});
|
|
372
455
|
// Auto-close after 4s if txns are cleared (matches original behaviour).
|
|
373
456
|
this.autoCloseTimeout = setTimeout(() => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
2
|
-
import { render, screen, fireEvent } from '@testing-library/angular';
|
|
2
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/angular';
|
|
3
3
|
import { map } from 'nanostores';
|
|
4
4
|
import { setExtensionInstalled, resetExtension } from '@block-auth.io/core';
|
|
5
5
|
import { TxnsOverviewComponent } from './txns-overview.component';
|
|
@@ -19,12 +19,23 @@ vi.mock('@wagmi/connectors', () => ({
|
|
|
19
19
|
describe('TxnsOverview', () => {
|
|
20
20
|
function makeClient(ws: Record<string, any> = {}, ui: Record<string, any> = {}) {
|
|
21
21
|
return {
|
|
22
|
-
txns: {
|
|
22
|
+
txns: {
|
|
23
|
+
reject: vi.fn(),
|
|
24
|
+
accept: vi.fn(),
|
|
25
|
+
report: vi.fn(),
|
|
26
|
+
dataRequest: vi.fn().mockResolvedValue(null),
|
|
27
|
+
},
|
|
23
28
|
closeModal: vi.fn(),
|
|
24
29
|
state: { $ui: map({ ...ui }), $ws: map({ requests: [], ...ws }) },
|
|
25
30
|
} as any;
|
|
26
31
|
}
|
|
27
32
|
|
|
33
|
+
const acceptButton = () => document.getElementById('btn-txn-accept') as HTMLButtonElement;
|
|
34
|
+
const visibleArrows = () =>
|
|
35
|
+
Array.from(document.querySelectorAll('[aria-hidden="true"]')).filter(
|
|
36
|
+
(el) => el.querySelector('.bal-ba-arrow') && !el.className.includes('bas-invisible'),
|
|
37
|
+
);
|
|
38
|
+
|
|
28
39
|
it('renderiza la vista general de txns (estado vacio sin crashear)', async () => {
|
|
29
40
|
await render(TxnsOverviewComponent, {
|
|
30
41
|
providers: [provideBlockAuthClientForTest(makeClient({}, { isSigning: false }))],
|
|
@@ -102,10 +113,267 @@ describe('TxnsOverview', () => {
|
|
|
102
113
|
],
|
|
103
114
|
});
|
|
104
115
|
await fireEvent.click(screen.getByTestId('txn-pptc-toggle'));
|
|
116
|
+
await waitFor(() => expect(acceptButton().disabled).toBe(false));
|
|
105
117
|
await fireEvent.click(screen.getByText('ACCEPT'));
|
|
106
118
|
expect(screen.getByText('No application key found')).toBeDefined();
|
|
107
119
|
});
|
|
108
120
|
|
|
121
|
+
describe('con datos solicitados', () => {
|
|
122
|
+
const pending = {
|
|
123
|
+
requests: [{ id: 7, pre_params: JSON.stringify({ appName: 'Shop', appKey: 'app', to: '0xabc' }) }],
|
|
124
|
+
};
|
|
125
|
+
const solicitedTitle = () => screen.getByText('Solicited data');
|
|
126
|
+
const solicitedButton = () => solicitedTitle().closest('button') as HTMLButtonElement;
|
|
127
|
+
|
|
128
|
+
it('pide los datos de la txn y exige ceder, rellenar y guardar los obligatorios', async () => {
|
|
129
|
+
const client = makeClient(pending);
|
|
130
|
+
client.txns.dataRequest.mockResolvedValue({
|
|
131
|
+
txnId: 7,
|
|
132
|
+
params: [
|
|
133
|
+
{ slug: 'users-firstname', mode: 'required', status: 'shared', value: 'Ana' },
|
|
134
|
+
{ slug: 'users-email', mode: 'required', status: 'consent', value: 'ana@block-auth.io' },
|
|
135
|
+
{ slug: 'users-phone', mode: 'required', status: 'missing', value: null },
|
|
136
|
+
],
|
|
137
|
+
});
|
|
138
|
+
await render(TxnsOverviewComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
139
|
+
expect(client.txns.dataRequest).toHaveBeenCalledWith(7);
|
|
140
|
+
await fireEvent.click(screen.getByTestId('txn-pptc-toggle'));
|
|
141
|
+
await waitFor(() => expect(solicitedButton().disabled).toBe(false));
|
|
142
|
+
expect(acceptButton().disabled).toBe(true);
|
|
143
|
+
expect(visibleArrows()).toHaveLength(2);
|
|
144
|
+
|
|
145
|
+
await fireEvent.click(solicitedTitle());
|
|
146
|
+
expect(screen.getByText('First Name')).toBeDefined();
|
|
147
|
+
await fireEvent.click(screen.getByLabelText('Share Email'));
|
|
148
|
+
expect(acceptButton().disabled).toBe(true);
|
|
149
|
+
await fireEvent.input(document.querySelector('input[type="tel"]')!, {
|
|
150
|
+
target: { value: ' +34 600 000 000 ' },
|
|
151
|
+
});
|
|
152
|
+
expect(document.getElementById('solicited-data-share-users-phone')?.getAttribute('aria-checked')).toBe('true');
|
|
153
|
+
expect(acceptButton().disabled).toBe(true);
|
|
154
|
+
await fireEvent.click(screen.getByText('Save'));
|
|
155
|
+
expect(acceptButton().disabled).toBe(false);
|
|
156
|
+
expect(visibleArrows()).toHaveLength(0);
|
|
157
|
+
|
|
158
|
+
await fireEvent.click(acceptButton());
|
|
159
|
+
expect(client.txns.accept).toHaveBeenCalledWith({
|
|
160
|
+
lifetime: 3600,
|
|
161
|
+
userParams: { 'users-phone': '+34600000000' },
|
|
162
|
+
grantedParams: ['users-email'],
|
|
163
|
+
revokedParams: [],
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('cierra el modal de datos solicitados con el botón de cerrar', async () => {
|
|
168
|
+
const client = makeClient(pending);
|
|
169
|
+
client.txns.dataRequest.mockResolvedValue({
|
|
170
|
+
txnId: 7,
|
|
171
|
+
params: [{ slug: 'users-email', mode: 'required', status: 'granted', value: 'ana@block-auth.io' }],
|
|
172
|
+
});
|
|
173
|
+
await render(TxnsOverviewComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
174
|
+
await fireEvent.click(screen.getByTestId('txn-pptc-toggle'));
|
|
175
|
+
await waitFor(() => expect(acceptButton().disabled).toBe(false));
|
|
176
|
+
await fireEvent.click(solicitedTitle());
|
|
177
|
+
expect(screen.getByText('Email')).toBeDefined();
|
|
178
|
+
await fireEvent.click(screen.getByRole('button', { name: 'Close' }));
|
|
179
|
+
expect(screen.queryByText('Email')).toBeNull();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('Aceptar del popup de datos lo cierra conservando los cambios sin aceptar el login', async () => {
|
|
183
|
+
const client = makeClient(pending);
|
|
184
|
+
client.txns.dataRequest.mockResolvedValue({
|
|
185
|
+
txnId: 7,
|
|
186
|
+
params: [{ slug: 'users-email', mode: 'optional', status: 'missing', value: null }],
|
|
187
|
+
});
|
|
188
|
+
await render(TxnsOverviewComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
189
|
+
await waitFor(() => expect(solicitedButton().disabled).toBe(false));
|
|
190
|
+
await fireEvent.click(solicitedTitle());
|
|
191
|
+
const slot = document.querySelector('.bal-ba-glass-modal-slot')!;
|
|
192
|
+
expect(slot.classList.contains('bas-inset-y-6')).toBe(true);
|
|
193
|
+
await fireEvent.input(document.querySelector('input[type="email"]')!, {
|
|
194
|
+
target: { value: 'ana@block-auth.io' },
|
|
195
|
+
});
|
|
196
|
+
await fireEvent.click(document.getElementById('solicited-data-accept')!);
|
|
197
|
+
expect(document.querySelector('.bal-ba-glass-modal')).toBeNull();
|
|
198
|
+
expect(client.txns.accept).not.toHaveBeenCalled();
|
|
199
|
+
await fireEvent.click(solicitedTitle());
|
|
200
|
+
expect((document.querySelector('input[type="email"]') as HTMLInputElement).value).toBe(
|
|
201
|
+
'ana@block-auth.io',
|
|
202
|
+
);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('retira un dato opcional cedido al desmarcar Ceder', async () => {
|
|
206
|
+
const client = makeClient(pending);
|
|
207
|
+
client.txns.dataRequest.mockResolvedValue({
|
|
208
|
+
txnId: 7,
|
|
209
|
+
params: [
|
|
210
|
+
{ slug: 'users-email', mode: 'required', status: 'granted', value: 'ana@block-auth.io' },
|
|
211
|
+
{ slug: 'users-phone', mode: 'optional', status: 'granted', value: '+34 600 000 000' },
|
|
212
|
+
],
|
|
213
|
+
});
|
|
214
|
+
await render(TxnsOverviewComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
215
|
+
await fireEvent.click(screen.getByTestId('txn-pptc-toggle'));
|
|
216
|
+
await waitFor(() => expect(acceptButton().disabled).toBe(false));
|
|
217
|
+
expect(visibleArrows()).toHaveLength(0);
|
|
218
|
+
await fireEvent.click(solicitedTitle());
|
|
219
|
+
await fireEvent.click(screen.getByLabelText('Share Phone'));
|
|
220
|
+
await fireEvent.click(acceptButton());
|
|
221
|
+
expect(client.txns.accept).toHaveBeenCalledWith({
|
|
222
|
+
lifetime: 3600,
|
|
223
|
+
userParams: {},
|
|
224
|
+
grantedParams: ['users-email'],
|
|
225
|
+
revokedParams: ['users-phone'],
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('bloquea ACEPTAR y muestra las flechas si se cambia un dato sin decidir', async () => {
|
|
230
|
+
const client = makeClient(pending);
|
|
231
|
+
client.txns.dataRequest.mockResolvedValue({
|
|
232
|
+
txnId: 7,
|
|
233
|
+
params: [{ slug: 'users-email', mode: 'required', status: 'granted', value: 'ana@block-auth.io' }],
|
|
234
|
+
});
|
|
235
|
+
await render(TxnsOverviewComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
236
|
+
await fireEvent.click(screen.getByTestId('txn-pptc-toggle'));
|
|
237
|
+
await waitFor(() => expect(acceptButton().disabled).toBe(false));
|
|
238
|
+
await fireEvent.click(solicitedTitle());
|
|
239
|
+
const emailInput = document.querySelector('input[type="email"]') as HTMLInputElement;
|
|
240
|
+
await fireEvent.input(emailInput, { target: { value: 'otra@block-auth.io' } });
|
|
241
|
+
expect(acceptButton().disabled).toBe(true);
|
|
242
|
+
expect(visibleArrows()).toHaveLength(2);
|
|
243
|
+
await fireEvent.click(screen.getByText('Undo'));
|
|
244
|
+
expect(acceptButton().disabled).toBe(false);
|
|
245
|
+
expect(emailInput.value).toBe('ana@block-auth.io');
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('al guardar vacío un dato cedido deja de cederlo sin enviarlo', async () => {
|
|
249
|
+
const client = makeClient(pending);
|
|
250
|
+
client.txns.dataRequest.mockResolvedValue({
|
|
251
|
+
txnId: 7,
|
|
252
|
+
params: [
|
|
253
|
+
{ slug: 'users-email', mode: 'required', status: 'granted', value: 'ana@block-auth.io' },
|
|
254
|
+
{ slug: 'users-phone', mode: 'optional', status: 'granted', value: '+34 600 000 000' },
|
|
255
|
+
],
|
|
256
|
+
});
|
|
257
|
+
await render(TxnsOverviewComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
258
|
+
await fireEvent.click(screen.getByTestId('txn-pptc-toggle'));
|
|
259
|
+
await waitFor(() => expect(acceptButton().disabled).toBe(false));
|
|
260
|
+
await fireEvent.click(solicitedTitle());
|
|
261
|
+
expect(
|
|
262
|
+
document
|
|
263
|
+
.querySelector('.bal-ba-glass-modal')
|
|
264
|
+
?.parentElement?.classList.contains('bal-ba-glass-modal-slot'),
|
|
265
|
+
).toBe(true);
|
|
266
|
+
await fireEvent.click(screen.getByLabelText('Clear field Phone'));
|
|
267
|
+
const phoneSwitch = document.getElementById('solicited-data-share-users-phone') as HTMLButtonElement;
|
|
268
|
+
expect(phoneSwitch.disabled).toBe(true);
|
|
269
|
+
expect(phoneSwitch.getAttribute('aria-checked')).toBe('false');
|
|
270
|
+
await fireEvent.click(screen.getByText('Save'));
|
|
271
|
+
expect(screen.getByText('This data will not be shared')).toBeDefined();
|
|
272
|
+
expect(acceptButton().disabled).toBe(false);
|
|
273
|
+
await fireEvent.click(acceptButton());
|
|
274
|
+
expect(client.txns.accept).toHaveBeenCalledWith({
|
|
275
|
+
lifetime: 3600,
|
|
276
|
+
userParams: {},
|
|
277
|
+
grantedParams: ['users-email'],
|
|
278
|
+
revokedParams: ['users-phone'],
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('un obligatorio guardado vacío queda pendiente y bloquea ACEPTAR', async () => {
|
|
283
|
+
const client = makeClient(pending);
|
|
284
|
+
client.txns.dataRequest.mockResolvedValue({
|
|
285
|
+
txnId: 7,
|
|
286
|
+
params: [{ slug: 'users-email', mode: 'required', status: 'granted', value: 'ana@block-auth.io' }],
|
|
287
|
+
});
|
|
288
|
+
await render(TxnsOverviewComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
289
|
+
await fireEvent.click(screen.getByTestId('txn-pptc-toggle'));
|
|
290
|
+
await waitFor(() => expect(acceptButton().disabled).toBe(false));
|
|
291
|
+
await fireEvent.click(solicitedTitle());
|
|
292
|
+
await fireEvent.click(screen.getByLabelText('Clear field Email'));
|
|
293
|
+
await fireEvent.click(screen.getByText('Save'));
|
|
294
|
+
expect(acceptButton().disabled).toBe(true);
|
|
295
|
+
expect(
|
|
296
|
+
document
|
|
297
|
+
.getElementById('solicited-data-users-email')
|
|
298
|
+
?.closest('.bal-ba-glass-field')
|
|
299
|
+
?.classList.contains('bal-ba-glass-field-pending'),
|
|
300
|
+
).toBe(true);
|
|
301
|
+
await fireEvent.input(document.querySelector('input[type="email"]')!, {
|
|
302
|
+
target: { value: 'otra@block-auth.io' },
|
|
303
|
+
});
|
|
304
|
+
expect(document.getElementById('solicited-data-share-users-email')?.getAttribute('aria-checked')).toBe('true');
|
|
305
|
+
await fireEvent.click(screen.getByText('Save'));
|
|
306
|
+
expect(acceptButton().disabled).toBe(false);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('deja "Datos solicitados" desactivado cuando no hay nada que mostrar', async () => {
|
|
310
|
+
const client = makeClient(pending, {
|
|
311
|
+
uiConfig: { config: { params: { 'users-email': { mode: 'required' } } } },
|
|
312
|
+
});
|
|
313
|
+
client.txns.dataRequest.mockResolvedValue({ txnId: 7, params: [] });
|
|
314
|
+
await render(TxnsOverviewComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
315
|
+
await fireEvent.click(screen.getByTestId('txn-pptc-toggle'));
|
|
316
|
+
await waitFor(() => expect(acceptButton().disabled).toBe(false));
|
|
317
|
+
expect(solicitedButton().disabled).toBe(true);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('no bloquea ACEPTAR mientras espera los datos si la app no pide ninguno', async () => {
|
|
321
|
+
const client = makeClient(pending);
|
|
322
|
+
client.txns.dataRequest.mockReturnValue(new Promise(() => undefined));
|
|
323
|
+
await render(TxnsOverviewComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
324
|
+
await fireEvent.click(screen.getByTestId('txn-pptc-toggle'));
|
|
325
|
+
await waitFor(() => expect(acceptButton().disabled).toBe(false));
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('sin respuesta del servidor mantiene el cálculo con uiConfig', async () => {
|
|
329
|
+
const client = makeClient(pending, {
|
|
330
|
+
uiConfig: { config: { params: { 'users-email': { mode: 'required' } } } },
|
|
331
|
+
});
|
|
332
|
+
await render(TxnsOverviewComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
333
|
+
await fireEvent.click(screen.getByTestId('txn-pptc-toggle'));
|
|
334
|
+
await waitFor(() => expect(solicitedButton().disabled).toBe(false));
|
|
335
|
+
expect(acceptButton().disabled).toBe(true);
|
|
336
|
+
await fireEvent.click(solicitedTitle());
|
|
337
|
+
await fireEvent.input(document.querySelector('input[type="email"]')!, {
|
|
338
|
+
target: { value: 'ana@block-auth.io' },
|
|
339
|
+
});
|
|
340
|
+
await waitFor(() => expect(acceptButton().disabled).toBe(false));
|
|
341
|
+
await fireEvent.click(acceptButton());
|
|
342
|
+
expect(client.txns.accept).toHaveBeenCalledWith({
|
|
343
|
+
lifetime: 3600,
|
|
344
|
+
userParams: { 'users-email': 'ana@block-auth.io' },
|
|
345
|
+
grantedParams: [],
|
|
346
|
+
revokedParams: [],
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('si el servidor falla mantiene el cálculo con uiConfig', async () => {
|
|
351
|
+
const client = makeClient(pending, {
|
|
352
|
+
uiConfig: { config: { params: { 'users-email': { mode: 'optional' } } } },
|
|
353
|
+
});
|
|
354
|
+
client.txns.dataRequest.mockRejectedValue(new Error('boom'));
|
|
355
|
+
await render(TxnsOverviewComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
356
|
+
await fireEvent.click(screen.getByTestId('txn-pptc-toggle'));
|
|
357
|
+
await waitFor(() => expect(acceptButton().disabled).toBe(false));
|
|
358
|
+
expect(solicitedButton().disabled).toBe(false);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('muestra el error del servidor en los datos solicitados y permite corregir', async () => {
|
|
362
|
+
const client = makeClient(pending);
|
|
363
|
+
client.txns.dataRequest.mockResolvedValue({ txnId: 7, params: [] });
|
|
364
|
+
const { fixture } = await render(TxnsOverviewComponent, {
|
|
365
|
+
providers: [provideBlockAuthClientForTest(client)],
|
|
366
|
+
});
|
|
367
|
+
await waitFor(() => expect(client.txns.dataRequest).toHaveBeenCalledTimes(1));
|
|
368
|
+
client.state.$ui.setKey('acceptTxnError', 'Required data not authorized');
|
|
369
|
+
fixture.detectChanges();
|
|
370
|
+
await waitFor(() =>
|
|
371
|
+
expect(screen.getByRole('alert').textContent).toContain('required data is missing or invalid'),
|
|
372
|
+
);
|
|
373
|
+
await waitFor(() => expect(client.txns.dataRequest).toHaveBeenCalledTimes(2));
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
|
|
109
377
|
describe('con la verificación OTP caducada', () => {
|
|
110
378
|
const pending = { requests: [{ id: 7, pre_params: JSON.stringify({ appName: 'Shop' }) }] };
|
|
111
379
|
afterEach(() => resetExtension());
|