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
122 changes: 122 additions & 0 deletions ui/mobile/tests/component/bulkActionBar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import React from 'react'
import { fireEvent, render, screen } from '@testing-library/react-native'
import { BulkActionBar } from '@ui/mobile/components/BulkActionBar'

jest.mock('@ui/mobile/providers', () => ({
useTheme: () => ({
colors: {
background: '#ffffff',
border: '#e0e0e0',
primary: '#007aff',
mutedForeground: '#666666',
destructive: '#ff3b30',
destructiveForeground: '#ffffff',
},
}),
}))

describe('BulkActionBar', () => {
const defaultProps = {
selectedCount: 2,
totalCount: 5,
onSelectAll: jest.fn(),
onDeselectAll: jest.fn(),
onDelete: jest.fn(),
isPending: false,
}

beforeEach(() => {
jest.clearAllMocks()
})

it('renders selected count and select all button text', () => {
render(<BulkActionBar {...defaultProps} />)

expect(screen.getByText('2 selected')).toBeTruthy()
expect(screen.getByText('Select All (5)')).toBeTruthy()
})

it('triggers onSelectAll when select all button is pressed', () => {
const onSelectAll = jest.fn()
render(<BulkActionBar {...defaultProps} onSelectAll={onSelectAll} />)

fireEvent.press(screen.getByText('Select All (5)'))

expect(onSelectAll).toHaveBeenCalledTimes(1)
})

it('shows Deselect All and triggers onDeselectAll when all items are selected', () => {
const onDeselectAll = jest.fn()
render(
<BulkActionBar
{...defaultProps}
selectedCount={5}
totalCount={5}
onDeselectAll={onDeselectAll}
/>
)

expect(screen.getByText('Deselect All')).toBeTruthy()
expect(screen.getByText('5 selected')).toBeTruthy()

fireEvent.press(screen.getByText('Deselect All'))

expect(onDeselectAll).toHaveBeenCalledTimes(1)
})

it('disables delete button when selectedCount is 0', () => {
const onDelete = jest.fn()
render(
<BulkActionBar
{...defaultProps}
selectedCount={0}
totalCount={5}
onDelete={onDelete}
/>
)

const deleteButton = screen.getByRole('button', { name: 'Delete 0 notes' })
expect(deleteButton.props.accessibilityState.disabled).toBe(true)

fireEvent.press(deleteButton)
expect(onDelete).not.toHaveBeenCalled()
})

it('disables delete button when isPending is true', () => {
const onDelete = jest.fn()
render(
<BulkActionBar
{...defaultProps}
selectedCount={2}
totalCount={5}
isPending={true}
onDelete={onDelete}
/>
)

const deleteButton = screen.getByRole('button', { name: 'Delete 2 notes' })
expect(deleteButton.props.accessibilityState.disabled).toBe(true)

fireEvent.press(deleteButton)
expect(onDelete).not.toHaveBeenCalled()
})

it('enables delete button and triggers onDelete when pressed with selectedCount > 0 and isPending is false', () => {
const onDelete = jest.fn()
render(
<BulkActionBar
{...defaultProps}
selectedCount={3}
totalCount={5}
isPending={false}
onDelete={onDelete}
/>
)

const deleteButton = screen.getByRole('button', { name: 'Delete 3 notes' })
expect(deleteButton.props.accessibilityState.disabled).toBe(false)

fireEvent.press(deleteButton)
expect(onDelete).toHaveBeenCalledTimes(1)
})
})
43 changes: 43 additions & 0 deletions ui/mobile/tests/component/comingSoonBadge.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react'
import { View } from 'react-native'
import { render, screen, createMockTheme } from '../testUtils'
import { ComingSoonBadge } from '@ui/mobile/components/settings/ComingSoonBadge'

const mockThemeColors = {
...createMockTheme().colors,
muted: '#f5f5f5',
mutedForeground: '#777777',
}

jest.mock('@ui/mobile/providers', () => ({
useTheme: () => ({
colors: mockThemeColors,
}),
}))

describe('ComingSoonBadge component', () => {
it('renders "Soon" text inside the badge', () => {
render(<ComingSoonBadge />)

expect(screen.getByText('Soon')).toBeTruthy()
})

it('applies colors.muted background style and colors.mutedForeground text style from useTheme', () => {
const { UNSAFE_getByType } = render(<ComingSoonBadge />)

const textElement = screen.getByText('Soon')
expect(textElement.props.style).toEqual(
expect.objectContaining({
color: '#777777',
fontSize: 11,
})
)

const badgeContainer = UNSAFE_getByType(View)
expect(badgeContainer.props.style).toEqual(
expect.objectContaining({
backgroundColor: '#f5f5f5',
})
)
})
})
44 changes: 44 additions & 0 deletions ui/mobile/tests/component/noteBodyPreview.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from 'react'
import { View } from 'react-native'
import { render, screen } from '../testUtils'
import { NoteBodyPreview } from '@ui/mobile/components/NoteBodyPreview'
import { colors as themeColors } from '@ui/mobile/lib/theme'

