Automerge backed Markdown editor for the Atmosphere
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142// src/layout/__tests__/Header.test.tsximport { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'import { render, screen, cleanup, fireEvent } from '@testing-library/react'import { Header } from '../Header'import { LayoutProvider } from '../LayoutContext'
const updates = vi.hoisted(() => ({ available: false, apply: vi.fn(), retry: vi.fn(), error: '', flush: vi.fn() }))vi.mock('../../app/pwa-updates', () => ({ updateAvailable: () => updates.available, updateError: () => updates.error, subscribeToUpdates: () => () => {}, applyPwaUpdate: updates.apply, retryPwaUpdate: updates.retry, registerPwaUpdateFlush: updates.flush,}))
describe('Header', () => { beforeEach(() => { vi.useFakeTimers() updates.available = false updates.error = '' updates.apply.mockReset() updates.retry.mockReset() })
afterEach(() => { Object.defineProperty(window, 'visualViewport', { value: undefined, writable: true, configurable: true }) vi.useRealTimers() cleanup() })
function renderHeader() { return render( <LayoutProvider> <Header /> </LayoutProvider> ) }
it('displays Reams branding text', () => { renderHeader() expect(screen.getByText('Reams')).toBeInTheDocument() })
it('does not display document title', () => { renderHeader() const header = screen.getByRole('banner') expect(header).not.toHaveTextContent('No document open') })
it('uses the shared action button when an update is ready', () => { updates.available = true renderHeader() expect(screen.getByRole('button', { name: 'Update' })).toHaveClass('reams-action-button', 'header-update-btn') })
it('does not render an editor mode button, including with legacy props', () => { render( <LayoutProvider> <Header /> </LayoutProvider> ) expect(screen.queryByRole('button', { name: /current mode/i })).not.toBeInTheDocument() })
it('does not register a no-op flush when no before-update callback is provided', () => { renderHeader() expect(updates.flush).not.toHaveBeenCalled() })
it('registers the provided before-update callback as the update flush', () => { const onBeforeUpdate = vi.fn().mockResolvedValue(undefined) render( <LayoutProvider> <Header onBeforeUpdate={onBeforeUpdate} /> </LayoutProvider> ) expect(updates.flush).toHaveBeenCalledWith(onBeforeUpdate) })
it('shows a retry button when an update error is reported', () => { updates.error = 'write failed' renderHeader() expect(screen.getByRole('button', { name: 'Retry' })).toBeInTheDocument() expect(screen.getByRole('alert')).toHaveTextContent('write failed') })
it('retries the update when the retry button is clicked', async () => { updates.error = 'write failed' updates.retry.mockResolvedValue(undefined) renderHeader() fireEvent.click(screen.getByRole('button', { name: 'Retry' })) expect(updates.retry).toHaveBeenCalledTimes(1) })
describe('branding', () => { let removeItemSpy: ReturnType<typeof vi.spyOn> let reloadSpy: ReturnType<typeof vi.spyOn> let deleteDatabaseSpy: ReturnType<typeof vi.fn>
beforeEach(() => { removeItemSpy = vi.spyOn(Storage.prototype, 'removeItem') deleteDatabaseSpy = vi.fn() Object.defineProperty(window, 'indexedDB', { value: { deleteDatabase: deleteDatabaseSpy }, writable: true, configurable: true, })
// Mock location.reload — jsdom doesn't support navigation // Use Object.defineProperty since location.reload is non-configurable reloadSpy = vi.fn() as unknown as ReturnType<typeof vi.spyOn> Object.defineProperty(window, 'location', { value: { ...window.location, reload: reloadSpy }, writable: true, configurable: true, }) })
afterEach(() => { removeItemSpy.mockRestore() Object.defineProperty(window, 'indexedDB', { value: undefined, writable: true, configurable: true }) })
it('preserves local state after repeated branding clicks', () => { localStorage.setItem('reams:ribbon-config', '{}') localStorage.setItem('other-app:data', 'keep')
renderHeader() const branding = screen.getByText('Reams')
for (let i = 0; i < 8; i++) { fireEvent.click(branding) }
expect(removeItemSpy).not.toHaveBeenCalled() expect(deleteDatabaseSpy).not.toHaveBeenCalled() expect(localStorage.getItem('reams:ribbon-config')).toBe('{}') expect(localStorage.getItem('other-app:data')).toBe('keep') expect(reloadSpy).not.toHaveBeenCalled() }) })})