diff --git a/bimex-frontend/e2e/fixtures/freighter.ts b/bimex-frontend/e2e/fixtures/freighter.ts index 0c64cb3..92908fc 100644 --- a/bimex-frontend/e2e/fixtures/freighter.ts +++ b/bimex-frontend/e2e/fixtures/freighter.ts @@ -38,6 +38,9 @@ export async function mockFreighterConnected(page: Page): Promise { // isConnected() short-circuits when window.freighter is truthy ;(window as any).freighter = true + // walletKit.getConnectedAddress() requires this to attempt auto-reconnect + localStorage.setItem('lastWallet', 'freighter') + // Intercept postMessage to fake Freighter extension responses const _postMessage = window.postMessage.bind(window) window.postMessage = (data: any, targetOrigin: any, transfer?: any) => { @@ -79,6 +82,7 @@ export async function mockFreighterConnected(page: Page): Promise { export async function mockFreighterDisconnected(page: Page): Promise { await page.addInitScript(() => { ;(window as any).freighter = undefined + localStorage.removeItem('lastWallet') const _postMessage = window.postMessage.bind(window) window.postMessage = (data: any, targetOrigin: any, transfer?: any) => { diff --git a/bimex-frontend/e2e/golden-path.spec.ts b/bimex-frontend/e2e/golden-path.spec.ts index 59243ef..95960ff 100644 --- a/bimex-frontend/e2e/golden-path.spec.ts +++ b/bimex-frontend/e2e/golden-path.spec.ts @@ -59,10 +59,10 @@ test.describe('Golden Path – flujo completo connect wallet → invertir → yi }) await page.goto('/') - const heroBtn = page.getByRole('button', { name: /freighter/i }).first() - await expect(heroBtn).toBeVisible({ timeout: 10_000 }) + const hero = page.locator('.landing-hero-section') + await expect(hero.getByRole('button', { name: /huella|face id|passkey|conectar/i }).first()).toBeVisible({ timeout: 10_000 }) - const navBtn = page.getByRole('button', { name: /^conectar$/i }) + const navBtn = page.locator('nav[aria-label="Navegación principal"]').getByRole('button', { name: /conectar/i }) await expect(navBtn).toBeVisible() await expect(page).toHaveURL('/') diff --git a/bimex-frontend/e2e/landing.spec.ts b/bimex-frontend/e2e/landing.spec.ts index d7e2eaa..f485ddf 100644 --- a/bimex-frontend/e2e/landing.spec.ts +++ b/bimex-frontend/e2e/landing.spec.ts @@ -11,13 +11,13 @@ test.describe('Landing – sin wallet', () => { await page.goto('/') }) - test('muestra el botón "Freighter" en el hero', async ({ page }) => { - const heroBtn = page.getByRole('button', { name: /freighter/i }).first() - await expect(heroBtn).toBeVisible() + test('muestra los botones de conexión en el hero', async ({ page }) => { + const hero = page.locator('.landing-hero-section') + await expect(hero.getByRole('button', { name: /huella|face id|passkey|conectar/i }).first()).toBeVisible() }) test('muestra el botón "Conectar" en la navbar del landing', async ({ page }) => { - const navBtn = page.getByRole('button', { name: /^conectar$/i }) + const navBtn = page.locator('nav[aria-label="Navegación principal"]').getByRole('button', { name: /conectar/i }) await expect(navBtn).toBeVisible() }) diff --git a/bimex-frontend/src/stellar/walletKit.js b/bimex-frontend/src/stellar/walletKit.js index 3637cb8..d04eaaa 100644 --- a/bimex-frontend/src/stellar/walletKit.js +++ b/bimex-frontend/src/stellar/walletKit.js @@ -1,8 +1,6 @@ import { StellarWalletsKit, FreighterModule, WalletNetwork } from "@creit.tech/stellar-wallets-kit"; import { CONFIG } from "./contrato.js"; -const _isMainnet = CONFIG.NETWORK === "mainnet"; - let kit = null; function initializeKit() { @@ -17,8 +15,10 @@ function initializeKit() { }).catch(() => {}); } + const isMainnet = CONFIG.NETWORK === "mainnet"; + kit = new StellarWalletsKit({ - network: _isMainnet ? WalletNetwork.PUBLIC : WalletNetwork.TESTNET, + network: isMainnet ? WalletNetwork.PUBLIC : WalletNetwork.TESTNET, selectedWalletId: "freighter", modules, }); diff --git a/bimex-frontend/src/test/ConectarWallet.test.jsx b/bimex-frontend/src/test/ConectarWallet.test.jsx index 5e9226d..52dad7b 100644 --- a/bimex-frontend/src/test/ConectarWallet.test.jsx +++ b/bimex-frontend/src/test/ConectarWallet.test.jsx @@ -2,21 +2,12 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { cleanup, render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import ConectarWallet from "../components/ConectarWallet.jsx"; -import { - getAddress, - getNetwork, - isAllowed, - isConnected, - requestAccess, -} from "@stellar/freighter-api"; +import { getConnectedAddress, openWalletModal } from "../stellar/walletKit.js"; import "../i18n/index.js"; -vi.mock("@stellar/freighter-api", () => ({ - isConnected: vi.fn(), - isAllowed: vi.fn(), - requestAccess: vi.fn(), - getAddress: vi.fn(), - getNetwork: vi.fn(), +vi.mock("../stellar/walletKit.js", () => ({ + getConnectedAddress: vi.fn(), + openWalletModal: vi.fn(), })); vi.mock("../stellar/contrato", () => ({ @@ -37,11 +28,8 @@ import { obtenerEstadoTrustlineMXNe, crearTrustlineMXNe } from "../stellar/contr beforeEach(() => { vi.clearAllMocks(); - isConnected.mockResolvedValue({ isConnected: false }); - isAllowed.mockResolvedValue({ isAllowed: false }); - requestAccess.mockResolvedValue({}); - getNetwork.mockResolvedValue({ networkPassphrase: "Test SDF Network ; September 2015" }); - getAddress.mockResolvedValue({ address: "" }); + getConnectedAddress.mockResolvedValue(null); + openWalletModal.mockResolvedValue(null); obtenerEstadoTrustlineMXNe.mockResolvedValue({ aplica: true, tieneTrustline: true, @@ -60,15 +48,13 @@ describe("ConectarWallet", () => { it("muestra el botón de conexión cuando no hay wallet conectada", () => { render(); - expect(screen.getByRole("button", { name: "Freighter" })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Connect" })).toBeInTheDocument(); }); it("muestra la dirección truncada cuando ya existe una sesión autorizada", async () => { const address = "GABC1234567890WXYZ"; const onConectado = vi.fn(); - isConnected.mockResolvedValueOnce({ isConnected: true }); - isAllowed.mockResolvedValueOnce({ isAllowed: true }); - getAddress.mockResolvedValueOnce({ address }); + getConnectedAddress.mockResolvedValueOnce(address); render(); @@ -80,14 +66,11 @@ describe("ConectarWallet", () => { it("llama onConectado con la dirección al conectar manualmente", async () => { const address = "GDEF1234567890QRST"; const onConectado = vi.fn(); - isConnected.mockResolvedValue({ isConnected: true }); - requestAccess.mockResolvedValue({}); - getNetwork.mockResolvedValue({ networkPassphrase: "Test SDF Network ; September 2015" }); - getAddress.mockResolvedValue({ address }); + openWalletModal.mockResolvedValueOnce(address); render(); - await userEvent.click(screen.getByRole("button", { name: "Freighter" })); + await userEvent.click(screen.getByRole("button", { name: "Connect" })); await waitFor(() => { expect(onConectado).toHaveBeenCalledWith(address); @@ -97,9 +80,7 @@ describe("ConectarWallet", () => { it("muestra banner cuando falta trustline MXNe", async () => { const address = "GABC1234567890WXYZ"; - isConnected.mockResolvedValueOnce({ isConnected: true }); - isAllowed.mockResolvedValueOnce({ isAllowed: true }); - getAddress.mockResolvedValueOnce({ address }); + getConnectedAddress.mockResolvedValueOnce(address); obtenerEstadoTrustlineMXNe.mockResolvedValueOnce({ aplica: true, tieneTrustline: false, @@ -117,9 +98,7 @@ describe("ConectarWallet", () => { it("no muestra banner cuando ya tiene trustline", async () => { const address = "GABC1234567890WXYZ"; - isConnected.mockResolvedValueOnce({ isConnected: true }); - isAllowed.mockResolvedValueOnce({ isAllowed: true }); - getAddress.mockResolvedValueOnce({ address }); + getConnectedAddress.mockResolvedValueOnce(address); render(); @@ -129,9 +108,7 @@ describe("ConectarWallet", () => { it("muestra instrucciones de Friendbot cuando no hay XLM suficiente", async () => { const address = "GABC1234567890WXYZ"; - isConnected.mockResolvedValueOnce({ isConnected: true }); - isAllowed.mockResolvedValueOnce({ isAllowed: true }); - getAddress.mockResolvedValueOnce({ address }); + getConnectedAddress.mockResolvedValueOnce(address); obtenerEstadoTrustlineMXNe.mockResolvedValueOnce({ aplica: true, tieneTrustline: false, @@ -149,9 +126,7 @@ describe("ConectarWallet", () => { it("crea trustline al hacer clic en habilitar", async () => { const address = "GABC1234567890WXYZ"; - isConnected.mockResolvedValueOnce({ isConnected: true }); - isAllowed.mockResolvedValueOnce({ isAllowed: true }); - getAddress.mockResolvedValueOnce({ address }); + getConnectedAddress.mockResolvedValueOnce(address); obtenerEstadoTrustlineMXNe.mockResolvedValueOnce({ aplica: true, tieneTrustline: false, diff --git a/bimex-frontend/src/test/accessibility.test.jsx b/bimex-frontend/src/test/accessibility.test.jsx index d091226..f6a082f 100644 --- a/bimex-frontend/src/test/accessibility.test.jsx +++ b/bimex-frontend/src/test/accessibility.test.jsx @@ -32,6 +32,11 @@ vi.mock('passkey-kit', () => ({ PasskeyKit: class { sign = vi.fn() } })); +vi.mock('../stellar/walletKit.js', () => ({ + getConnectedAddress: vi.fn().mockResolvedValue(null), + openWalletModal: vi.fn().mockResolvedValue(null), +})); + import { render, waitFor } from '@testing-library/react'; import { act } from 'react'; import { MemoryRouter } from 'react-router-dom'; diff --git a/bimex-frontend/src/test/contrato.test.js b/bimex-frontend/src/test/contrato.test.js index 39b46eb..5401469 100644 --- a/bimex-frontend/src/test/contrato.test.js +++ b/bimex-frontend/src/test/contrato.test.js @@ -2,7 +2,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; const mockLoadAccount = vi.fn(); const mockSubmitTransaction = vi.fn(); -const mockSignTransaction = vi.fn(); +const mockSignWithWalletKit = vi.fn(); vi.mock('@stellar/stellar-sdk', () => ({ Contract: vi.fn(), @@ -42,7 +42,6 @@ vi.mock('@stellar/stellar-sdk', () => ({ })); vi.mock('@stellar/freighter-api', () => ({ - signTransaction: (...args) => mockSignTransaction(...args), isConnected: vi.fn(), isAllowed: vi.fn(), requestAccess: vi.fn(), @@ -55,6 +54,10 @@ vi.mock('passkey-kit', () => ({ PasskeyKit: class { sign = vi.fn() } })); +vi.mock('../stellar/walletKit.js', () => ({ + signWithWalletKit: (...args) => mockSignWithWalletKit(...args), +})); + import { stroopsAMXNe, mxneAStroops, @@ -190,15 +193,12 @@ describe('trustline MXNe', () => { sequenceNumber: () => '1', }); - mockSignTransaction.mockResolvedValueOnce({ - signedTxXdr: 'signed-xdr', - error: null, - }); + mockSignWithWalletKit.mockResolvedValueOnce('signed-xdr'); mockSubmitTransaction.mockResolvedValueOnce({ hash: 'trust-hash' }); const resultado = await crearTrustlineMXNe('GABC1234567890WXYZ'); expect(resultado.hash).toBe('trust-hash'); - expect(mockSignTransaction).toHaveBeenCalled(); + expect(mockSignWithWalletKit).toHaveBeenCalled(); expect(mockSubmitTransaction).toHaveBeenCalled(); }); diff --git a/bimex-frontend/src/test/metaTags.test.js b/bimex-frontend/src/test/metaTags.test.js index eda16c5..7305e6c 100644 --- a/bimex-frontend/src/test/metaTags.test.js +++ b/bimex-frontend/src/test/metaTags.test.js @@ -27,6 +27,7 @@ describe("Bimex metadata", () => { id: 42, nombre: "Biblioteca Solar", meta: 200000000n, + aportado: 100000000n, estado: "Liberado", }); @@ -34,11 +35,17 @@ describe("Bimex metadata", () => { expect(document.title).toBe("Biblioteca Solar — Bimex"); expect(document.head.querySelector('meta[property="og:title"]')?.content).toBe("Biblioteca Solar — Bimex"); - expect(document.head.querySelector('meta[property="og:description"]')?.content).toBe("Meta: 20 MXNe · Liberado"); + expect(document.head.querySelector('meta[property="og:description"]')?.content).toBe("10 MXNe recaudados · faltan 10 MXNe para la meta"); expect(document.head.querySelector('meta[property="og:url"]')?.content).toBe("https://bimex-frontend.vercel.app/proyectos/42"); expect(document.head.querySelector('meta[name="twitter:image"]')?.content).toBe("https://bimex-frontend.vercel.app/og-image.png"); }); + it("defaults missing aportado/meta to zero without crashing on BigInt arithmetic", () => { + const meta = crearMetaProyecto({ id: 7, nombre: "Sin datos" }); + + expect(meta.description).toBe("0 MXNe recaudados · faltan 0 MXNe para la meta"); + }); + it("parses project IDs from share paths", () => { expect(leerProyectoIdDesdePath("/proyectos/42")).toBe(42); expect(leerProyectoIdDesdePath("/proyectos/42/")).toBe(42); diff --git a/bimex-frontend/src/test/stellar.test.js b/bimex-frontend/src/test/stellar.test.js index dd52f9b..32dbc00 100644 --- a/bimex-frontend/src/test/stellar.test.js +++ b/bimex-frontend/src/test/stellar.test.js @@ -6,10 +6,10 @@ import { esDireccionValida, esContractIdValido } from '../utils/stellar.js'; // valid base32 charset) so StrKey can decode them properly. // Valid Ed25519 public key (G-address) — 56 chars, correct checksum. -const DIRECCION_VALIDA = 'GAAHI26FKE4GGBHMMXFYUTGOXJB6ZGLHOMVQIBOIIZH2KFZJBVFCMVH'; +const DIRECCION_VALIDA = 'GCWXHQPEOQEVUJ2NLIVNAOMYOJ5N5L4W753EPV7R4PU7WWNBSEJMZSU2'; // Valid Soroban contract ID (C-address) — 56 chars, correct checksum. -const CONTRACT_ID_VALIDO = 'CAHJJJKK6EXBRUXZGPQE5ZDDPVB5ZFJFUQKDNQZUBQLBQ6HB5PQHGJZ'; +const CONTRACT_ID_VALIDO = 'CCWXHQPEOQEVUJ2NLIVNAOMYOJ5N5L4W753EPV7R4PU7WWNBSEJMYWRD'; describe('esDireccionValida', () => { it('returns true for a valid G-address', () => { diff --git a/bimex-frontend/src/utils/metaTags.js b/bimex-frontend/src/utils/metaTags.js index 00366cd..9f84171 100644 --- a/bimex-frontend/src/utils/metaTags.js +++ b/bimex-frontend/src/utils/metaTags.js @@ -21,8 +21,10 @@ function setMetaTag(selector, attrs) { export function crearMetaProyecto(proyecto) { if (!proyecto) return DEFAULT_META; const nombre = proyecto.nombre || "Proyecto Bimex"; - const raised = stroopsAMXNe(proyecto.aportado ?? 0); - const remaining = stroopsAMXNe((proyecto.meta ?? 0) > (proyecto.aportado ?? 0) ? (proyecto.meta - proyecto.aportado) : 0n); + const meta = BigInt(proyecto.meta ?? 0); + const aportado = BigInt(proyecto.aportado ?? 0); + const raised = stroopsAMXNe(aportado); + const remaining = stroopsAMXNe(meta > aportado ? meta - aportado : 0n); return { title: `${nombre} — Bimex`, description: `${raised} recaudados · faltan ${remaining} para la meta`,