@block-auth.io/angular 0.1.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 +37 -0
- package/.turbo/turbo-lint.log +4 -0
- package/.turbo/turbo-test.log +82 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/CHANGELOG.md +10 -0
- package/ng-package.json +6 -0
- package/package.json +50 -0
- package/scripts/css-to-ts.mjs +14 -0
- package/src/lib/__tests__/integration.test.ts +45 -0
- package/src/lib/block-auth.service.test.ts +69 -0
- package/src/lib/block-auth.service.ts +81 -0
- package/src/lib/components/btns/btn-base-provider.component.test.ts +22 -0
- package/src/lib/components/btns/btn-base-provider.component.ts +55 -0
- package/src/lib/components/btns/btn-block-auth.component.test.ts +79 -0
- package/src/lib/components/btns/btn-block-auth.component.ts +104 -0
- package/src/lib/components/btns/btn-gmail.component.test.ts +16 -0
- package/src/lib/components/btns/btn-gmail.component.ts +41 -0
- package/src/lib/components/btns/btn-google-base.component.test.ts +16 -0
- package/src/lib/components/btns/btn-google-base.component.ts +73 -0
- package/src/lib/components/btns/btn-metamask.component.test.ts +20 -0
- package/src/lib/components/btns/btn-metamask.component.ts +98 -0
- package/src/lib/components/btns/btn-microsoft.component.test.ts +16 -0
- package/src/lib/components/btns/btn-microsoft.component.ts +77 -0
- package/src/lib/components/btns/btn-phantom.component.test.ts +20 -0
- package/src/lib/components/btns/btn-phantom.component.ts +93 -0
- package/src/lib/components/btns/btn-signin.component.test.ts +55 -0
- package/src/lib/components/btns/btn-signin.component.ts +108 -0
- package/src/lib/components/btns/btn-wallet-connect.component.test.ts +20 -0
- package/src/lib/components/btns/btn-wallet-connect.component.ts +109 -0
- package/src/lib/components/btns/index.ts +11 -0
- package/src/lib/components/contents/content-signin.component.test.ts +33 -0
- package/src/lib/components/contents/content-signin.component.ts +68 -0
- package/src/lib/components/contents/content-signup.component.test.ts +78 -0
- package/src/lib/components/contents/content-signup.component.ts +225 -0
- package/src/lib/components/dds/change-account.component.test.ts +66 -0
- package/src/lib/components/dds/change-account.component.ts +112 -0
- package/src/lib/components/dds/dropdown-profile-btn.component.test.ts +66 -0
- package/src/lib/components/dds/dropdown-profile-btn.component.ts +185 -0
- package/src/lib/components/dds/dropdown-profile-item.component.test.ts +75 -0
- package/src/lib/components/dds/dropdown-profile-item.component.ts +74 -0
- package/src/lib/components/dds/dropdown-profile.component.test.ts +98 -0
- package/src/lib/components/dds/dropdown-profile.component.ts +258 -0
- package/src/lib/components/flow-block-auth.component.test.ts +93 -0
- package/src/lib/components/flow-block-auth.component.ts +226 -0
- package/src/lib/components/modals/modal-overlay.component.test.ts +81 -0
- package/src/lib/components/modals/modal-overlay.component.ts +43 -0
- package/src/lib/components/modals/modal-restoration-expired.component.test.ts +71 -0
- package/src/lib/components/modals/modal-restoration-expired.component.ts +83 -0
- package/src/lib/components/modals/modal-restore.component.test.ts +74 -0
- package/src/lib/components/modals/modal-restore.component.ts +104 -0
- package/src/lib/components/modals/modal-signin.component.test.ts +66 -0
- package/src/lib/components/modals/modal-signin.component.ts +44 -0
- package/src/lib/components/modals/modal-signup.component.test.ts +56 -0
- package/src/lib/components/modals/modal-signup.component.ts +40 -0
- package/src/lib/components/sections/footer-section.component.test.ts +19 -0
- package/src/lib/components/sections/footer-section.component.ts +49 -0
- package/src/lib/components/sections/internals/providers-buttons.component.test.ts +27 -0
- package/src/lib/components/sections/internals/providers-buttons.component.ts +290 -0
- package/src/lib/components/sections/internals/providers-filters.component.test.ts +25 -0
- package/src/lib/components/sections/internals/providers-filters.component.ts +139 -0
- package/src/lib/components/sections/internals/selected-button.component.test.ts +36 -0
- package/src/lib/components/sections/internals/selected-button.component.ts +92 -0
- package/src/lib/components/sections/login-section.component.test.ts +49 -0
- package/src/lib/components/sections/login-section.component.ts +120 -0
- package/src/lib/components/sections/providers-section.component.test.ts +22 -0
- package/src/lib/components/sections/providers-section.component.ts +187 -0
- package/src/lib/components/txns/internals/permissions-required-modal.component.test.ts +14 -0
- package/src/lib/components/txns/internals/permissions-required-modal.component.ts +35 -0
- package/src/lib/components/txns/internals/solicited-data-modal.component.test.ts +72 -0
- package/src/lib/components/txns/internals/solicited-data-modal.component.ts +187 -0
- package/src/lib/components/txns/internals/txn-error.component.test.ts +100 -0
- package/src/lib/components/txns/internals/txn-error.component.ts +121 -0
- package/src/lib/components/txns/internals/txn-type.component.test.ts +52 -0
- package/src/lib/components/txns/internals/txn-type.component.ts +51 -0
- package/src/lib/components/txns/internals/txn.component.test.ts +188 -0
- package/src/lib/components/txns/internals/txn.component.ts +298 -0
- package/src/lib/components/txns/txns-overview.component.test.ts +107 -0
- package/src/lib/components/txns/txns-overview.component.ts +229 -0
- package/src/lib/icons/ai-outline-disconnect.component.ts +21 -0
- package/src/lib/icons/bi-logo-gmail.component.ts +21 -0
- package/src/lib/icons/cg-profile.component.ts +31 -0
- package/src/lib/icons/check-icon.component.ts +27 -0
- package/src/lib/icons/ci-clock2.component.ts +31 -0
- package/src/lib/icons/fa-arrow-right.component.ts +21 -0
- package/src/lib/icons/fa-times.component.ts +21 -0
- package/src/lib/icons/fa-undo.component.ts +21 -0
- package/src/lib/icons/icons.test.ts +25 -0
- package/src/lib/icons/index.ts +22 -0
- package/src/lib/icons/io-ios-arrow-back.component.ts +21 -0
- package/src/lib/icons/io-ios-arrow-forward.component.ts +21 -0
- package/src/lib/icons/io-md-arrow-back.component.ts +19 -0
- package/src/lib/icons/io-md-arrow-forward.component.ts +19 -0
- package/src/lib/icons/pi-warning-fill.component.ts +21 -0
- package/src/lib/icons/tb-arrows-exchange2.component.ts +30 -0
- package/src/lib/icons/tb-copy-check-filled.component.ts +21 -0
- package/src/lib/icons/tb-copy.component.ts +34 -0
- package/src/lib/icons/ti-vendor-microsoft.component.ts +21 -0
- package/src/lib/inject-store.test.ts +26 -0
- package/src/lib/inject-store.ts +16 -0
- package/src/lib/primitives/arrow-icon.component.test.ts +12 -0
- package/src/lib/primitives/arrow-icon.component.ts +38 -0
- package/src/lib/primitives/btn-arrow-dir.component.test.ts +22 -0
- package/src/lib/primitives/btn-arrow-dir.component.ts +65 -0
- package/src/lib/primitives/btn-base.component.test.ts +38 -0
- package/src/lib/primitives/btn-base.component.ts +140 -0
- package/src/lib/primitives/divider-base.component.test.ts +12 -0
- package/src/lib/primitives/divider-base.component.ts +74 -0
- package/src/lib/primitives/dropdown-arrow.component.test.ts +12 -0
- package/src/lib/primitives/dropdown-arrow.component.ts +46 -0
- package/src/lib/primitives/dropdown-base.component.test.ts +49 -0
- package/src/lib/primitives/dropdown-base.component.ts +329 -0
- package/src/lib/primitives/helpers.ts +151 -0
- package/src/lib/primitives/horizontal-loader.component.test.ts +19 -0
- package/src/lib/primitives/horizontal-loader.component.ts +70 -0
- package/src/lib/primitives/icon-base.component.test.ts +19 -0
- package/src/lib/primitives/icon-base.component.ts +58 -0
- package/src/lib/primitives/img-loader.component.test.ts +12 -0
- package/src/lib/primitives/img-loader.component.ts +118 -0
- package/src/lib/primitives/index.ts +29 -0
- package/src/lib/primitives/input-base.component.test.ts +20 -0
- package/src/lib/primitives/input-base.component.ts +210 -0
- package/src/lib/primitives/lifetime-slider-base.component.test.ts +88 -0
- package/src/lib/primitives/lifetime-slider-base.component.ts +75 -0
- package/src/lib/primitives/oval-spinner.component.test.ts +9 -0
- package/src/lib/primitives/oval-spinner.component.ts +158 -0
- package/src/lib/primitives/pickers/dropdown-picker-animated-base.component.ts +220 -0
- package/src/lib/primitives/pickers/picker-button.component.ts +74 -0
- package/src/lib/primitives/pickers/time-picker-dropdown-selector.component.ts +188 -0
- package/src/lib/primitives/pickers/time-picker.component.ts +124 -0
- package/src/lib/primitives/pickers/time-unit-selector.component.ts +82 -0
- package/src/lib/primitives/progressbar.component.test.ts +18 -0
- package/src/lib/primitives/progressbar.component.ts +93 -0
- package/src/lib/primitives/providersStyles.ts +268 -0
- package/src/lib/primitives/slider-base.component.ts +56 -0
- package/src/lib/provide-block-auth.test.ts +43 -0
- package/src/lib/provide-block-auth.ts +43 -0
- package/src/lib/runtime/block-auth-runtime.directive.test.ts +89 -0
- package/src/lib/runtime/block-auth-runtime.directive.ts +68 -0
- package/src/lib/teleport-body.directive.test.ts +29 -0
- package/src/lib/teleport-body.directive.ts +21 -0
- package/src/lib/testing.ts +15 -0
- package/src/lib/transitions.css +23 -0
- package/src/public-api.ts +85 -0
- package/src/vitest.setup.ts +8 -0
- package/tailwind.config.cjs +6 -0
- package/tsconfig.json +15 -0
- package/tsconfig.lib.json +9 -0
- package/vite.config.mts +44 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { Component } from '@angular/core';
|
|
3
|
+
import { render, screen, fireEvent } from '@testing-library/angular';
|
|
4
|
+
import { map } from 'nanostores';
|
|
5
|
+
import { TxnComponent } from './txn.component';
|
|
6
|
+
import { provideBlockAuthClientForTest } from '../../../testing';
|
|
7
|
+
|
|
8
|
+
vi.mock('@wagmi/core', () => ({
|
|
9
|
+
createConfig: vi.fn(() => ({ connectors: [] })),
|
|
10
|
+
connect: vi.fn(), disconnect: vi.fn(), getAccount: vi.fn(() => ({})),
|
|
11
|
+
signMessage: vi.fn(), watchAccount: vi.fn(() => () => undefined), http: vi.fn(),
|
|
12
|
+
}));
|
|
13
|
+
vi.mock('@wagmi/connectors', () => ({
|
|
14
|
+
injected: vi.fn(() => ({ id: 'injected' })), walletConnect: vi.fn(() => ({ id: 'walletConnect' })),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
// Port de packages/react/src/components/txns/internals/Txn.test.tsx +
|
|
18
|
+
// TxnActions.behavior.test.tsx (mapa: packages/vue/.../Txn.test.ts).
|
|
19
|
+
//
|
|
20
|
+
// El toggle PPTC se ejerce vía `data-testid="txn-pptc-toggle"` (el div exterior,
|
|
21
|
+
// único dueño del handler que alterna `pptcAccepted`), no vía click directo en
|
|
22
|
+
// `checkbox-animated` (que es `bas-hidden`).
|
|
23
|
+
describe('Txn', () => {
|
|
24
|
+
function makeClient(overrides: Record<string, any> = {}) {
|
|
25
|
+
return {
|
|
26
|
+
txns: { reject: vi.fn(), accept: vi.fn(), report: vi.fn() },
|
|
27
|
+
closeModal: vi.fn(),
|
|
28
|
+
state: { $ui: map({}), $ws: map({ requests: [] }) },
|
|
29
|
+
...overrides,
|
|
30
|
+
} as any;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
it('renderiza el cuerpo de la txn con aceptar/rechazar', async () => {
|
|
34
|
+
await render(TxnComponent, {
|
|
35
|
+
inputs: { hasSolicitedData: true },
|
|
36
|
+
providers: [provideBlockAuthClientForTest(makeClient())],
|
|
37
|
+
});
|
|
38
|
+
expect(screen.getByText('Permissions required')).toBeDefined();
|
|
39
|
+
expect(screen.getByText('ACCEPT')).toBeDefined();
|
|
40
|
+
expect(screen.getByText('REJECT')).toBeDefined();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('ACCEPT esta deshabilitado hasta aceptar PPTC (y allRequiredFilled)', async () => {
|
|
44
|
+
await render(TxnComponent, { providers: [provideBlockAuthClientForTest(makeClient())] });
|
|
45
|
+
const acceptBtn = screen.getByText('ACCEPT').closest('button') as HTMLButtonElement;
|
|
46
|
+
const checkbox = screen.getByTestId('checkbox-animated') as HTMLInputElement;
|
|
47
|
+
expect(acceptBtn.disabled).toBe(true);
|
|
48
|
+
expect(checkbox.checked).toBe(false);
|
|
49
|
+
|
|
50
|
+
await fireEvent.click(screen.getByTestId('txn-pptc-toggle'));
|
|
51
|
+
expect(checkbox.checked).toBe(true);
|
|
52
|
+
expect(acceptBtn.disabled).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('ACCEPT sigue deshabilitado si allRequiredFilled=false aunque PPTC este aceptado', async () => {
|
|
56
|
+
await render(TxnComponent, {
|
|
57
|
+
inputs: { allRequiredFilled: false },
|
|
58
|
+
providers: [provideBlockAuthClientForTest(makeClient())],
|
|
59
|
+
});
|
|
60
|
+
await fireEvent.click(screen.getByTestId('txn-pptc-toggle'));
|
|
61
|
+
const acceptBtn = screen.getByText('ACCEPT').closest('button') as HTMLButtonElement;
|
|
62
|
+
expect(acceptBtn.disabled).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('REJECT llama client.txns.reject() y luego closeModal() (no closeTxns)', async () => {
|
|
66
|
+
const client = makeClient();
|
|
67
|
+
const closeTxns = vi.fn();
|
|
68
|
+
@Component({
|
|
69
|
+
standalone: true,
|
|
70
|
+
imports: [TxnComponent],
|
|
71
|
+
template: `<ba-txn [closeTxns]="c" />`,
|
|
72
|
+
})
|
|
73
|
+
class Host {
|
|
74
|
+
c = closeTxns;
|
|
75
|
+
}
|
|
76
|
+
await render(Host, { providers: [provideBlockAuthClientForTest(client)] });
|
|
77
|
+
await fireEvent.click(screen.getByText('REJECT'));
|
|
78
|
+
|
|
79
|
+
expect(client.txns.reject).toHaveBeenCalledOnce();
|
|
80
|
+
expect(client.closeModal).toHaveBeenCalledOnce();
|
|
81
|
+
expect(closeTxns).not.toHaveBeenCalled();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('ACCEPT llama a addError("no_app_key_found") cuando falta appKey', async () => {
|
|
85
|
+
const client = makeClient();
|
|
86
|
+
const addError = vi.fn();
|
|
87
|
+
@Component({
|
|
88
|
+
standalone: true,
|
|
89
|
+
imports: [TxnComponent],
|
|
90
|
+
template: `<ba-txn [selectedTxnPreParams]="{}" [addError]="e" />`,
|
|
91
|
+
})
|
|
92
|
+
class Host {
|
|
93
|
+
e = addError;
|
|
94
|
+
}
|
|
95
|
+
await render(Host, { providers: [provideBlockAuthClientForTest(client)] });
|
|
96
|
+
await fireEvent.click(screen.getByTestId('txn-pptc-toggle'));
|
|
97
|
+
await fireEvent.click(screen.getByText('ACCEPT'));
|
|
98
|
+
expect(addError).toHaveBeenCalledWith('no_app_key_found');
|
|
99
|
+
expect(client.txns.accept).not.toHaveBeenCalled();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('ACCEPT llama a addError("no_contract_address_found") cuando falta to', async () => {
|
|
103
|
+
const client = makeClient();
|
|
104
|
+
const addError = vi.fn();
|
|
105
|
+
@Component({
|
|
106
|
+
standalone: true,
|
|
107
|
+
imports: [TxnComponent],
|
|
108
|
+
template: `<ba-txn [selectedTxnPreParams]="{ appKey: 'k' }" [addError]="e" />`,
|
|
109
|
+
})
|
|
110
|
+
class Host {
|
|
111
|
+
e = addError;
|
|
112
|
+
}
|
|
113
|
+
await render(Host, { providers: [provideBlockAuthClientForTest(client)] });
|
|
114
|
+
await fireEvent.click(screen.getByTestId('txn-pptc-toggle'));
|
|
115
|
+
await fireEvent.click(screen.getByText('ACCEPT'));
|
|
116
|
+
expect(addError).toHaveBeenCalledWith('no_contract_address_found');
|
|
117
|
+
expect(client.txns.accept).not.toHaveBeenCalled();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('ACCEPT llama a client.txns.accept con lifetime/userParams cuando los preParams son validos', async () => {
|
|
121
|
+
const client = makeClient();
|
|
122
|
+
@Component({
|
|
123
|
+
standalone: true,
|
|
124
|
+
imports: [TxnComponent],
|
|
125
|
+
template: `<ba-txn [selectedTxnPreParams]="{ appKey: 'k', to: '0xabc' }" [userParamValues]="{ 'users-email': 'a@b.com' }" />`,
|
|
126
|
+
})
|
|
127
|
+
class Host {}
|
|
128
|
+
await render(Host, { providers: [provideBlockAuthClientForTest(client)] });
|
|
129
|
+
await fireEvent.click(screen.getByTestId('txn-pptc-toggle'));
|
|
130
|
+
await fireEvent.click(screen.getByText('ACCEPT'));
|
|
131
|
+
|
|
132
|
+
expect(client.txns.accept).toHaveBeenCalledWith({
|
|
133
|
+
lifetime: 3600,
|
|
134
|
+
userParams: { 'users-email': 'a@b.com' },
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('emite permissionsRequiredClicked/solicitedDataClicked (solo si hasSolicitedData)', async () => {
|
|
139
|
+
const onPerm = vi.fn();
|
|
140
|
+
const onSol = vi.fn();
|
|
141
|
+
@Component({
|
|
142
|
+
standalone: true,
|
|
143
|
+
imports: [TxnComponent],
|
|
144
|
+
template: `<ba-txn [hasSolicitedData]="true" (permissionsRequiredClicked)="p()" (solicitedDataClicked)="s()" />`,
|
|
145
|
+
})
|
|
146
|
+
class Host {
|
|
147
|
+
p = onPerm;
|
|
148
|
+
s = onSol;
|
|
149
|
+
}
|
|
150
|
+
await render(Host, { providers: [provideBlockAuthClientForTest(makeClient())] });
|
|
151
|
+
await fireEvent.click(screen.getByText('Permissions required'));
|
|
152
|
+
await fireEvent.click(screen.getByText('Solicited data'));
|
|
153
|
+
expect(onPerm).toHaveBeenCalledOnce();
|
|
154
|
+
expect(onSol).toHaveBeenCalledOnce();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('el boton de configurar sesion revela el LifetimeSliderBase (slider + TimePicker)', async () => {
|
|
158
|
+
const { container } = await render(TxnComponent, {
|
|
159
|
+
providers: [provideBlockAuthClientForTest(makeClient())],
|
|
160
|
+
});
|
|
161
|
+
// Antes del click no hay ni slider ni TimePicker.
|
|
162
|
+
expect(container.querySelector('#txn-lifetime-slider-slider')).toBeNull();
|
|
163
|
+
await fireEvent.click(screen.getByText('I want to configure my session time'));
|
|
164
|
+
// El slider estilado (SliderBase) y el input del TimePicker aparecen.
|
|
165
|
+
const slider = container.querySelector('#txn-lifetime-slider-slider') as HTMLInputElement;
|
|
166
|
+
expect(slider).not.toBeNull();
|
|
167
|
+
expect(slider.type).toBe('range');
|
|
168
|
+
const timeInput = container.querySelector(
|
|
169
|
+
'#txn-lifetime-slider-time-picker-time-input',
|
|
170
|
+
) as HTMLInputElement;
|
|
171
|
+
expect(timeInput).not.toBeNull();
|
|
172
|
+
// Refleja el lifetime por defecto (3600s → 01:00:00) como HH:MM:SS.
|
|
173
|
+
expect(timeInput.value).toBe('01:00:00');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('mover el slider actualiza el valor HH:MM:SS del TimePicker', async () => {
|
|
177
|
+
const { container } = await render(TxnComponent, {
|
|
178
|
+
providers: [provideBlockAuthClientForTest(makeClient())],
|
|
179
|
+
});
|
|
180
|
+
await fireEvent.click(screen.getByText('I want to configure my session time'));
|
|
181
|
+
const slider = container.querySelector('#txn-lifetime-slider-slider') as HTMLInputElement;
|
|
182
|
+
await fireEvent.input(slider, { target: { value: '7200' } }); // 02:00:00
|
|
183
|
+
const timeInput = container.querySelector(
|
|
184
|
+
'#txn-lifetime-slider-time-picker-time-input',
|
|
185
|
+
) as HTMLInputElement;
|
|
186
|
+
expect(timeInput.value).toBe('02:00:00');
|
|
187
|
+
});
|
|
188
|
+
});
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChangeDetectionStrategy,
|
|
3
|
+
Component,
|
|
4
|
+
DestroyRef,
|
|
5
|
+
computed,
|
|
6
|
+
inject,
|
|
7
|
+
input,
|
|
8
|
+
output,
|
|
9
|
+
signal,
|
|
10
|
+
} from '@angular/core';
|
|
11
|
+
import { injectBlockAuthClient } from '../../../provide-block-auth';
|
|
12
|
+
import { ArrowIconComponent } from '../../../primitives/arrow-icon.component';
|
|
13
|
+
import { LifetimeSliderBaseComponent } from '../../../primitives/lifetime-slider-base.component';
|
|
14
|
+
import { getTimeBySec } from '../../../primitives/helpers';
|
|
15
|
+
|
|
16
|
+
// Con `showLifetime` se revela el `<ba-lifetime-slider-base>` completo (slider +
|
|
17
|
+
// TimePicker HH:MM:SS con dropdown animado), igual que react/vue. El slider y el
|
|
18
|
+
// picker reportan en segundos; `onLifetimeValueChanged` mantiene ambas vistas
|
|
19
|
+
// (`lifetimeSign` número + `lifetimeTimeValue` HH:MM:SS) en sync. El valor
|
|
20
|
+
// elegido (segundos) es lo que ACCEPT envía como `lifetime`. El botón que revela
|
|
21
|
+
// el control (`id="btn-configure-session-time"`) se mantiene idéntico.
|
|
22
|
+
//
|
|
23
|
+
// `onPermissionsRequiredClicked`/`onSolicitedDataClicked` (callback props) ->
|
|
24
|
+
// outputs `permissionsRequiredClicked`/`solicitedDataClicked`. `closeTxns`/
|
|
25
|
+
// `addError` se mantienen como INPUTS DE FUNCIÓN (invocados imperativamente).
|
|
26
|
+
//
|
|
27
|
+
// `close()` NO llama a `closeTxns`: reject ya emite `cancelled` por sí mismo y
|
|
28
|
+
// accept no debe deshacerse (encadenar `closeTxns()` cancelaría dos veces en
|
|
29
|
+
// reject y cancelaría una txn ya aceptada). El `setTimeout` de autocierre de 4s
|
|
30
|
+
// se limpia vía `DestroyRef`. `data-testid="txn-pptc-toggle"` en el div exterior
|
|
31
|
+
// del check PPTC (solo testability, igual que el port Vue).
|
|
32
|
+
interface TxnPreParams {
|
|
33
|
+
appKey?: string;
|
|
34
|
+
to?: string;
|
|
35
|
+
lifetime?: { value?: number; min?: number; max?: number };
|
|
36
|
+
[key: string]: unknown;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@Component({
|
|
40
|
+
selector: 'ba-txn',
|
|
41
|
+
standalone: true,
|
|
42
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
43
|
+
imports: [ArrowIconComponent, LifetimeSliderBaseComponent],
|
|
44
|
+
template: `
|
|
45
|
+
<div class="bas-flex bas-h-full bas-w-full bas-flex-col bas-items-center bas-justify-between">
|
|
46
|
+
<div class="bal-ba-transition-3 bas-flex bas-w-full bas-flex-col bas-gap-8 bas-pt-2">
|
|
47
|
+
<div
|
|
48
|
+
class="bal-ba-outline-none bas-grid bas-h-[30px] bas-cursor-pointer bas-grid-cols-[65px_auto_65px] bas-items-center bas-justify-center bas-gap-2"
|
|
49
|
+
(click)="permissionsRequiredClicked.emit()"
|
|
50
|
+
>
|
|
51
|
+
<ba-arrow-icon direction="right" />
|
|
52
|
+
<div class="bas-flex bas-h-full bas-w-full bas-items-end">
|
|
53
|
+
<p class="bas-m-0 bas-text-center bas-text-lg bas-font-medium bas-text-[var(--bal-primary-900)]">
|
|
54
|
+
{{ permissionsRequiredTxt() }}
|
|
55
|
+
</p>
|
|
56
|
+
</div>
|
|
57
|
+
<ba-arrow-icon direction="left" />
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div
|
|
61
|
+
[class]="
|
|
62
|
+
'bas-grid bas-h-[30px] bas-grid-cols-[65px_auto_65px] bas-items-center bas-justify-center bas-gap-2 ' +
|
|
63
|
+
(hasSolicitedData() ? 'bas-cursor-pointer' : 'bas-cursor-not-allowed bas-opacity-40')
|
|
64
|
+
"
|
|
65
|
+
(click)="onSolicitedDataClick()"
|
|
66
|
+
>
|
|
67
|
+
<ba-arrow-icon direction="right" />
|
|
68
|
+
<div class="bas-flex bas-h-full bas-w-full bas-items-end bas-justify-center">
|
|
69
|
+
<p
|
|
70
|
+
[class]="
|
|
71
|
+
'bas-m-0 bas-text-center bas-text-lg bas-transition-colors ' +
|
|
72
|
+
(hasSolicitedData()
|
|
73
|
+
? 'bas-font-medium bas-text-[var(--bal-primary-900)]'
|
|
74
|
+
: 'bas-text-slate-400')
|
|
75
|
+
"
|
|
76
|
+
>
|
|
77
|
+
{{ solicitedDataTxt() }}
|
|
78
|
+
</p>
|
|
79
|
+
</div>
|
|
80
|
+
<ba-arrow-icon direction="left" />
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
@if (!showLifetime()) {
|
|
85
|
+
<button
|
|
86
|
+
type="button"
|
|
87
|
+
id="btn-configure-session-time"
|
|
88
|
+
class="bal-ba-transition-2 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]"
|
|
89
|
+
(click)="showLifetime.set(true)"
|
|
90
|
+
>
|
|
91
|
+
<p class="bas-m-0 bas-text-center bas-text-base">{{ configureSessionTimeTxt() }}</p>
|
|
92
|
+
<svg class="bas-h-5 bas-w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
|
|
93
|
+
<circle cx="12" cy="12" r="9" />
|
|
94
|
+
<path d="M12 7v5l3 2" stroke-linecap="round" stroke-linejoin="round" />
|
|
95
|
+
</svg>
|
|
96
|
+
</button>
|
|
97
|
+
} @else {
|
|
98
|
+
<ba-lifetime-slider-base
|
|
99
|
+
id="txn-lifetime-slider"
|
|
100
|
+
[sliderValue]="lifetimeSign()"
|
|
101
|
+
[step]="1"
|
|
102
|
+
[min]="minLifetime()"
|
|
103
|
+
[max]="maxLifetime()"
|
|
104
|
+
[timeInputValue]="lifetimeTimeValue()"
|
|
105
|
+
[isOpen]="isTimePickerOpen()"
|
|
106
|
+
[isTimePickerInsideModal]="true"
|
|
107
|
+
modalId="txns-overview"
|
|
108
|
+
timePickerDropdownDirection="up"
|
|
109
|
+
classNames="bas-grid bas-h-auto bas-w-full bas-items-center bas-gap-3 bas-grid-cols-[152px_1fr]"
|
|
110
|
+
timePickerContainerClassNames="bas-flex bas-w-full bas-items-center bas-justify-center"
|
|
111
|
+
(sliderValueChanged)="onLifetimeValueChanged($event)"
|
|
112
|
+
(timeChanged)="onLifetimeValueChanged($event)"
|
|
113
|
+
(isOpenChange)="isTimePickerOpen.set($event)"
|
|
114
|
+
(toggleTimerDropdown)="toggleTimerDropdown()"
|
|
115
|
+
/>
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
<div class="bal-ba-transition-3 bas-flex bas-w-full bas-flex-col bas-gap-8 bas-pb-4">
|
|
119
|
+
<div
|
|
120
|
+
data-testid="txn-pptc-toggle"
|
|
121
|
+
[class]="
|
|
122
|
+
'bas-group bal-ba-transition-2 bas-box-border bas-flex bas-h-fit bas-w-full bas-flex-row bas-items-center bas-gap-3 bas-rounded-md bas-border-2 bas-border-solid bas-border-[var(--bal-primary-100)] bas-p-1 bas-pl-2 bal-ba-ring-base bal-ba-hover-ring-2 ' +
|
|
123
|
+
(pptcAccepted()
|
|
124
|
+
? 'bas-border-[var(--bal-primary-300)] bal-ba-hover-ring-primary-300'
|
|
125
|
+
: 'bal-ba-hover-ring-primary-200')
|
|
126
|
+
"
|
|
127
|
+
(click)="pptcAccepted.set(!pptcAccepted())"
|
|
128
|
+
>
|
|
129
|
+
<div class="bas-flex bas-h-full bas-items-center bas-pb-1">
|
|
130
|
+
<label
|
|
131
|
+
for="txn-pptc-checkbox"
|
|
132
|
+
class="bal-ba-transition-3 bas-group bas-relative bas-flex bas-cursor-default bas-flex-row bas-items-center bas-gap-[5px] bas-text-sm bas-text-[var(--bal-primary-600)] hover:bas-text-[#003366]"
|
|
133
|
+
>
|
|
134
|
+
<input
|
|
135
|
+
type="checkbox"
|
|
136
|
+
id="txn-pptc-checkbox"
|
|
137
|
+
data-testid="checkbox-animated"
|
|
138
|
+
[checked]="pptcAccepted()"
|
|
139
|
+
class="bal-ba-checkbox-animated-input bal-ba-outline-none bas-peer bas-hidden"
|
|
140
|
+
(change)="pptcAccepted.set(!pptcAccepted())"
|
|
141
|
+
/>
|
|
142
|
+
<span class="bal-ba-input-check bal-ba-input-check-big bal-ba-transition-3 bas-relative bas-top-0 bas-inline-block bas-h-[25px] bas-w-[25px] bas-cursor-pointer bas-rounded-[4px] bas-border-2 bas-border-solid bas-border-[#c2c6c8] group-hover:bas-border-[#61a5c2] peer-checked:bas-border-2 peer-checked:bas-border-solid peer-checked:bas-border-[var(--bal-primary-600)] peer-checked:bas-bg-[var(--bal-primary-600)] peer-disabled:bas-cursor-not-allowed peer-disabled:bas-opacity-50"></span>
|
|
143
|
+
</label>
|
|
144
|
+
</div>
|
|
145
|
+
<div class="bas-flex bas-flex-col bas-gap-0.5 bas-text-sm bas-text-[var(--bal-primary-500)] group-hover:bas-text-[var(--bal-primary-800)]">
|
|
146
|
+
<div class="bas-flex bas-flex-row bas-items-center bas-gap-1">
|
|
147
|
+
<p class="bal-ba-transition-2 bas-m-0 bas-cursor-default">{{ iAuthorizeTheTxt() }}</p>
|
|
148
|
+
<button
|
|
149
|
+
type="button"
|
|
150
|
+
data-testid="btn-privacy-policy"
|
|
151
|
+
class="bal-ba-transition-2 bas-flex bas-h-full bas-cursor-pointer bas-items-center bas-border-none bas-bg-transparent bas-p-0 bas-pb-[1px] bas-text-sm bas-text-[var(--bal-primary-700)] bas-underline group-hover:bas-text-[var(--bal-primary-900)]"
|
|
152
|
+
(click)="openPrivacyPolicy()"
|
|
153
|
+
>
|
|
154
|
+
{{ privacyPolicyTxt() }}
|
|
155
|
+
</button>
|
|
156
|
+
<p class="bal-ba-transition-2 bas-m-0 bas-cursor-default">{{ andTheTxt() }}</p>
|
|
157
|
+
</div>
|
|
158
|
+
<button
|
|
159
|
+
type="button"
|
|
160
|
+
data-testid="btn-terms-and-conditions"
|
|
161
|
+
class="bal-ba-transition-2 bas-flex bas-h-full bas-w-fit bas-cursor-pointer bas-items-center bas-border-none bas-bg-transparent bas-p-0 bas-pb-[1px] bas-text-sm bas-text-[var(--bal-primary-700)] bas-underline group-hover:bas-text-[var(--bal-primary-900)]"
|
|
162
|
+
(click)="openTermsAndConditions()"
|
|
163
|
+
>
|
|
164
|
+
{{ termsAndConditionsTxt() }}
|
|
165
|
+
</button>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
<div class="bas-flex bas-w-full bas-flex-row bas-items-center bas-justify-between bas-gap-2">
|
|
169
|
+
<button
|
|
170
|
+
type="button"
|
|
171
|
+
id="btn-txn-reject"
|
|
172
|
+
class="bal-ba-ring-base bal-ba-ring bal-ba-transition-2 bas-group bas-relative bas-box-border bas-flex bas-flex-row bas-gap-2 bas-items-center bas-overflow-hidden bas-bg-transparent bal-ba-hover-ring-slate-400 bal-ba-focus-ring-slate-400 bas-h-[42px] bas-px-2.5 bas-py-0 disabled:bas-cursor-not-allowed disabled:bas-opacity-50 bal-ba-hover-ring-2 bas-cursor-pointer bal-ba-focus-ring-2 focus:bas-opacity-90 bal-ba-focus-ring-offset-0 bas-w-full bas-justify-center bas-border-2 bas-border-slate-400 bas-rounded-md bas-border-solid"
|
|
173
|
+
(click)="onTxnRejectClicked()"
|
|
174
|
+
>
|
|
175
|
+
<span class="bal-ba-transition-2 bas-text-center bas-text-base bas-font-medium bas-text-slate-600 group-hover:bas-text-slate-800 bas-flex bas-h-full bas-items-center">
|
|
176
|
+
{{ rejectTxt() }}
|
|
177
|
+
</span>
|
|
178
|
+
</button>
|
|
179
|
+
<button
|
|
180
|
+
type="button"
|
|
181
|
+
id="btn-txn-accept"
|
|
182
|
+
[disabled]="!pptcAccepted() || !allRequiredFilled()"
|
|
183
|
+
class="bal-ba-ring-base bal-ba-ring bal-ba-transition-2 bas-group bas-relative bas-box-border bas-flex bas-flex-row bas-gap-2 bas-items-center bas-overflow-hidden bas-bg-[#38b000] hover:bas-bg-[#329e00] bal-ba-hover-ring-green-500 bal-ba-focus-ring-green-500 bas-h-[42px] bas-px-2.5 bas-py-0 disabled:bas-cursor-not-allowed disabled:bas-opacity-50 bal-ba-hover-ring-2 bas-cursor-pointer bal-ba-focus-ring-2 focus:bas-opacity-90 bal-ba-focus-ring-offset-2 bal-ba-hover-ring-offset-2 bas-w-full bas-justify-center bas-border-none bas-rounded-md"
|
|
184
|
+
(click)="onTxnAcceptClicked()"
|
|
185
|
+
>
|
|
186
|
+
<span 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">
|
|
187
|
+
{{ acceptTxt() }}
|
|
188
|
+
</span>
|
|
189
|
+
</button>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
`,
|
|
194
|
+
})
|
|
195
|
+
export class TxnComponent {
|
|
196
|
+
permissionsRequiredTxt = input('Permissions required');
|
|
197
|
+
solicitedDataTxt = input('Solicited data');
|
|
198
|
+
rejectTxt = input('REJECT');
|
|
199
|
+
acceptTxt = input('ACCEPT');
|
|
200
|
+
iAuthorizeTheTxt = input('I authorize the');
|
|
201
|
+
privacyPolicyTxt = input('Privacy Policy');
|
|
202
|
+
andTheTxt = input('and the');
|
|
203
|
+
termsAndConditionsTxt = input('Terms and Conditions');
|
|
204
|
+
configureSessionTimeTxt = input('I want to configure my session time');
|
|
205
|
+
hasSolicitedData = input(false);
|
|
206
|
+
allRequiredFilled = input(true);
|
|
207
|
+
closeTxns = input<() => void>(() => {});
|
|
208
|
+
/** Session lifetime in seconds (default 3600). */
|
|
209
|
+
lifetime = input(3600);
|
|
210
|
+
/** Pre-params from the selected transaction (used for validation). */
|
|
211
|
+
selectedTxnPreParams = input<TxnPreParams>({});
|
|
212
|
+
/** User-provided parameter values for solicited data. */
|
|
213
|
+
userParamValues = input<Record<string, string>>({});
|
|
214
|
+
/** Callback when an error occurs during accept validation. */
|
|
215
|
+
addError = input<(messageSlug: string) => void>(() => {});
|
|
216
|
+
|
|
217
|
+
permissionsRequiredClicked = output<void>();
|
|
218
|
+
solicitedDataClicked = output<void>();
|
|
219
|
+
|
|
220
|
+
private readonly client = injectBlockAuthClient();
|
|
221
|
+
|
|
222
|
+
protected readonly pptcAccepted = signal(false);
|
|
223
|
+
protected readonly showLifetime = signal(false);
|
|
224
|
+
|
|
225
|
+
private readonly lifetimeData = computed(() => this.selectedTxnPreParams().lifetime ?? {});
|
|
226
|
+
protected readonly minLifetime = computed(() => this.lifetimeData().min ?? 10);
|
|
227
|
+
protected readonly maxLifetime = computed(() => this.lifetimeData().max ?? 29100);
|
|
228
|
+
protected readonly lifetimeSign = signal(this.lifetime());
|
|
229
|
+
// El TimePicker refleja `lifetimeSign` como string HH:MM:SS; tanto el slider
|
|
230
|
+
// como el picker reportan en segundos, así un único handler mantiene ambos
|
|
231
|
+
// en sync.
|
|
232
|
+
protected readonly lifetimeTimeValue = signal(getTimeBySec(this.lifetime()));
|
|
233
|
+
protected readonly isTimePickerOpen = signal(false);
|
|
234
|
+
|
|
235
|
+
private autoCloseTimeout: ReturnType<typeof setTimeout> | undefined;
|
|
236
|
+
|
|
237
|
+
constructor() {
|
|
238
|
+
// Init lifetimeSign/lifetimeTimeValue from the selected txn's lifetime value
|
|
239
|
+
// (or the default).
|
|
240
|
+
const initialLifetime = this.lifetimeData().value ?? this.lifetime();
|
|
241
|
+
this.lifetimeSign.set(initialLifetime);
|
|
242
|
+
this.lifetimeTimeValue.set(getTimeBySec(initialLifetime));
|
|
243
|
+
inject(DestroyRef).onDestroy(() => {
|
|
244
|
+
if (this.autoCloseTimeout) clearTimeout(this.autoCloseTimeout);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
onSolicitedDataClick(): void {
|
|
249
|
+
if (this.hasSolicitedData()) this.solicitedDataClicked.emit();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
toggleTimerDropdown(): void {
|
|
253
|
+
this.isTimePickerOpen.update((v) => !v);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// El slider y el TimePicker reportan en segundos; un único handler mantiene
|
|
257
|
+
// ambas vistas (número + HH:MM:SS) en sync.
|
|
258
|
+
onLifetimeValueChanged(value: number): void {
|
|
259
|
+
this.lifetimeSign.set(value);
|
|
260
|
+
this.lifetimeTimeValue.set(getTimeBySec(value));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
openPrivacyPolicy(): void {
|
|
264
|
+
if (typeof window !== 'undefined')
|
|
265
|
+
window.open('https://block-auth.io/politica-de-privacidad/', '_blank');
|
|
266
|
+
}
|
|
267
|
+
openTermsAndConditions(): void {
|
|
268
|
+
if (typeof window !== 'undefined')
|
|
269
|
+
window.open('https://block-auth.io/aviso-legal/', '_blank');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Solo resetea flags locales/UI y cierra el modal.
|
|
273
|
+
// NO cancela la txn (ver comentario de cabecera).
|
|
274
|
+
private close(): void {
|
|
275
|
+
this.pptcAccepted.set(false);
|
|
276
|
+
this.client.state.$ui.setKey('isAcceptingTxn', false);
|
|
277
|
+
this.client.closeModal();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
onTxnRejectClicked(): void {
|
|
281
|
+
this.client.txns.reject();
|
|
282
|
+
this.close();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
onTxnAcceptClicked(): void {
|
|
286
|
+
const preParams = this.selectedTxnPreParams();
|
|
287
|
+
if (!preParams) return this.addError()('no_transactions_params_found');
|
|
288
|
+
if (!preParams.appKey) return this.addError()('no_app_key_found');
|
|
289
|
+
if (!preParams.to) return this.addError()('no_contract_address_found');
|
|
290
|
+
this.client.state.$ui.setKey('isAcceptingTxn', true);
|
|
291
|
+
this.client.txns.accept({ lifetime: this.lifetimeSign(), userParams: this.userParamValues() });
|
|
292
|
+
// Auto-close after 4s if txns are cleared (matches original behaviour).
|
|
293
|
+
this.autoCloseTimeout = setTimeout(() => {
|
|
294
|
+
const requests = this.client.state.$ws.get().requests;
|
|
295
|
+
if (requests && requests.length === 0) this.close();
|
|
296
|
+
}, 4000);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/angular';
|
|
3
|
+
import { map } from 'nanostores';
|
|
4
|
+
import { TxnsOverviewComponent } from './txns-overview.component';
|
|
5
|
+
import { provideBlockAuthClientForTest } from '../../testing';
|
|
6
|
+
|
|
7
|
+
vi.mock('@wagmi/core', () => ({
|
|
8
|
+
createConfig: vi.fn(() => ({ connectors: [] })),
|
|
9
|
+
connect: vi.fn(), disconnect: vi.fn(), getAccount: vi.fn(() => ({})),
|
|
10
|
+
signMessage: vi.fn(), watchAccount: vi.fn(() => () => undefined), http: vi.fn(),
|
|
11
|
+
}));
|
|
12
|
+
vi.mock('@wagmi/connectors', () => ({
|
|
13
|
+
injected: vi.fn(() => ({ id: 'injected' })), walletConnect: vi.fn(() => ({ id: 'walletConnect' })),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
// Port de packages/react/src/components/txns/TxnsOverview.test.tsx, extendido
|
|
17
|
+
// con la vista poblada (Txn + navegación prev/next), igual que el port Vue.
|
|
18
|
+
describe('TxnsOverview', () => {
|
|
19
|
+
function makeClient(ws: Record<string, any> = {}, ui: Record<string, any> = {}) {
|
|
20
|
+
return {
|
|
21
|
+
txns: { reject: vi.fn(), accept: vi.fn(), report: vi.fn() },
|
|
22
|
+
closeModal: vi.fn(),
|
|
23
|
+
state: { $ui: map({ ...ui }), $ws: map({ requests: [], ...ws }) },
|
|
24
|
+
} as any;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
it('renderiza la vista general de txns (estado vacio sin crashear)', async () => {
|
|
28
|
+
await render(TxnsOverviewComponent, {
|
|
29
|
+
providers: [provideBlockAuthClientForTest(makeClient({}, { isSigning: false }))],
|
|
30
|
+
});
|
|
31
|
+
expect(screen.getByTestId('txns-overview')).toBeDefined();
|
|
32
|
+
expect(screen.queryByText('No transactions found')).toBeNull();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('estado vacio + isSigning muestra el error "No transactions found"', async () => {
|
|
36
|
+
await render(TxnsOverviewComponent, {
|
|
37
|
+
providers: [provideBlockAuthClientForTest(makeClient({}, { isSigning: true }))],
|
|
38
|
+
});
|
|
39
|
+
expect(screen.getByText('No transactions found')).toBeDefined();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('con txns pendientes renderiza el header + TxnType + Txn', async () => {
|
|
43
|
+
await render(TxnsOverviewComponent, {
|
|
44
|
+
providers: [
|
|
45
|
+
provideBlockAuthClientForTest(
|
|
46
|
+
makeClient({
|
|
47
|
+
did: '0x1234567890abcdef',
|
|
48
|
+
requests: [
|
|
49
|
+
{ id: 1, pre_params: JSON.stringify({ appName: 'Acme', domain: 'acme.io', txnType: 'Log In' }) },
|
|
50
|
+
],
|
|
51
|
+
}),
|
|
52
|
+
),
|
|
53
|
+
],
|
|
54
|
+
});
|
|
55
|
+
expect(screen.getByTestId('txns-overview')).toBeDefined();
|
|
56
|
+
expect(screen.getByText('acme.io')).toBeDefined();
|
|
57
|
+
expect(screen.getByText(/Acme/)).toBeDefined();
|
|
58
|
+
expect(screen.getByText('Log In')).toBeDefined();
|
|
59
|
+
expect(screen.getByText('ACCEPT')).toBeDefined();
|
|
60
|
+
expect(screen.getByText('REJECT')).toBeDefined();
|
|
61
|
+
// Single txn -> no prev/next nav.
|
|
62
|
+
expect(screen.queryByLabelText('next transaction')).toBeNull();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('navega entre txns con prev/next', async () => {
|
|
66
|
+
await render(TxnsOverviewComponent, {
|
|
67
|
+
providers: [
|
|
68
|
+
provideBlockAuthClientForTest(
|
|
69
|
+
makeClient({
|
|
70
|
+
requests: [
|
|
71
|
+
{ id: 1, pre_params: JSON.stringify({ txnType: 'Log In' }) },
|
|
72
|
+
{ id: 2, pre_params: JSON.stringify({ txnType: 'Sign message' }) },
|
|
73
|
+
],
|
|
74
|
+
}),
|
|
75
|
+
),
|
|
76
|
+
],
|
|
77
|
+
});
|
|
78
|
+
expect(screen.getByText('Log In')).toBeDefined();
|
|
79
|
+
await fireEvent.click(screen.getByLabelText('next transaction'));
|
|
80
|
+
expect(screen.getByText('Sign message')).toBeDefined();
|
|
81
|
+
await fireEvent.click(screen.getByLabelText('next transaction'));
|
|
82
|
+
expect(screen.getByText('Log In')).toBeDefined();
|
|
83
|
+
await fireEvent.click(screen.getByLabelText('previous transaction'));
|
|
84
|
+
expect(screen.getByText('Sign message')).toBeDefined();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('un pre_params invalido no rompe el render (fallback a valores por defecto)', async () => {
|
|
88
|
+
await render(TxnsOverviewComponent, {
|
|
89
|
+
providers: [
|
|
90
|
+
provideBlockAuthClientForTest(makeClient({ requests: [{ id: 1, pre_params: 'not-json' }] })),
|
|
91
|
+
],
|
|
92
|
+
});
|
|
93
|
+
expect(screen.getByText('dashboard.block-auth.io')).toBeDefined();
|
|
94
|
+
expect(screen.getByText('Log In')).toBeDefined();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('addError de Txn muestra el error correspondiente en TxnsOverview', async () => {
|
|
98
|
+
await render(TxnsOverviewComponent, {
|
|
99
|
+
providers: [
|
|
100
|
+
provideBlockAuthClientForTest(makeClient({ requests: [{ id: 1, pre_params: JSON.stringify({}) }] })),
|
|
101
|
+
],
|
|
102
|
+
});
|
|
103
|
+
await fireEvent.click(screen.getByTestId('txn-pptc-toggle'));
|
|
104
|
+
await fireEvent.click(screen.getByText('ACCEPT'));
|
|
105
|
+
expect(screen.getByText('No application key found')).toBeDefined();
|
|
106
|
+
});
|
|
107
|
+
});
|