@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,81 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { Component, input } from '@angular/core';
|
|
3
|
+
import { render, screen, fireEvent } from '@testing-library/angular';
|
|
4
|
+
import { ModalOverlayComponent } from './modal-overlay.component';
|
|
5
|
+
|
|
6
|
+
// Port de packages/react/src/components/modals/ModalOverlay.tsx (mapa:
|
|
7
|
+
// packages/vue/src/components/modals/ModalOverlay.test.ts): verifica el
|
|
8
|
+
// contrato de portal + backdrop-click + slot que comparten los 4 modales.
|
|
9
|
+
// Los signal inputs no se pueden fijar por `componentInputs` de forma fiable,
|
|
10
|
+
// así que se usa un host component con binding de template.
|
|
11
|
+
@Component({
|
|
12
|
+
selector: 'ba-overlay-host',
|
|
13
|
+
standalone: true,
|
|
14
|
+
imports: [ModalOverlayComponent],
|
|
15
|
+
template: `
|
|
16
|
+
<ba-modal-overlay [open]="open()" testId="modal-x" (closed)="onClose()">
|
|
17
|
+
<div data-testid="panel-content">hola</div>
|
|
18
|
+
</ba-modal-overlay>
|
|
19
|
+
`,
|
|
20
|
+
})
|
|
21
|
+
class OverlayHostComponent {
|
|
22
|
+
open = input(false);
|
|
23
|
+
onClose = (): void => undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('ModalOverlay', () => {
|
|
27
|
+
it('no renderiza nada cuando open es false', async () => {
|
|
28
|
+
await render(OverlayHostComponent);
|
|
29
|
+
expect(screen.queryByTestId('modal-x')).toBeNull();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('se monta en document.body (portal) y renderiza el slot cuando open es true', async () => {
|
|
33
|
+
@Component({
|
|
34
|
+
selector: 'ba-overlay-host-open',
|
|
35
|
+
standalone: true,
|
|
36
|
+
imports: [ModalOverlayComponent],
|
|
37
|
+
template: `
|
|
38
|
+
<ba-modal-overlay [open]="true" testId="modal-x">
|
|
39
|
+
<div data-testid="panel-content">hola</div>
|
|
40
|
+
</ba-modal-overlay>
|
|
41
|
+
`,
|
|
42
|
+
})
|
|
43
|
+
class HostOpen {}
|
|
44
|
+
await render(HostOpen);
|
|
45
|
+
const overlay = screen.getByTestId('modal-x');
|
|
46
|
+
expect(overlay).toBeDefined();
|
|
47
|
+
expect(overlay.parentElement).toBe(document.body);
|
|
48
|
+
expect(screen.getByTestId('panel-content').textContent).toBe('hola');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('el panel tiene role="dialog" y aria-modal="true"', async () => {
|
|
52
|
+
@Component({
|
|
53
|
+
selector: 'ba-overlay-host-dialog',
|
|
54
|
+
standalone: true,
|
|
55
|
+
imports: [ModalOverlayComponent],
|
|
56
|
+
template: `<ba-modal-overlay [open]="true" testId="modal-x" />`,
|
|
57
|
+
})
|
|
58
|
+
class HostDialog {}
|
|
59
|
+
await render(HostDialog);
|
|
60
|
+
const dialog = screen.getByRole('dialog');
|
|
61
|
+
expect(dialog.getAttribute('aria-modal')).toBe('true');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('al hacer click en el backdrop emite closed', async () => {
|
|
65
|
+
const onClose = vi.fn();
|
|
66
|
+
@Component({
|
|
67
|
+
selector: 'ba-overlay-host-close',
|
|
68
|
+
standalone: true,
|
|
69
|
+
imports: [ModalOverlayComponent],
|
|
70
|
+
template: `<ba-modal-overlay [open]="true" testId="modal-x" (closed)="onClose()" />`,
|
|
71
|
+
})
|
|
72
|
+
class HostClose {
|
|
73
|
+
onClose = onClose;
|
|
74
|
+
}
|
|
75
|
+
await render(HostClose);
|
|
76
|
+
const backdrop = screen.getByTestId('modal-x').querySelector('[aria-hidden="true"]');
|
|
77
|
+
expect(backdrop).not.toBeNull();
|
|
78
|
+
await fireEvent.click(backdrop!);
|
|
79
|
+
expect(onClose).toHaveBeenCalledOnce();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, input, output } from '@angular/core';
|
|
2
|
+
import { TeleportBodyDirective } from '../../teleport-body.directive';
|
|
3
|
+
|
|
4
|
+
// Overlay mínimo usado por los 4 modales. La fuente React usaba `<ModalBase>` de
|
|
5
|
+
// `@block-auth.io/blockauth-lib-react` (un Dialog headless-ui con transiciones
|
|
6
|
+
// + portal); esa lib aún no es dependencia de este paquete, así que este es un
|
|
7
|
+
// stand-in ligero que preserva las clases `bas-` del panel original y el
|
|
8
|
+
// contrato open/close. La visibilidad la maneja el estado core `$openModals`
|
|
9
|
+
// en cada modal llamador.
|
|
10
|
+
//
|
|
11
|
+
// Se monta en `document.body` vía `baTeleportBody` (equivalente a
|
|
12
|
+
// createPortal(..., document.body) / <Teleport to="body">): las apps host
|
|
13
|
+
// montan `FlowBlockAuth` dentro de contenedores transformados/animados que de
|
|
14
|
+
// otro modo atraparían el overlay `fixed` dentro de ese ancestro.
|
|
15
|
+
@Component({
|
|
16
|
+
selector: 'ba-modal-overlay',
|
|
17
|
+
standalone: true,
|
|
18
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
19
|
+
imports: [TeleportBodyDirective],
|
|
20
|
+
template: `
|
|
21
|
+
@if (open()) {
|
|
22
|
+
<div
|
|
23
|
+
baTeleportBody
|
|
24
|
+
[attr.data-testid]="testId()"
|
|
25
|
+
[class]="'bas-fixed bas-inset-0 bas-z-50 bas-bg-black/40 ' + containerClassName()"
|
|
26
|
+
>
|
|
27
|
+
<div class="bas-absolute bas-inset-0" aria-hidden="true" (click)="closed.emit()"></div>
|
|
28
|
+
<div [class]="'bas-relative ' + panelClassName()" role="dialog" aria-modal="true">
|
|
29
|
+
<ng-content />
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
}
|
|
33
|
+
`,
|
|
34
|
+
})
|
|
35
|
+
export class ModalOverlayComponent {
|
|
36
|
+
open = input.required<boolean>();
|
|
37
|
+
panelClassName = input('');
|
|
38
|
+
containerClassName = input(
|
|
39
|
+
'bas-flex bas-min-h-full bas-items-end xmd:bas-items-baseline xmd:bas-justify-center',
|
|
40
|
+
);
|
|
41
|
+
testId = input<string | undefined>(undefined);
|
|
42
|
+
closed = output<void>();
|
|
43
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/angular';
|
|
3
|
+
import { atom } from 'nanostores';
|
|
4
|
+
import type { ModalName } from '@block-auth.io/core';
|
|
5
|
+
import { ModalRestorationExpiredComponent } from './modal-restoration-expired.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
|
+
// ModalRestorationExpired inyecta el client directamente (BlockAuthService no
|
|
18
|
+
// expone finalizeRestorationExpired()), así que el fake sólo necesita
|
|
19
|
+
// state.$openModals + los métodos.
|
|
20
|
+
function makeClient() {
|
|
21
|
+
const $openModals = atom<ModalName[]>([]);
|
|
22
|
+
const openModal = (m: ModalName) => $openModals.set([...$openModals.get(), m]);
|
|
23
|
+
const closeModal = vi.fn(() => $openModals.set([]));
|
|
24
|
+
const finalizeRestorationExpired = vi.fn();
|
|
25
|
+
const client = { state: { $openModals }, openModal, closeModal, finalizeRestorationExpired } as any;
|
|
26
|
+
return { client, $openModals, openModal, closeModal, finalizeRestorationExpired };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Port de packages/react/src/components/modals/ModalRestorationExpired.test.tsx.
|
|
30
|
+
describe('ModalRestorationExpired', () => {
|
|
31
|
+
it('no renderiza cuando el modal está cerrado', async () => {
|
|
32
|
+
const { client } = makeClient();
|
|
33
|
+
await render(ModalRestorationExpiredComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
34
|
+
expect(screen.queryByTestId('modal-restoration-expired')).toBeNull();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('renderiza el contenido cuando el modal restorationExpired está abierto', async () => {
|
|
38
|
+
const { client } = makeClient();
|
|
39
|
+
client.openModal('restorationExpired');
|
|
40
|
+
await render(ModalRestorationExpiredComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
41
|
+
expect(screen.getByTestId('modal-restoration-expired')).toBeDefined();
|
|
42
|
+
expect(screen.getByText('Restoration Period Expired')).toBeDefined();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('al hacer click en "Continue" llama finalizeRestorationExpired() y luego closeModal()', async () => {
|
|
46
|
+
const { client } = makeClient();
|
|
47
|
+
const callOrder: string[] = [];
|
|
48
|
+
client.finalizeRestorationExpired = vi.fn(() => callOrder.push('finalize'));
|
|
49
|
+
client.closeModal = vi.fn(() => callOrder.push('closeModal'));
|
|
50
|
+
client.openModal('restorationExpired');
|
|
51
|
+
|
|
52
|
+
await render(ModalRestorationExpiredComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
53
|
+
await fireEvent.click(screen.getByRole('button', { name: /continue/i }));
|
|
54
|
+
|
|
55
|
+
expect(client.finalizeRestorationExpired).toHaveBeenCalledOnce();
|
|
56
|
+
expect(client.closeModal).toHaveBeenCalledOnce();
|
|
57
|
+
expect(callOrder).toEqual(['finalize', 'closeModal']);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('al hacer click en el backdrop también llama finalizeRestorationExpired() y closeModal()', async () => {
|
|
61
|
+
const { client, finalizeRestorationExpired, closeModal } = makeClient();
|
|
62
|
+
client.openModal('restorationExpired');
|
|
63
|
+
|
|
64
|
+
await render(ModalRestorationExpiredComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
65
|
+
const backdrop = screen.getByTestId('modal-restoration-expired').querySelector('[aria-hidden="true"]');
|
|
66
|
+
await fireEvent.click(backdrop!);
|
|
67
|
+
|
|
68
|
+
expect(finalizeRestorationExpired).toHaveBeenCalledOnce();
|
|
69
|
+
expect(closeModal).toHaveBeenCalledOnce();
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, computed } from '@angular/core';
|
|
2
|
+
import { ModalOverlayComponent } from './modal-overlay.component';
|
|
3
|
+
import { BtnBaseComponent } from '../../primitives/btn-base.component';
|
|
4
|
+
import { PiWarningFillComponent, FaArrowRightComponent } from '../../icons';
|
|
5
|
+
import { injectBlockAuthClient } from '../../provide-block-auth';
|
|
6
|
+
import { injectStore } from '../../inject-store';
|
|
7
|
+
|
|
8
|
+
const TEXTS = {
|
|
9
|
+
title: 'Restoration Period Expired',
|
|
10
|
+
message:
|
|
11
|
+
'Account permanently deleted, please complete the registration again to use Blockauth again',
|
|
12
|
+
button: 'Continue',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Modal de restauración expirada fino, dirigido por el core. Visibilidad por
|
|
17
|
+
* `openModals().includes('restorationExpired')`. Confirmar (o backdrop) ejecuta
|
|
18
|
+
* `finalizeRestorationExpired()` (limpia el storage de auth restante y resetea
|
|
19
|
+
* los flags de borrado/firma) y luego `closeModal()`.
|
|
20
|
+
*
|
|
21
|
+
* El `text` de React era un ReactNode (label + icono); igual que en el port Vue
|
|
22
|
+
* se usa el slot de contenido por defecto de `<ba-btn-base>` (`isIcon=false`)
|
|
23
|
+
* para renderizar la etiqueta + la flecha, en vez de la prop `text`.
|
|
24
|
+
*
|
|
25
|
+
* Se inyecta el client directamente (no via BlockAuthService) porque
|
|
26
|
+
* `finalizeRestorationExpired()` no está expuesto en el facade.
|
|
27
|
+
*/
|
|
28
|
+
@Component({
|
|
29
|
+
selector: 'ba-modal-restoration-expired',
|
|
30
|
+
standalone: true,
|
|
31
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
32
|
+
imports: [ModalOverlayComponent, BtnBaseComponent, PiWarningFillComponent, FaArrowRightComponent],
|
|
33
|
+
template: `
|
|
34
|
+
<ba-modal-overlay
|
|
35
|
+
testId="modal-restoration-expired"
|
|
36
|
+
[open]="open()"
|
|
37
|
+
containerClassName="bas-flex bas-h-full bas-items-end bas-justify-center md:bas-items-center"
|
|
38
|
+
panelClassName="bas-overflow-hidden bas-bg-white bas-rounded-2xl bas-w-[90%] sm:bas-max-w-[540px]"
|
|
39
|
+
(closed)="onOkClicked()"
|
|
40
|
+
>
|
|
41
|
+
<header class="bas-w-full bas-border-b-2 bas-border-solid bas-border-[--bal-primary-100] bas-bg-[--bg-200] bas-py-6 bas-px-8 bas-text-center sm:bas-py-10">
|
|
42
|
+
<h3 class="bas-text-lg bas-font-bold bas-text-[--bal-primary-900] sm:bas-text-2xl">{{ TEXTS.title }}</h3>
|
|
43
|
+
</header>
|
|
44
|
+
<main class="bas-flex bas-flex-col bas-gap-8 bas-p-8 sm:bas-gap-10 sm:bas-p-12">
|
|
45
|
+
<div class="bas-flex bas-flex-col bas-items-center bas-gap-6 bas-text-center">
|
|
46
|
+
<ba-pi-warning-fill class="bas-h-12 bas-w-12 bas-text-black sm:bas-h-20 sm:bas-w-20" />
|
|
47
|
+
<article class="bas-text-sm bas-font-medium bas-leading-relaxed bas-text-black sm:bas-text-base">{{ TEXTS.message }}</article>
|
|
48
|
+
</div>
|
|
49
|
+
<footer class="bas-flex bas-w-full bas-justify-center">
|
|
50
|
+
<ba-btn-base
|
|
51
|
+
btnId="btn-ok-restoration-expired"
|
|
52
|
+
size="small"
|
|
53
|
+
bgClassNames="bas-bg-blue-900"
|
|
54
|
+
ringClassNames="bal-ba-hover-ring-blue-600 bal-ba-focus-ring-blue-600"
|
|
55
|
+
textClassNames="bas-text-white bas-font-semibold"
|
|
56
|
+
classNames="bas-w-full bas-justify-center bas-shadow-md"
|
|
57
|
+
paddingClassNames="bas-py-3"
|
|
58
|
+
[isIcon]="false"
|
|
59
|
+
[disabled]="!open()"
|
|
60
|
+
(action)="onOkClicked()"
|
|
61
|
+
>
|
|
62
|
+
<div class="bas-flex bas-items-center bas-gap-2">
|
|
63
|
+
<span class="bas-uppercase bas-tracking-wider">{{ TEXTS.button }}</span>
|
|
64
|
+
<ba-fa-arrow-right class="bas-text-white" />
|
|
65
|
+
</div>
|
|
66
|
+
</ba-btn-base>
|
|
67
|
+
</footer>
|
|
68
|
+
</main>
|
|
69
|
+
</ba-modal-overlay>
|
|
70
|
+
`,
|
|
71
|
+
})
|
|
72
|
+
export class ModalRestorationExpiredComponent {
|
|
73
|
+
protected readonly TEXTS = TEXTS;
|
|
74
|
+
|
|
75
|
+
private readonly client = injectBlockAuthClient();
|
|
76
|
+
private readonly openModals = injectStore(this.client.state.$openModals);
|
|
77
|
+
readonly open = computed(() => this.openModals().includes('restorationExpired'));
|
|
78
|
+
|
|
79
|
+
onOkClicked(): void {
|
|
80
|
+
this.client.finalizeRestorationExpired();
|
|
81
|
+
this.client.closeModal();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/angular';
|
|
3
|
+
import { atom } from 'nanostores';
|
|
4
|
+
import type { ModalName, RestoreResult } from '@block-auth.io/core';
|
|
5
|
+
import { ModalRestoreComponent } from './modal-restore.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
|
+
// ModalRestore inyecta el client directamente (BlockAuthService no expone
|
|
18
|
+
// restore()), así que el fake sólo necesita state.$openModals + los métodos.
|
|
19
|
+
function makeClient() {
|
|
20
|
+
const $openModals = atom<ModalName[]>([]);
|
|
21
|
+
const openModal = (m: ModalName) => $openModals.set([...$openModals.get(), m]);
|
|
22
|
+
const closeModal = vi.fn(() => $openModals.set([]));
|
|
23
|
+
const restore = vi.fn((): Promise<RestoreResult> => Promise.resolve({ success: true }));
|
|
24
|
+
const client = { state: { $openModals }, openModal, closeModal, restore } as any;
|
|
25
|
+
return { client, $openModals, openModal, closeModal, restore };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Port de packages/react/src/components/modals/ModalRestore.test.tsx.
|
|
29
|
+
describe('ModalRestore', () => {
|
|
30
|
+
it('no renderiza cuando el modal está cerrado', async () => {
|
|
31
|
+
const { client } = makeClient();
|
|
32
|
+
await render(ModalRestoreComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
33
|
+
expect(screen.queryByTestId('modal-restore')).toBeNull();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('renderiza el contenido cuando el modal restore está abierto', async () => {
|
|
37
|
+
const { client } = makeClient();
|
|
38
|
+
client.openModal('restore');
|
|
39
|
+
await render(ModalRestoreComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
40
|
+
expect(screen.getByTestId('modal-restore')).toBeDefined();
|
|
41
|
+
expect(screen.getByText('Do you want to restore your account?')).toBeDefined();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('click en "No" llama solo client.closeModal(), sin invocar restore()', async () => {
|
|
45
|
+
const { client, closeModal, restore } = makeClient();
|
|
46
|
+
client.openModal('restore');
|
|
47
|
+
await render(ModalRestoreComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
48
|
+
await fireEvent.click(screen.getByRole('button', { name: /no/i }));
|
|
49
|
+
|
|
50
|
+
expect(closeModal).toHaveBeenCalledOnce();
|
|
51
|
+
expect(restore).not.toHaveBeenCalled();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('click en "Restore" llama closeModal() antes de await restore(), en ese orden', async () => {
|
|
55
|
+
const { client } = makeClient();
|
|
56
|
+
const callOrder: string[] = [];
|
|
57
|
+
client.closeModal = vi.fn(() => callOrder.push('closeModal'));
|
|
58
|
+
let resolveRestore: (result: RestoreResult) => void;
|
|
59
|
+
client.restore = vi.fn(() => {
|
|
60
|
+
callOrder.push('restore');
|
|
61
|
+
return new Promise<RestoreResult>((resolve) => { resolveRestore = resolve; });
|
|
62
|
+
});
|
|
63
|
+
client.openModal('restore');
|
|
64
|
+
|
|
65
|
+
await render(ModalRestoreComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
66
|
+
await fireEvent.click(screen.getByRole('button', { name: /restore/i }));
|
|
67
|
+
|
|
68
|
+
expect(client.closeModal).toHaveBeenCalledOnce();
|
|
69
|
+
expect(client.restore).toHaveBeenCalledOnce();
|
|
70
|
+
expect(callOrder).toEqual(['closeModal', 'restore']);
|
|
71
|
+
|
|
72
|
+
resolveRestore!({ success: true });
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core';
|
|
2
|
+
import { ModalOverlayComponent } from './modal-overlay.component';
|
|
3
|
+
import { BtnBaseComponent } from '../../primitives/btn-base.component';
|
|
4
|
+
import { FaTimesComponent, FaUndoComponent } from '../../icons';
|
|
5
|
+
import { injectBlockAuthClient } from '../../provide-block-auth';
|
|
6
|
+
import { injectStore } from '../../inject-store';
|
|
7
|
+
|
|
8
|
+
const TEXTS = {
|
|
9
|
+
title: 'Do you want to restore your account?',
|
|
10
|
+
message: 'By continuing you will be able to return to your usual operations.',
|
|
11
|
+
yes: 'Restore',
|
|
12
|
+
no: 'No',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Modal de restauración de cuenta fino, dirigido por el core. Visibilidad por
|
|
17
|
+
* `openModals().includes('restore')`; el cierre delega en `closeModal()`.
|
|
18
|
+
* Confirmar cierra el modal y luego `await client.restore()` (el core resuelve
|
|
19
|
+
* la dirección de borrado/DID, emite `sdk:restore` y espera el desenlace). Los
|
|
20
|
+
* botones se deshabilitan mientras la restauración está en curso.
|
|
21
|
+
*
|
|
22
|
+
* Se inyecta el client directamente (no via BlockAuthService) porque `restore()`
|
|
23
|
+
* no está expuesto en el facade.
|
|
24
|
+
*/
|
|
25
|
+
@Component({
|
|
26
|
+
selector: 'ba-modal-restore',
|
|
27
|
+
standalone: true,
|
|
28
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
29
|
+
imports: [ModalOverlayComponent, BtnBaseComponent, FaTimesComponent, FaUndoComponent],
|
|
30
|
+
template: `
|
|
31
|
+
<ba-modal-overlay
|
|
32
|
+
testId="modal-restore"
|
|
33
|
+
[open]="open()"
|
|
34
|
+
containerClassName="bas-flex bas-h-full bas-items-end bas-justify-center md:bas-items-center"
|
|
35
|
+
panelClassName="bas-overflow-hidden bas-bg-white bas-rounded-2xl bas-w-[90%] sm:bas-max-w-[540px]"
|
|
36
|
+
(closed)="onNoClicked()"
|
|
37
|
+
>
|
|
38
|
+
<div class="bas-w-full bas-border-b-2 bas-border-solid bas-border-[--bal-primary-100] bas-bg-[--bg-200] bas-py-6 bas-px-8 sm:bas-py-10 bas-text-center">
|
|
39
|
+
<h3 class="bas-text-lg bas-font-bold bas-text-[--bal-primary-900] sm:bas-text-2xl">{{ TEXTS.title }}</h3>
|
|
40
|
+
</div>
|
|
41
|
+
<div class="bas-flex bas-flex-col bas-gap-8 bas-p-8 sm:bas-gap-10 sm:bas-p-12">
|
|
42
|
+
<div class="bas-flex bas-flex-col bas-gap-4 bas-text-center">
|
|
43
|
+
<p class="bas-text-base bas-text-[--bal-primary-800] sm:bas-text-lg">{{ TEXTS.message }}</p>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="bas-flex bas-w-full bas-flex-col-reverse bas-gap-4 sm:bas-flex-row">
|
|
46
|
+
<ba-btn-base
|
|
47
|
+
btnId="btn-no-restore-account"
|
|
48
|
+
[text]="TEXTS.no"
|
|
49
|
+
size="small"
|
|
50
|
+
bgClassNames="bas-bg-blue-900"
|
|
51
|
+
ringClassNames="bal-ba-hover-ring-blue-600 bal-ba-focus-ring-blue-600"
|
|
52
|
+
textClassNames="bas-text-white bas-font-semibold bas-uppercase bas-tracking-wider"
|
|
53
|
+
classNames="bas-w-full bas-justify-center bas-shadow-md"
|
|
54
|
+
paddingClassNames="bas-py-3"
|
|
55
|
+
[isIcon]="true"
|
|
56
|
+
[disabled]="!open() || isRestoring()"
|
|
57
|
+
(action)="onNoClicked()"
|
|
58
|
+
>
|
|
59
|
+
<ba-fa-times slot="icon" class="bas-mr-2 bas-text-white" />
|
|
60
|
+
</ba-btn-base>
|
|
61
|
+
<ba-btn-base
|
|
62
|
+
btnId="btn-yes-restore-account"
|
|
63
|
+
[text]="TEXTS.yes"
|
|
64
|
+
size="small"
|
|
65
|
+
bgClassNames="bas-bg-green-600 hover:bas-bg-green-700"
|
|
66
|
+
ringClassNames="bal-ba-hover-ring-green-600 bal-ba-focus-ring-green-600"
|
|
67
|
+
textClassNames="bas-text-white bas-font-semibold bas-tracking-wider bas-uppercase"
|
|
68
|
+
classNames="bas-w-full bas-justify-center bas-shadow-lg"
|
|
69
|
+
paddingClassNames="bas-py-3"
|
|
70
|
+
[isIcon]="true"
|
|
71
|
+
[disabled]="!open() || isRestoring()"
|
|
72
|
+
(action)="onYesClicked()"
|
|
73
|
+
>
|
|
74
|
+
<ba-fa-undo slot="icon" class="bas-mr-2 bas-text-white" />
|
|
75
|
+
</ba-btn-base>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</ba-modal-overlay>
|
|
79
|
+
`,
|
|
80
|
+
})
|
|
81
|
+
export class ModalRestoreComponent {
|
|
82
|
+
protected readonly TEXTS = TEXTS;
|
|
83
|
+
|
|
84
|
+
private readonly client = injectBlockAuthClient();
|
|
85
|
+
private readonly openModals = injectStore(this.client.state.$openModals);
|
|
86
|
+
readonly open = computed(() => this.openModals().includes('restore'));
|
|
87
|
+
readonly isRestoring = signal(false);
|
|
88
|
+
|
|
89
|
+
onNoClicked(): void {
|
|
90
|
+
if (this.isRestoring()) return;
|
|
91
|
+
this.client.closeModal();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async onYesClicked(): Promise<void> {
|
|
95
|
+
if (this.isRestoring()) return;
|
|
96
|
+
this.isRestoring.set(true);
|
|
97
|
+
this.client.closeModal();
|
|
98
|
+
try {
|
|
99
|
+
await this.client.restore();
|
|
100
|
+
} finally {
|
|
101
|
+
this.isRestoring.set(false);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/angular';
|
|
3
|
+
import { atom, map } from 'nanostores';
|
|
4
|
+
import type { ModalName } from '@block-auth.io/core';
|
|
5
|
+
import { ModalSigninComponent } from './modal-signin.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
|
+
// Fake client con stores nanostores-backed: BlockAuthService lee $ws/$ui/$wallet/
|
|
18
|
+
// $openModals, así que los cuatro deben existir. `openModal`/`closeModal`/
|
|
19
|
+
// `cancelTxns` son spies (BlockAuthService captura closeModal/openModal por
|
|
20
|
+
// referencia en construcción, por eso se fijan antes de render).
|
|
21
|
+
function makeClient() {
|
|
22
|
+
const $openModals = atom<ModalName[]>([]);
|
|
23
|
+
const openModal = vi.fn((m: ModalName) => $openModals.set([...$openModals.get(), m]));
|
|
24
|
+
const closeModal = vi.fn(() => $openModals.set([]));
|
|
25
|
+
const cancelTxns = vi.fn();
|
|
26
|
+
const client = {
|
|
27
|
+
state: { $ws: map({}), $ui: map({}), $wallet: map({}), $openModals },
|
|
28
|
+
openModal, closeModal, cancelTxns,
|
|
29
|
+
} as any;
|
|
30
|
+
return { client, $openModals, openModal, closeModal, cancelTxns };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Port de packages/react/src/components/modals/ModalSignin.test.tsx.
|
|
34
|
+
describe('ModalSignin', () => {
|
|
35
|
+
it('no renderiza cuando el modal está cerrado', async () => {
|
|
36
|
+
const { client } = makeClient();
|
|
37
|
+
await render(ModalSigninComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
38
|
+
expect(screen.queryByTestId('modal-signin')).toBeNull();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('renderiza el modal signin (teleportado a body) cuando está abierto', async () => {
|
|
42
|
+
const { client } = makeClient();
|
|
43
|
+
client.openModal('signin');
|
|
44
|
+
await render(ModalSigninComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
45
|
+
const overlay = screen.getByTestId('modal-signin');
|
|
46
|
+
expect(overlay).toBeDefined();
|
|
47
|
+
expect(overlay.parentElement).toBe(document.body);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('al cerrar (backdrop) llama cancelTxns() y luego closeModal(), en ese orden', async () => {
|
|
51
|
+
const { client } = makeClient();
|
|
52
|
+
const callOrder: string[] = [];
|
|
53
|
+
client.cancelTxns = vi.fn(() => callOrder.push('cancelTxns'));
|
|
54
|
+
client.closeModal = vi.fn(() => callOrder.push('closeModal'));
|
|
55
|
+
client.openModal('signin');
|
|
56
|
+
|
|
57
|
+
await render(ModalSigninComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
58
|
+
const backdrop = screen.getByTestId('modal-signin').querySelector('[aria-hidden="true"]');
|
|
59
|
+
expect(backdrop).not.toBeNull();
|
|
60
|
+
await fireEvent.click(backdrop!);
|
|
61
|
+
|
|
62
|
+
expect(client.cancelTxns).toHaveBeenCalledOnce();
|
|
63
|
+
expect(client.closeModal).toHaveBeenCalledOnce();
|
|
64
|
+
expect(callOrder).toEqual(['cancelTxns', 'closeModal']);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, computed, inject } from '@angular/core';
|
|
2
|
+
import { ModalOverlayComponent } from './modal-overlay.component';
|
|
3
|
+
import { ContentSigninComponent } from '../contents/content-signin.component';
|
|
4
|
+
import { BlockAuthService } from '../../block-auth.service';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Modal de sign-in fino, dirigido por el core. La visibilidad se deriva de
|
|
8
|
+
* `openModals().includes('signin')` y el cierre llama a `cancelTxns()` y luego
|
|
9
|
+
* a `closeModal()`. El ORDEN importa (idéntico al `.tsx`): `cancelTxns()`
|
|
10
|
+
* primero (emite `user:txns:update { state:'cancelled' }` sobre el socket) y
|
|
11
|
+
* `closeModal()` después (cierra localmente).
|
|
12
|
+
*
|
|
13
|
+
* El contenido real es `<ba-content-signin [closeTxns]="handleClose" />`
|
|
14
|
+
* (port de A9), que muestra el overview de transacciones / spinner de firma.
|
|
15
|
+
*/
|
|
16
|
+
@Component({
|
|
17
|
+
selector: 'ba-modal-signin',
|
|
18
|
+
standalone: true,
|
|
19
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
20
|
+
imports: [ModalOverlayComponent, ContentSigninComponent],
|
|
21
|
+
template: `
|
|
22
|
+
<ba-modal-overlay
|
|
23
|
+
testId="modal-signin"
|
|
24
|
+
[open]="open()"
|
|
25
|
+
panelClassName="xmd:bas-top-24 bas-w-full xsm1:bas-w-[380px] bas-overflow-hidden bas-bg-white bas-text-left bal-ba-shadow-base bal-ba-shadow-xl bas-h-[600px] bas-rounded-tl-lg bas-rounded-tr-lg xmd:bas-rounded-br-lg xmd:bas-rounded-bl-lg"
|
|
26
|
+
(closed)="handleClose()"
|
|
27
|
+
>
|
|
28
|
+
<div class="bas-flex bas-h-full bas-w-full bas-items-center bas-justify-center">
|
|
29
|
+
<div class="bas-relative bas-h-full bas-w-fit">
|
|
30
|
+
<ba-content-signin [closeTxns]="handleClose" />
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</ba-modal-overlay>
|
|
34
|
+
`,
|
|
35
|
+
})
|
|
36
|
+
export class ModalSigninComponent {
|
|
37
|
+
private readonly ba = inject(BlockAuthService);
|
|
38
|
+
readonly open = computed(() => this.ba.openModals().includes('signin'));
|
|
39
|
+
|
|
40
|
+
readonly handleClose = (): void => {
|
|
41
|
+
this.ba.cancelTxns();
|
|
42
|
+
this.ba.closeModal();
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/angular';
|
|
3
|
+
import { atom, map } from 'nanostores';
|
|
4
|
+
import type { ModalName } from '@block-auth.io/core';
|
|
5
|
+
import { ModalSignupComponent } from './modal-signup.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
|
+
function makeClient() {
|
|
18
|
+
const $openModals = atom<ModalName[]>([]);
|
|
19
|
+
const openModal = vi.fn((m: ModalName) => $openModals.set([...$openModals.get(), m]));
|
|
20
|
+
const closeModal = vi.fn(() => $openModals.set([]));
|
|
21
|
+
const client = {
|
|
22
|
+
state: { $ws: map({}), $ui: map({}), $wallet: map({}), $openModals },
|
|
23
|
+
openModal, closeModal,
|
|
24
|
+
} as any;
|
|
25
|
+
return { client, $openModals, openModal, closeModal };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Port de packages/react/src/components/modals/ModalSignup.test.tsx.
|
|
29
|
+
describe('ModalSignup', () => {
|
|
30
|
+
it('no renderiza cuando el modal está cerrado', async () => {
|
|
31
|
+
const { client } = makeClient();
|
|
32
|
+
await render(ModalSignupComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
33
|
+
expect(screen.queryByTestId('modal-signup')).toBeNull();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('renderiza el modal signup (teleportado a body) cuando está abierto', async () => {
|
|
37
|
+
const { client } = makeClient();
|
|
38
|
+
client.openModal('signup');
|
|
39
|
+
await render(ModalSignupComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
40
|
+
const overlay = screen.getByTestId('modal-signup');
|
|
41
|
+
expect(overlay).toBeDefined();
|
|
42
|
+
expect(overlay.parentElement).toBe(document.body);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('al hacer click en el backdrop llama client.closeModal()', async () => {
|
|
46
|
+
const { client, closeModal } = makeClient();
|
|
47
|
+
client.openModal('signup');
|
|
48
|
+
await render(ModalSignupComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
49
|
+
|
|
50
|
+
const backdrop = screen.getByTestId('modal-signup').querySelector('[aria-hidden="true"]');
|
|
51
|
+
expect(backdrop).not.toBeNull();
|
|
52
|
+
await fireEvent.click(backdrop!);
|
|
53
|
+
|
|
54
|
+
expect(closeModal).toHaveBeenCalledOnce();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, computed, inject } from '@angular/core';
|
|
2
|
+
import { ModalOverlayComponent } from './modal-overlay.component';
|
|
3
|
+
import { ContentSignupComponent } from '../contents/content-signup.component';
|
|
4
|
+
import { BlockAuthService } from '../../block-auth.service';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Modal de sign-up fino, dirigido por el core. La visibilidad se deriva de
|
|
8
|
+
* `openModals().includes('signup')` y el cierre delega en `closeModal()`.
|
|
9
|
+
*
|
|
10
|
+
* El contenido real es `<ba-content-signup [isEditable]="false"
|
|
11
|
+
* [isEditing]="false" />` (port de A9).
|
|
12
|
+
* TODO: restaurar el estado de carga `<OvalSpinner>` cuando la lib de
|
|
13
|
+
* componentes esté cableada en este paquete (igual que en el `.tsx`).
|
|
14
|
+
*/
|
|
15
|
+
@Component({
|
|
16
|
+
selector: 'ba-modal-signup',
|
|
17
|
+
standalone: true,
|
|
18
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
19
|
+
imports: [ModalOverlayComponent, ContentSignupComponent],
|
|
20
|
+
template: `
|
|
21
|
+
<ba-modal-overlay
|
|
22
|
+
testId="modal-signup"
|
|
23
|
+
[open]="open()"
|
|
24
|
+
panelClassName="xmd:bas-top-24 xmd:bas-m-1 bas-min-w-fit bas-overflow-hidden bas-bg-white bas-text-left bal-ba-shadow-base bal-ba-shadow-xl xmd:bas-my-8 sm1:bas-h-[364px] bas-w-full xmd:bas-w-fit lg:bas-h-[376px] bas-h-[508px] bas-rounded-tl-lg bas-rounded-tr-lg xmd:bas-rounded-br-lg xmd:bas-rounded-bl-lg"
|
|
25
|
+
(closed)="onClose()"
|
|
26
|
+
>
|
|
27
|
+
<div class="bas-box-border bas-h-full bas-w-full bas-px-4 bas-pb-4 bas-pt-6 sm1:bas-pt-5 xmd:bas-pb-3.5 lg:bas-px-6 lg:bas-pb-5 lg:bas-pt-[26px]">
|
|
28
|
+
<ba-content-signup [isEditable]="false" [isEditing]="false" />
|
|
29
|
+
</div>
|
|
30
|
+
</ba-modal-overlay>
|
|
31
|
+
`,
|
|
32
|
+
})
|
|
33
|
+
export class ModalSignupComponent {
|
|
34
|
+
private readonly ba = inject(BlockAuthService);
|
|
35
|
+
readonly open = computed(() => this.ba.openModals().includes('signup'));
|
|
36
|
+
|
|
37
|
+
onClose(): void {
|
|
38
|
+
this.ba.closeModal();
|
|
39
|
+
}
|
|
40
|
+
}
|