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
58 changes: 58 additions & 0 deletions __tests__/components/address-book-settings.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { AddressBookSettings } from '@/components/settings/address-book-settings'
import { addAddressBookEntry } from '@/lib/address-book'

vi.mock('sonner', () => ({
toast: { success: vi.fn(), error: vi.fn() },
}))

describe('AddressBookSettings', () => {
beforeEach(() => {
localStorage.clear()
})

it('renders saved entries', () => {
addAddressBookEntry({ label: 'Alice', address: 'GALICE111' })
addAddressBookEntry({ label: 'Bob', address: 'GBOB222' })

render(<AddressBookSettings />)

expect(screen.getByText('Alice')).toBeInTheDocument()
expect(screen.getByText('GALICE111')).toBeInTheDocument()
expect(screen.getByText('Bob')).toBeInTheDocument()
expect(screen.getByText('GBOB222')).toBeInTheDocument()
})

it('edit then save updates the entry', async () => {
const user = userEvent.setup()
addAddressBookEntry({ label: 'Alice', address: 'GALICE111' })

render(<AddressBookSettings />)

await user.click(screen.getByRole('button', { name: 'Edit label' }))
const input = screen.getByDisplayValue('Alice')
await user.clear(input)
await user.type(input, 'Alice Updated')
await user.click(screen.getByRole('button', { name: 'Save' }))

expect(screen.getByText('Alice Updated')).toBeInTheDocument()
expect(screen.queryByText('Alice')).not.toBeInTheDocument()
})

it('delete removes the entry', async () => {
const user = userEvent.setup()
addAddressBookEntry({ label: 'Alice', address: 'GALICE111' })

render(<AddressBookSettings />)

await user.click(screen.getByRole('button', { name: 'Delete' }))
await user.click(screen.getByRole('button', { name: 'Confirm' }))

expect(screen.queryByText('Alice')).not.toBeInTheDocument()
expect(
screen.getByText('No saved addresses yet. Addresses you save while creating a stream will appear here.'),
).toBeInTheDocument()
})
})
100 changes: 100 additions & 0 deletions __tests__/components/notification-bell.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render, screen, fireEvent } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { NotificationBell } from '@/components/layout/notification-bell'
import { useNotifications } from '@/hooks/use-notifications'

vi.mock('@/hooks/use-wallet', () => ({ useWallet: vi.fn(() => ({ address: 'GSENDER111' })) }))
vi.mock('@/hooks/use-notifications', () => ({ useNotifications: vi.fn() }))

const mockedUseNotifications = useNotifications as unknown as ReturnType<typeof vi.fn>

function makeNotification(overrides?: Partial<Record<string, unknown>>) {
return {
id: 'notif-1',
type: 'stream_created',
title: 'Stream created',
body: 'A new stream was created for you.',
timestamp: Date.now(),
read: false,
...overrides,
}
}

function setNotifications(notifications: ReturnType<typeof makeNotification>[]) {
mockedUseNotifications.mockReturnValue({
notifications,
unreadCount: notifications.filter((n) => !n.read).length,
markAllRead: vi.fn(),
clearAll: vi.fn(),
dismissNotification: vi.fn(),
})
}

describe('NotificationBell', () => {
beforeEach(() => {
setNotifications([])
})

it('shows the unread badge count', () => {
setNotifications([makeNotification({ id: 'a' }), makeNotification({ id: 'b', read: true })])
render(<NotificationBell />)
expect(screen.getByText('1')).toBeInTheDocument()
})

it('toggle opens and closes the panel', async () => {
const user = userEvent.setup()
render(<NotificationBell />)

expect(screen.queryByRole('menu')).not.toBeInTheDocument()

await user.click(screen.getByTestId('notification-bell-trigger'))
expect(screen.getByRole('menu')).toBeInTheDocument()

await user.click(screen.getByTestId('notification-bell-trigger'))
expect(screen.queryByRole('menu')).not.toBeInTheDocument()
})

it('click outside closes it', async () => {
const user = userEvent.setup()
render(
<div>
<NotificationBell />
<button>outside</button>
</div>,
)

await user.click(screen.getByTestId('notification-bell-trigger'))
expect(screen.getByRole('menu')).toBeInTheDocument()

await user.click(screen.getByText('outside'))
expect(screen.queryByRole('menu')).not.toBeInTheDocument()
})

it('Escape closes it', async () => {
const user = userEvent.setup()
render(<NotificationBell />)

await user.click(screen.getByTestId('notification-bell-trigger'))
expect(screen.getByRole('menu')).toBeInTheDocument()

fireEvent.keyDown(document, { key: 'Escape' })
expect(screen.queryByRole('menu')).not.toBeInTheDocument()
})

it('each notification renders title and body', async () => {
const user = userEvent.setup()
setNotifications([
makeNotification({ id: 'a', title: 'Stream created', body: 'First stream body' }),
makeNotification({ id: 'b', title: 'Withdrawal', body: 'Second stream body' }),
])
render(<NotificationBell />)

await user.click(screen.getByTestId('notification-bell-trigger'))

expect(screen.getByText('Stream created')).toBeInTheDocument()
expect(screen.getByText('First stream body')).toBeInTheDocument()
expect(screen.getByText('Withdrawal')).toBeInTheDocument()
expect(screen.getByText('Second stream body')).toBeInTheDocument()
})
})
41 changes: 41 additions & 0 deletions __tests__/components/notification-preferences-settings.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { NotificationPreferencesSettings } from '@/components/notifications/notification-preferences'

vi.mock('sonner', () => ({
toast: { warning: vi.fn() },
}))

describe('NotificationPreferencesSettings', () => {
beforeEach(() => {
localStorage.clear()
})

it('renders a toggle per notification type', () => {
render(<NotificationPreferencesSettings />)
const switches = screen.getAllByRole('switch')
expect(switches).toHaveLength(3)
expect(screen.getByText('Stream created')).toBeInTheDocument()
expect(screen.getByText('Stream cancelled')).toBeInTheDocument()
expect(screen.getByText('Withdrawal')).toBeInTheDocument()
})

it('toggling persists via the hook', async () => {
const user = userEvent.setup()
render(<NotificationPreferencesSettings />)

const toggle = screen.getByRole('switch', {
name: 'Disable Stream created notifications',
})
expect(toggle).toHaveAttribute('aria-checked', 'true')

await user.click(toggle)

expect(toggle).toHaveAttribute('aria-checked', 'false')
expect(toggle).toHaveAccessibleName('Enable Stream created notifications')

const stored = JSON.parse(localStorage.getItem('flowstar:notification-preferences') || '{}')
expect(stored.stream_created).toBe(false)
})
})
2 changes: 1 addition & 1 deletion e2e/landing.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { test, expect } from '@playwright/test'
test.describe('Landing page', () => {
test('renders hero and navigates to app', async ({ page }) => {
await page.goto('/')
await expect(page.locator('text=FlowStar')).toBeVisible()
await expect(page.getByRole('banner').getByText('FlowStar', { exact: true })).toBeVisible()
await expect(page.locator('text=Open app').first()).toBeVisible()

await page.locator('text=Open app').first().click()
Expand Down
Loading