From a788419f7b24a5eb45a68a557019573eb59bf290 Mon Sep 17 00:00:00 2001 From: yosemite01 Date: Sun, 18 Oct 2026 15:41:26 +0100 Subject: [PATCH] Add ClearLocalData tests, speed up CI, fix share-button e2e test - Add Vitest coverage for the "clear local data" settings control: confirmation gating, flowstar*-only key removal, walletId preserved, and the success toast. - Raise Playwright CI workers from 1 to 2 to stop serialising the suite. - Run Playwright on chromium-desktop only for pull_request CI runs (full cross-browser matrix stays on pushes to main), and only install the browsers needed for each case. - Rewrite the stream-detail share-button e2e test to visit an existing mock stream (id "1") with a connected wallet and assert on the actual "Share stream" button, instead of a nonexistent stream id. Closes #913 Closes #910 Closes #909 Closes #904 Co-Authored-By: Claude Sonnet 5 --- .github/workflows/ci.yml | 21 ++++- .../components/clear-local-data.test.tsx | 84 +++++++++++++++++++ e2e/contract-integration.spec.ts | 21 ++++- playwright.config.ts | 2 +- 4 files changed, 120 insertions(+), 8 deletions(-) create mode 100644 __tests__/components/clear-local-data.test.tsx diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 08ab610..f8b5209 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,7 +55,14 @@ jobs: - run: npm ci - name: Install Playwright browsers - run: npx playwright install --with-deps chromium firefox webkit + # PRs only need chromium for the chromium-desktop project; pushes to + # main run the full cross-browser matrix. + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + npx playwright install --with-deps chromium + else + npx playwright install --with-deps chromium firefox webkit + fi - name: Build app in mock mode run: npm run build @@ -68,12 +75,20 @@ jobs: # workflow (workflow_dispatch). Until they are committed, run the # functional e2e tests and skip screenshot assertions instead of # silently regenerating (and so never actually comparing) them. + # PRs only run chromium-desktop for fast feedback; pushes to main + # keep running the full browser/device project matrix. run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + PROJECT_ARGS="--project=chromium-desktop" + else + PROJECT_ARGS="" + fi + if find e2e/__screenshots__ -name '*.png' | grep -q .; then - npx playwright test + npx playwright test $PROJECT_ARGS else echo "::warning::No visual baselines committed; skipping screenshot assertions." - npx playwright test --ignore-snapshots + npx playwright test --ignore-snapshots $PROJECT_ARGS fi env: NEXT_PUBLIC_STREAM_CONTRACT_ID_TESTNET: '' diff --git a/__tests__/components/clear-local-data.test.tsx b/__tests__/components/clear-local-data.test.tsx new file mode 100644 index 0000000..031b4b1 --- /dev/null +++ b/__tests__/components/clear-local-data.test.tsx @@ -0,0 +1,84 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' +import { render, screen } from '@testing-library/react' +import { ClearLocalData } from '@/components/settings/clear-local-data' + +const mockToastSuccess = vi.fn() + +vi.mock('sonner', () => ({ + toast: { + success: (...args: any[]) => mockToastSuccess(...args), + }, +})) + +describe('ClearLocalData', () => { + beforeEach(() => { + vi.useFakeTimers() + window.localStorage.clear() + mockToastSuccess.mockClear() + }) + + afterEach(() => { + vi.useRealTimers() + vi.restoreAllMocks() + }) + + it('requires a confirmation step before clearing', () => { + window.localStorage.setItem('flowstar:streams', '[]') + render() + + expect(screen.queryByText("Are you sure? This can't be undone.")).not.toBeInTheDocument() + + screen.getByText('Clear local data').click() + + expect(screen.getByText("Are you sure? This can't be undone.")).toBeInTheDocument() + // Clicking the initial button only arms the confirmation; nothing is removed yet. + expect(window.localStorage.getItem('flowstar:streams')).toBe('[]') + }) + + it('only removes keys prefixed with flowstar, keeping other keys like walletId', () => { + window.localStorage.setItem('flowstar:streams', '[]') + window.localStorage.setItem('flowstar_notifications', '{}') + window.localStorage.setItem('flowstar-network', 'testnet') + window.localStorage.setItem('walletId', 'abc123') + window.localStorage.setItem('unrelatedKey', 'keep-me') + + render() + + screen.getByText('Clear local data').click() + screen.getByText('Confirm clear').click() + + expect(window.localStorage.getItem('flowstar:streams')).toBeNull() + expect(window.localStorage.getItem('flowstar_notifications')).toBeNull() + expect(window.localStorage.getItem('flowstar-network')).toBeNull() + expect(window.localStorage.getItem('walletId')).toBe('abc123') + expect(window.localStorage.getItem('unrelatedKey')).toBe('keep-me') + }) + + it('shows a success toast reporting how many items were cleared', () => { + window.localStorage.setItem('flowstar:streams', '[]') + window.localStorage.setItem('flowstar_notifications', '{}') + window.localStorage.setItem('walletId', 'abc123') + + render() + + screen.getByText('Clear local data').click() + screen.getByText('Confirm clear').click() + + expect(mockToastSuccess).toHaveBeenCalledWith( + 'Cleared 2 items of local data', + expect.objectContaining({ description: 'Reloading…' }), + ) + }) + + it('cancel button dismisses the confirmation without clearing anything', () => { + window.localStorage.setItem('flowstar:streams', '[]') + render() + + screen.getByText('Clear local data').click() + screen.getByText('Cancel').click() + + expect(screen.queryByText("Are you sure? This can't be undone.")).not.toBeInTheDocument() + expect(window.localStorage.getItem('flowstar:streams')).toBe('[]') + expect(mockToastSuccess).not.toHaveBeenCalled() + }) +}) diff --git a/e2e/contract-integration.spec.ts b/e2e/contract-integration.spec.ts index abd6a28..5f2b61c 100644 --- a/e2e/contract-integration.spec.ts +++ b/e2e/contract-integration.spec.ts @@ -1,4 +1,17 @@ -import { test, expect } from '@playwright/test' +import { test, expect, type Page } from '@playwright/test' + +// ─── Shared helper: inject xBull mock so the wallet-gated stream detail page renders ─ +async function withWallet(page: Page) { + await page.addInitScript(() => { + localStorage.setItem('walletId', 'xbull') + ;(window as any).xBullSDK = { + connect: async () => ({ + publicKey: 'GA4M5Y74MXREPXU6LGRR7WOC6VLXJBLKJHZBJGDBKOFIP7GBM3MHF3G5', + }), + signXDR: async () => 'AAAAAgAAAAA...dummy-signature...', + } + }) +} test.describe('Contract Integration Tests', () => { test.describe('Wallet Connection & Dashboard', () => { @@ -39,9 +52,9 @@ test.describe('Contract Integration Tests', () => { }) test('should display share buttons on stream detail', async ({ page }) => { - await page.goto('/app/stream/test-id') - const shareBtn = page.locator('button:has-text("Share")') - // Should be clickable even if stream doesn't exist + await withWallet(page) + await page.goto('/app/stream/1') + const shareBtn = page.getByRole('button', { name: 'Share stream' }) await expect(shareBtn).toBeVisible() }) }) diff --git a/playwright.config.ts b/playwright.config.ts index a3d41a6..92607a5 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -5,7 +5,7 @@ export default defineConfig({ fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, - workers: process.env.CI ? 1 : undefined, + workers: process.env.CI ? 2 : undefined, reporter: "html", outputDir: "test-results", snapshotDir: "./e2e/__screenshots__",