Files
tolaria/src/components/SettingsPanel.test.tsx

244 lines
8.0 KiB
TypeScript
Raw Normal View History

import { beforeEach, describe, expect, it, vi } from 'vitest'
import { fireEvent, render, screen, within } from '@testing-library/react'
import { SettingsPanel } from './SettingsPanel'
import type { Settings } from '../types'
const emptySettings: Settings = {
feat: auto-pull vault changes from Git (background sync + conflict handling) (#79) * feat: add auto-pull vault sync with conflict handling - Add git_pull, has_remote, get_conflict_files to Rust backend - Add GitPullResult type and auto_pull_interval_minutes to Settings - Create useAutoSync hook (pull on launch, focus, periodic interval) - Update StatusBar with real sync status indicator - Add pull interval setting to SettingsPanel - Add mock handler for git_pull - Update existing tests for new Settings field Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: add tests for git pull, settings, and useAutoSync hook - Add Rust tests: has_remote, git_pull (no_remote, up_to_date, updated), get_conflict_files, parse_updated_files, GitPullResult serialization - Add frontend tests: useAutoSync (mount pull, focus pull, conflict, error, manual trigger, concurrent prevention, no_remote) - Fix existing settings tests for new auto_pull_interval_minutes field Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * design: add auto-pull-vault wireframes Frames showing: sync idle, syncing, conflict indicator, settings sync section, and conflict toast notification. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add auto_pull_interval_minutes to all Settings literals Fix build error and test fixtures missing the new field. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: cargo fmt * ci: re-trigger CI after flaky test * ci: retrigger after disk space cleanup --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 01:55:16 +01:00
auto_pull_interval_minutes: null,
telemetry_consent: null,
crash_reporting_enabled: null,
analytics_enabled: null,
anonymous_id: null,
release_channel: null,
}
function installPointerCapturePolyfill() {
if (!HTMLElement.prototype.hasPointerCapture) {
HTMLElement.prototype.hasPointerCapture = () => false
}
if (!HTMLElement.prototype.setPointerCapture) {
HTMLElement.prototype.setPointerCapture = () => {}
}
if (!HTMLElement.prototype.releasePointerCapture) {
HTMLElement.prototype.releasePointerCapture = () => {}
}
}
describe('SettingsPanel', () => {
const onSave = vi.fn()
const onClose = vi.fn()
beforeEach(() => {
vi.clearAllMocks()
installPointerCapturePolyfill()
})
it('renders nothing when not open', () => {
const { container } = render(
<SettingsPanel open={false} settings={emptySettings} onSave={onSave} onClose={onClose} />
)
expect(container.innerHTML).toBe('')
})
it('renders modal when open', () => {
render(
<SettingsPanel open={true} settings={emptySettings} onSave={onSave} onClose={onClose} />
)
expect(screen.getByText('Settings')).toBeInTheDocument()
expect(screen.getByText('Sync')).toBeInTheDocument()
})
it('calls onSave with stable defaults on save', () => {
render(
<SettingsPanel open={true} settings={emptySettings} onSave={onSave} onClose={onClose} />
)
fireEvent.click(screen.getByTestId('settings-save'))
expect(onSave).toHaveBeenCalledWith(expect.objectContaining({
auto_pull_interval_minutes: 5,
release_channel: null,
}))
expect(onClose).toHaveBeenCalled()
})
it('defaults the release channel trigger to stable', () => {
render(
<SettingsPanel open={true} settings={emptySettings} onSave={onSave} onClose={onClose} />
)
expect(screen.getByTestId('settings-release-channel')).toHaveAttribute('data-value', 'stable')
expect(screen.queryByText(/Beta\/Stable/i)).not.toBeInTheDocument()
})
it('treats a legacy beta release channel as stable', () => {
render(
<SettingsPanel
open={true}
settings={{ ...emptySettings, release_channel: 'beta' }}
onSave={onSave}
onClose={onClose}
/>
)
expect(screen.getByTestId('settings-release-channel')).toHaveAttribute('data-value', 'stable')
expect(screen.queryByText('Beta')).not.toBeInTheDocument()
})
it('preserves alpha when alpha is already selected', () => {
const alphaSettings: Settings = {
...emptySettings,
release_channel: 'alpha',
}
render(
<SettingsPanel open={true} settings={alphaSettings} onSave={onSave} onClose={onClose} />
)
fireEvent.click(screen.getByTestId('settings-save'))
expect(onSave).toHaveBeenCalledWith(expect.objectContaining({
release_channel: 'alpha',
}))
})
it('defaults the organization workflow switch to on', () => {
render(
<SettingsPanel open={true} settings={emptySettings} onSave={onSave} onClose={onClose} />
)
expect(screen.getByRole('switch', { name: 'Organize notes explicitly' })).toHaveAttribute('aria-checked', 'true')
})
2026-04-16 12:18:11 +02:00
it('defaults the initial H1 auto-rename switch to on', () => {
render(
<SettingsPanel open={true} settings={emptySettings} onSave={onSave} onClose={onClose} />
)
expect(screen.getByRole('switch', { name: 'Auto-rename untitled notes from first H1' })).toHaveAttribute('aria-checked', 'true')
})
it('saves the initial H1 auto-rename preference when toggled off', () => {
render(
<SettingsPanel open={true} settings={emptySettings} onSave={onSave} onClose={onClose} />
)
fireEvent.click(screen.getByRole('switch', { name: 'Auto-rename untitled notes from first H1' }))
fireEvent.click(screen.getByTestId('settings-save'))
expect(onSave).toHaveBeenCalledWith(expect.objectContaining({
initial_h1_auto_rename_enabled: false,
}))
})
it('saves the organization workflow preference when toggled off', () => {
const onSaveExplicitOrganization = vi.fn()
render(
<SettingsPanel
open={true}
settings={emptySettings}
onSave={onSave}
explicitOrganizationEnabled={true}
onSaveExplicitOrganization={onSaveExplicitOrganization}
onClose={onClose}
/>
)
fireEvent.click(screen.getByRole('switch', { name: 'Organize notes explicitly' }))
fireEvent.click(screen.getByTestId('settings-save'))
expect(onSaveExplicitOrganization).toHaveBeenCalledWith(false)
})
it('calls onClose when Cancel is clicked', () => {
render(
<SettingsPanel open={true} settings={emptySettings} onSave={onSave} onClose={onClose} />
)
fireEvent.click(screen.getByText('Cancel'))
expect(onClose).toHaveBeenCalled()
})
it('calls onClose when close button is clicked', () => {
render(
<SettingsPanel open={true} settings={emptySettings} onSave={onSave} onClose={onClose} />
)
fireEvent.click(screen.getByTitle('Close settings'))
expect(onClose).toHaveBeenCalled()
})
it('calls onClose on Escape key', () => {
render(
<SettingsPanel open={true} settings={emptySettings} onSave={onSave} onClose={onClose} />
)
fireEvent.keyDown(screen.getByTestId('settings-panel'), { key: 'Escape' })
expect(onClose).toHaveBeenCalled()
})
it('saves on Cmd+Enter', () => {
render(
<SettingsPanel open={true} settings={emptySettings} onSave={onSave} onClose={onClose} />
)
fireEvent.keyDown(screen.getByTestId('settings-panel'), { key: 'Enter', metaKey: true })
expect(onSave).toHaveBeenCalledWith(expect.objectContaining({
feat: auto-pull vault changes from Git (background sync + conflict handling) (#79) * feat: add auto-pull vault sync with conflict handling - Add git_pull, has_remote, get_conflict_files to Rust backend - Add GitPullResult type and auto_pull_interval_minutes to Settings - Create useAutoSync hook (pull on launch, focus, periodic interval) - Update StatusBar with real sync status indicator - Add pull interval setting to SettingsPanel - Add mock handler for git_pull - Update existing tests for new Settings field Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: add tests for git pull, settings, and useAutoSync hook - Add Rust tests: has_remote, git_pull (no_remote, up_to_date, updated), get_conflict_files, parse_updated_files, GitPullResult serialization - Add frontend tests: useAutoSync (mount pull, focus pull, conflict, error, manual trigger, concurrent prevention, no_remote) - Fix existing settings tests for new auto_pull_interval_minutes field Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * design: add auto-pull-vault wireframes Frames showing: sync idle, syncing, conflict indicator, settings sync section, and conflict toast notification. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add auto_pull_interval_minutes to all Settings literals Fix build error and test fixtures missing the new field. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: cargo fmt * ci: re-trigger CI after flaky test * ci: retrigger after disk space cleanup --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 01:55:16 +01:00
auto_pull_interval_minutes: 5,
}))
})
it('calls onClose when clicking backdrop', () => {
render(
<SettingsPanel open={true} settings={emptySettings} onSave={onSave} onClose={onClose} />
)
fireEvent.click(screen.getByTestId('settings-panel'))
expect(onClose).toHaveBeenCalled()
})
it('shows keyboard shortcut hint in footer', () => {
render(
<SettingsPanel open={true} settings={emptySettings} onSave={onSave} onClose={onClose} />
)
expect(screen.getByText(/to open settings/)).toBeInTheDocument()
})
describe('Privacy & Telemetry section', () => {
it('renders crash reporting and analytics toggles', () => {
render(
<SettingsPanel open={true} settings={emptySettings} onSave={onSave} onClose={onClose} />
)
expect(screen.getByTestId('settings-crash-reporting')).toBeInTheDocument()
expect(screen.getByTestId('settings-analytics')).toBeInTheDocument()
})
it('toggles reflect initial settings state', () => {
const withTelemetry: Settings = {
...emptySettings,
telemetry_consent: true,
crash_reporting_enabled: true,
analytics_enabled: false,
anonymous_id: 'test-uuid',
}
render(
<SettingsPanel open={true} settings={withTelemetry} onSave={onSave} onClose={onClose} />
)
const crashCheckbox = within(screen.getByTestId('settings-crash-reporting')).getByRole('checkbox')
const analyticsCheckbox = within(screen.getByTestId('settings-analytics')).getByRole('checkbox')
expect(crashCheckbox).toHaveAttribute('aria-checked', 'true')
expect(analyticsCheckbox).toHaveAttribute('aria-checked', 'false')
})
it('saves telemetry settings when toggled and saved', () => {
render(
<SettingsPanel open={true} settings={emptySettings} onSave={onSave} onClose={onClose} />
)
fireEvent.click(within(screen.getByTestId('settings-crash-reporting')).getByRole('checkbox'))
fireEvent.click(screen.getByTestId('settings-save'))
expect(onSave).toHaveBeenCalledWith(expect.objectContaining({
crash_reporting_enabled: true,
analytics_enabled: false,
}))
})
})
})