describe('NoteBodyPreview component', () => {
it('renders empty View container when HTML content parses to empty string or whitespace', () => {
const { UNSAFE_getByType, rerender } = render(
<NoteBodyPreview html="" colors={themeColors.light} />
)

expect(screen.queryByText(/./)).toBeNull()
const emptyView = UNSAFE_getByType(View)
expect(emptyView.props.style).toEqual(
expect.objectContaining({
backgroundColor: themeColors.light.background,
})
)

rerender(<NoteBodyPreview html="<p> </p>" colors={themeColors.light} />)
expect(screen.queryByText(/./)).toBeNull()
})

it('renders converted plain text inside Text within ScrollView when HTML contains text', () => {
render(<NoteBodyPreview html="<p>Hello world</p>" colors={themeColors.light} />)

const textElement = screen.getByText('Hello world')
expect(textElement).toBeTruthy()
})

it('applies proper background color and text color based on colors prop', () => {
render(<NoteBodyPreview html="<div>Test Content</div>" colors={themeColors.light} />)

const textElement = screen.getByText('Test Content')
expect(textElement.props.style).toEqual(
expect.objectContaining({
color: themeColors.light.foreground,
fontSize: 16,
lineHeight: 28,
})
)
})
})
89 changes: 89 additions & 0 deletions ui/mobile/tests/component/settingsRow.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import React from 'react'
import { Text } from 'react-native'
import { fireEvent, render, screen } from '@testing-library/react-native'
import { SettingsRow } from '@ui/mobile/components/settings/SettingsRow'

jest.mock('@ui/mobile/providers', () => ({
useTheme: () => ({
colors: {
card: '#ffffff',
border: '#e0e0e0',
muted: '#f0f0f0',
foreground: '#000000',
mutedForeground: '#666666',
},
}),
}))

jest.mock('lucide-react-native', () => {
const React = require('react')
const { Text } = require('react-native')
return {
ChevronRight: () => React.createElement(Text, { testID: 'ChevronRight' }, 'ChevronRight'),
}
})

describe('SettingsRow', () => {
it('renders title and optional subtitle', () => {
render(<SettingsRow title="Account" subtitle="Manage profile" />)

expect(screen.getByText('Account')).toBeTruthy()
expect(screen.getByText('Manage profile')).toBeTruthy()
})

it('renders right node passed in props', () => {
render(
<SettingsRow
title="Notifications"
right={<Text testID="badge">Enabled</Text>}
/>
)

expect(screen.getByTestId('badge')).toBeTruthy()
expect(screen.getByText('Enabled')).toBeTruthy()
})

it('invokes onPress when pressed and not disabled', () => {
const onPress = jest.fn()
render(<SettingsRow title="Clickable" onPress={onPress} />)

fireEvent.press(screen.getByText('Clickable'))

expect(onPress).toHaveBeenCalledTimes(1)
})

it('does not invoke onPress when disabled is true', () => {
const onPress = jest.fn()
render(<SettingsRow title="Disabled" onPress={onPress} disabled={true} />)

fireEvent.press(screen.getByText('Disabled'))

expect(onPress).not.toHaveBeenCalled()
})

it('renders chevron only when showChevron is true, onPress is provided, and disabled is false', () => {
const { rerender } = render(<SettingsRow title="Option" onPress={jest.fn()} />)
expect(screen.queryByTestId('ChevronRight')).toBeTruthy()

rerender(<SettingsRow title="Option" onPress={jest.fn()} showChevron={false} />)
expect(screen.queryByTestId('ChevronRight')).toBeNull()

rerender(<SettingsRow title="Option" showChevron={true} />)
expect(screen.queryByTestId('ChevronRight')).toBeNull()

rerender(<SettingsRow title="Option" onPress={jest.fn()} disabled={true} />)
expect(screen.queryByTestId('ChevronRight')).toBeNull()
})

it('applies top rounded corners for isFirst and bottom rounded corners for isLast', () => {
render(<SettingsRow title="Row" isFirst={true} isLast={true} />)

const row = screen.getByLabelText('Row')
const flatStyle = Object.assign({}, ...(Array.isArray(row.props.style) ? row.props.style : [row.props.style]))

expect(flatStyle.borderTopLeftRadius).toBe(12)
expect(flatStyle.borderTopRightRadius).toBe(12)
expect(flatStyle.borderBottomLeftRadius).toBe(12)
expect(flatStyle.borderBottomRightRadius).toBe(12)
})
})
33 changes: 33 additions & 0 deletions ui/mobile/tests/component/settingsSectionHeader.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react'
import { render, screen } from '@testing-library/react-native'
import { SettingsSectionHeader } from '@ui/mobile/components/settings/SettingsSectionHeader'

const mockColors = {
mutedForeground: '#6b7280',
}

jest.mock('@ui/mobile/providers', () => ({
useTheme: () => ({
colors: mockColors,
}),
}))

describe('SettingsSectionHeader', () => {
it('renders title string in uppercase format', () => {
render(<SettingsSectionHeader title="account settings" />)

expect(screen.getByText('ACCOUNT SETTINGS')).toBeTruthy()
})

it('applies colors.mutedForeground to text style', () => {
render(<SettingsSectionHeader title="general" />)

const textElement = screen.getByText('GENERAL')

expect(textElement.props.style).toEqual(
expect.objectContaining({
color: mockColors.mutedForeground,
})
)
})
})
Loading