Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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: ''
Expand Down
84 changes: 84 additions & 0 deletions __tests__/components/clear-local-data.test.tsx
Original file line number Diff line number Diff line change
@@ -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(<ClearLocalData />)

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(<ClearLocalData />)

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(<ClearLocalData />)

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(<ClearLocalData />)

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()
})
})
7 changes: 4 additions & 3 deletions e2e/contract-integration.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { test, expect, type Page } from '@playwright/test'

// ─── Shared helper: inject xBull mock so the wallet-gated stream detail page renders ─
// The create form is wrapped in `RequireWallet`, so without a connected
// wallet it only renders a connect prompt instead of the real form fields.
// Mirrors the helper used in e2e/accessibility.spec.ts.
Expand Down Expand Up @@ -54,9 +55,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()
})
})
Expand Down
2 changes: 1 addition & 1 deletion playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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__",
Expand Down
Loading