Automerge backed Markdown editor for the Atmosphere
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255// src/layout/__tests__/LayoutContext.test.tsximport { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'import { render, screen, act, cleanup } from '@testing-library/react'import { LayoutProvider, useLayout } from '../LayoutContext'import { LAYOUT_STORAGE_KEY } from '../layout-persistence'import type { ReactNode } from 'react'import { VaultPreferences } from '../../services/vault-preferences'import { createTestVaultStateMap } from '../../services/__tests__/vault-state-test-helpers'
// Test component that consumes the contextfunction TestConsumer() { const layout = useLayout() return ( <div> <span data-testid="sidebar-width">{layout.state.sidebarWidth}</span> <span data-testid="sidebar-mode">{layout.state.sidebarMode}</span> <span data-testid="editor-mode">{layout.state.editorMode}</span> <button onClick={layout.toggleSidebar}>Toggle Sidebar</button> <button onClick={layout.toggleRightPanel}>Toggle Right Panel</button> </div> )}
function renderWithProvider(children: ReactNode) { return render(<LayoutProvider>{children}</LayoutProvider>)}
describe('LayoutContext', () => { beforeEach(() => { localStorage.clear() })
afterEach(() => { cleanup() })
describe('LayoutProvider', () => { it('provides default state', () => { renderWithProvider(<TestConsumer />) expect(screen.getByTestId('sidebar-width').textContent).toBe('240') expect(screen.getByTestId('sidebar-mode').textContent).toBe('full') expect(screen.getByTestId('editor-mode').textContent).toBe('live-preview') })
it('loads state from localStorage on mount', () => { localStorage.setItem(LAYOUT_STORAGE_KEY, JSON.stringify({ version: 5, sidebarWidth: 300, sidebarMode: 'hidden', rightPanelWidth: 280, rightPanelCollapsed: true, editorMode: 'source', })) renderWithProvider(<TestConsumer />) expect(screen.getByTestId('sidebar-width').textContent).toBe('300') expect(screen.getByTestId('sidebar-mode').textContent).toBe('hidden') expect(screen.getByTestId('editor-mode').textContent).toBe('source') })
it('uses vault mode over conflicting device state and follows remote preference changes', async () => { const map = createTestVaultStateMap() await map.set('reams:editor-mode', 'live-preview') const preferences = new VaultPreferences(map) await preferences.open() localStorage.setItem(LAYOUT_STORAGE_KEY, JSON.stringify({ version: 5, editorMode: 'source' })) function ModeControls() { const layout = useLayout() return <><TestConsumer /><button onClick={() => layout.setEditorMode('source')}>Source preference</button></> } const view = render(<LayoutProvider preferences={preferences}><ModeControls /></LayoutProvider>) try { expect(screen.getByTestId('editor-mode')).toHaveTextContent('live-preview') expect(JSON.parse(localStorage.getItem(LAYOUT_STORAGE_KEY)!)).not.toHaveProperty('editorMode') act(() => { screen.getByText('Source preference').click() }) expect(preferences.get('reams:editor-mode')).toBe('source') expect(map.snapshot()['reams:editor-mode']).toBe('source') await act(async () => { await map.set('reams:editor-mode', 'live-preview') }) expect(screen.getByTestId('editor-mode')).toHaveTextContent('live-preview') } finally { view.unmount(); preferences.close() } })
it('defaults a fresh vault to live-preview without importing the device mode', async () => { const map = createTestVaultStateMap() const preferences = new VaultPreferences(map) await preferences.open() localStorage.setItem(LAYOUT_STORAGE_KEY, JSON.stringify({ version: 5, editorMode: 'source' })) const view = render(<LayoutProvider preferences={preferences}><TestConsumer /></LayoutProvider>) expect(map.snapshot()['reams:editor-mode']).toBeUndefined() expect(JSON.parse(localStorage.getItem(LAYOUT_STORAGE_KEY)!)).not.toHaveProperty('editorMode') view.unmount() const reopened = render(<LayoutProvider preferences={preferences}><TestConsumer /></LayoutProvider>) try { expect(screen.getByTestId('editor-mode')).toHaveTextContent('live-preview') } finally { reopened.unmount(); preferences.close() } })
it('does not migrate the previous vault mode when switching to a fresh vault', async () => { const mapA = createTestVaultStateMap(), mapB = createTestVaultStateMap() await mapA.set('reams:editor-mode', 'source') const a = new VaultPreferences(mapA), b = new VaultPreferences(mapB) await a.open(); await b.open() const view = render(<LayoutProvider preferences={a}><TestConsumer /></LayoutProvider>) try { expect(screen.getByTestId('editor-mode')).toHaveTextContent('source') view.rerender(<LayoutProvider preferences={b}><TestConsumer /></LayoutProvider>) expect(screen.getByTestId('editor-mode')).toHaveTextContent('live-preview') expect(b.get('reams:editor-mode')).toBeNull() expect(a.get('reams:editor-mode')).toBe('source') } finally { view.unmount(); a.close(); b.close() } }) })
describe('toggleSidebar', () => { it('cycles sidebar mode: full -> hidden -> ribbon -> full', () => { renderWithProvider(<TestConsumer />) // Default is 'full' expect(screen.getByTestId('sidebar-mode').textContent).toBe('full') // full -> hidden act(() => { screen.getByText('Toggle Sidebar').click() }) expect(screen.getByTestId('sidebar-mode').textContent).toBe('hidden')
// hidden -> ribbon act(() => { screen.getByText('Toggle Sidebar').click() }) expect(screen.getByTestId('sidebar-mode').textContent).toBe('ribbon')
// ribbon -> full act(() => { screen.getByText('Toggle Sidebar').click() }) expect(screen.getByTestId('sidebar-mode').textContent).toBe('full') })
it('persists to localStorage', () => { renderWithProvider(<TestConsumer />) act(() => { screen.getByText('Toggle Sidebar').click() }) const stored = JSON.parse(localStorage.getItem(LAYOUT_STORAGE_KEY)!) expect(stored.sidebarMode).toBe('hidden') }) })
describe('setSidebarMode', () => { it('sets sidebar mode directly', () => { function TestWithSetMode() { const layout = useLayout() return ( <div> <span data-testid="sidebar-mode">{layout.state.sidebarMode}</span> <button onClick={() => layout.setSidebarMode('ribbon')}>Set Ribbon</button> <button onClick={() => layout.setSidebarMode('hidden')}>Set Hidden</button> </div> ) }
renderWithProvider(<TestWithSetMode />) expect(screen.getByTestId('sidebar-mode').textContent).toBe('full') act(() => { screen.getByText('Set Ribbon').click() }) expect(screen.getByTestId('sidebar-mode').textContent).toBe('ribbon') act(() => { screen.getByText('Set Hidden').click() }) expect(screen.getByTestId('sidebar-mode').textContent).toBe('hidden') }) })
describe('setRightPanelCollapsed', () => { it('sets right panel collapsed directly', () => { function TestWithSetCollapsed() { const layout = useLayout() return ( <div> <span data-testid="right-collapsed">{String(layout.state.rightPanelCollapsed)}</span> <button onClick={() => layout.setRightPanelCollapsed(false)}>Expand</button> <button onClick={() => layout.setRightPanelCollapsed(true)}>Collapse</button> </div> ) }
renderWithProvider(<TestWithSetCollapsed />) // Right panel starts collapsed by default expect(screen.getByTestId('right-collapsed').textContent).toBe('true') act(() => { screen.getByText('Expand').click() }) expect(screen.getByTestId('right-collapsed').textContent).toBe('false') act(() => { screen.getByText('Collapse').click() }) expect(screen.getByTestId('right-collapsed').textContent).toBe('true') }) })
describe('setEditorMode', () => { it('sets editor mode directly', () => { function TestWithSetEditorMode() { const layout = useLayout() return ( <div> <span data-testid="editor-mode">{layout.state.editorMode}</span> <button onClick={() => layout.setEditorMode('source')}>Set Source</button> <button onClick={() => layout.setEditorMode('live-preview')}>Set Live Preview</button> </div> ) }
renderWithProvider(<TestWithSetEditorMode />) // Default is 'live-preview' expect(screen.getByTestId('editor-mode').textContent).toBe('live-preview') act(() => { screen.getByText('Set Source').click() }) expect(screen.getByTestId('editor-mode').textContent).toBe('source') act(() => { screen.getByText('Set Live Preview').click() }) expect(screen.getByTestId('editor-mode').textContent).toBe('live-preview') }) })
describe('useLayout outside provider', () => { it('throws an error', () => { // Suppress console.error for this test const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) expect(() => { render(<TestConsumer />) }).toThrow('useLayout must be used within a LayoutProvider') consoleSpy.mockRestore() }) })})