diff --git a/__tests__/components/address-book-settings.test.tsx b/__tests__/components/address-book-settings.test.tsx
new file mode 100644
index 0000000..4341db7
--- /dev/null
+++ b/__tests__/components/address-book-settings.test.tsx
@@ -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()
+
+ 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()
+
+ 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()
+
+ 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()
+ })
+})
diff --git a/__tests__/components/notification-bell.test.tsx b/__tests__/components/notification-bell.test.tsx
new file mode 100644
index 0000000..8a1d007
--- /dev/null
+++ b/__tests__/components/notification-bell.test.tsx
@@ -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
+
+function makeNotification(overrides?: Partial>) {
+ 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[]) {
+ 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()
+ expect(screen.getByText('1')).toBeInTheDocument()
+ })
+
+ it('toggle opens and closes the panel', async () => {
+ const user = userEvent.setup()
+ render()
+
+ 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(
+
+
+
+
,
+ )
+
+ 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()
+
+ 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()
+
+ 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()
+ })
+})
diff --git a/__tests__/components/notification-preferences-settings.test.tsx b/__tests__/components/notification-preferences-settings.test.tsx
new file mode 100644
index 0000000..48f4a82
--- /dev/null
+++ b/__tests__/components/notification-preferences-settings.test.tsx
@@ -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()
+ 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()
+
+ 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)
+ })
+})
diff --git a/e2e/landing.spec.ts b/e2e/landing.spec.ts
index fabc6c9..cdc9b6e 100644
--- a/e2e/landing.spec.ts
+++ b/e2e/landing.spec.ts
@@ -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()