Automerge backed Markdown editor for the Atmosphere
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659// src/layout/__tests__/SidebarViewContainer.test.tsximport { describe, it, expect, beforeEach, afterEach } from 'vitest'import { render, screen, cleanup, act } from '@testing-library/react'import userEvent from '@testing-library/user-event'import type { ReactNode } from 'react'import { createElement } from 'react'import { Workspace } from '../../plugin/workspace'import { ItemView, type ReactItemView } from '../../plugin/views/item-view'import { SidebarViewContainer } from '../SidebarViewContainer'import { savePersistedSidebarLeaves } from '../sidebar-leaf-persistence'import { ReamsEvents } from '../../plugin/reams/events'import type { App } from '../../plugin/app'import { setTypesRecordsEnabled, TYPES_RECORDS_SETTING_KEY } from '../../types-records/feature-setting'import { TYPES_BROWSER_VIEW_TYPE } from '../../plugins/types-browser/types-browser-view'import { openTestVaultPreferences } from '../../services/__tests__/vault-state-test-helpers'import { writeVaultPreference, type VaultPreferences } from '../../services/vault-preferences'
class TestViewA extends ItemView { getViewType() { return 'view-a' } getDisplayText() { return 'View A' } getIcon() { return 'folder' } onOpen() { this.contentEl.textContent = 'View A Content' return Promise.resolve() }}
class TestViewB extends ItemView { getViewType() { return 'view-b' } getDisplayText() { return 'View B' } getIcon() { return 'search' } onOpen() { this.contentEl.textContent = 'View B Content' return Promise.resolve() }}
class TestTypesView extends ItemView { getViewType() { return TYPES_BROWSER_VIEW_TYPE } getDisplayText() { return 'Types' } onOpen() { return Promise.resolve() }}
/** ReactItemView for testing the inline React rendering path */class TestReactView extends ItemView implements ReactItemView { getViewType() { return 'react-view' } getDisplayText() { return 'React View' } getIcon() { return 'calendar' } getReactComponent(): ReactNode { return createElement('div', { 'data-testid': 'react-view-content' }, 'React View Content') }}
/** View with unknown icon to test fallback */class TestViewUnknownIcon extends ItemView { getViewType() { return 'view-unknown-icon' } getDisplayText() { return 'Mystery View' } getIcon() { return 'definitely-not-a-real-icon-xyz' } onOpen() { this.contentEl.textContent = 'Mystery Content' return Promise.resolve() }}
/** Helper to get all sidebar-view-leaf elements and their data-visible attrs */function getLeafVisibility(container: HTMLElement) { const leaves = container.querySelectorAll('.sidebar-view-leaf') return Array.from(leaves).map(el => ({ visible: el.getAttribute('data-visible'), }))}
/** * Create a minimal App mock for testing components that need app.reamsEvents */function createAppMock(ws: Workspace): App { const reamsEvents = new ReamsEvents() const app = { workspace: ws, reamsEvents, } as unknown as App ws.app = app return app}
describe('SidebarViewContainer', () => { let workspace: Workspace let app: App let preferences: VaultPreferences
beforeEach(async () => { preferences = await openTestVaultPreferences() workspace = new Workspace(document.createElement('div')) app = createAppMock(workspace) localStorage.clear() })
afterEach(() => { cleanup() preferences.close() })
it('keeps sidebar DOM and keyboard order aligned when a view requests a trailing position', async () => { class TrailingView extends TestReactView { readonly sidebarOrder = 1 } workspace.registerViewType('react-view', leaf => new TrailingView(leaf)) workspace.registerViewType('view-a', leaf => new TestViewA(leaf)) await workspace.getRightLeaf(true).openView('react-view') await workspace.getRightLeaf(true).openView('view-a') render(<SidebarViewContainer app={app} side="right" />) const tabs = screen.getAllByRole('tab') expect(tabs.map(tab => tab.getAttribute('aria-label'))).toEqual(['View A', 'React View']) tabs[0].focus() await userEvent.setup().tab() expect(tabs[1]).toHaveFocus() })
describe('left panel', () => { it('hides a restored Types tab while Types are off and shows it when enabled', async () => { workspace.registerViewType('view-a', leaf => new TestViewA(leaf)) workspace.registerViewType(TYPES_BROWSER_VIEW_TYPE, leaf => new TestTypesView(leaf)) await workspace.getLeftLeaf(true).openView('view-a') const typesLeaf = workspace.getLeftLeaf(true) await typesLeaf.openView(TYPES_BROWSER_VIEW_TYPE)
render(<SidebarViewContainer app={app} side="left" />) expect(screen.queryByRole('tab', { name: 'Types' })).not.toBeInTheDocument() expect(screen.getByRole('tab', { name: 'View A' })).toBeInTheDocument()
act(() => setTypesRecordsEnabled(true)) expect(screen.getByRole('tab', { name: 'Types' })).toBeInTheDocument()
act(() => { writeVaultPreference(TYPES_RECORDS_SETTING_KEY, 'false') }) expect(screen.queryByRole('tab', { name: 'Types' })).not.toBeInTheDocument() expect(workspace.getLeavesOfType(TYPES_BROWSER_VIEW_TYPE)).toHaveLength(1) })
it('shows placeholder when no views are registered', () => { render(<SidebarViewContainer app={app} side="left" />) expect(screen.getByText('No views available')).toBeInTheDocument() })
it('replaces the empty-view message with real panel chrome while views load', async () => { savePersistedSidebarLeaves([{ side: 'left', viewType: 'view-a' }]) const { container, rerender } = render(<SidebarViewContainer app={app} side="left" loading />) expect(screen.getByRole('status', { name: 'Loading sidebar views' })).toHaveAttribute('aria-busy', 'true') expect(screen.queryByText('No views available')).not.toBeInTheDocument() expect(container.querySelector('.sidebar-view-tabs')).toBeInTheDocument() expect(container.querySelector('.sidebar-view-header')).toBeInTheDocument() expect(container.querySelectorAll('.file-tree-item')).toHaveLength(8) expect(container.querySelectorAll('.sidebar-loading__tab')).toHaveLength(1) expect([...container.querySelectorAll('.sidebar-loading__tab')].every(tab => tab.childElementCount === 0)).toBe(true) expect(container.querySelector('.sidebar-loading__icon')).not.toBeInTheDocument() expect(container.querySelector('.file-tree-item__chevron')).not.toBeInTheDocument() expect(screen.queryByRole('button')).not.toBeInTheDocument()
workspace.registerViewType('view-a', leaf => new TestViewA(leaf)) await act(async () => { await workspace.getLeftLeaf(true).openView('view-a') }) expect(screen.queryByRole('status')).not.toBeInTheDocument() expect(screen.getByRole('tab', { name: 'View A' })).toBeInTheDocument()
await act(async () => { workspace.getLeftLeaves()[0].detach() }) rerender(<SidebarViewContainer app={app} side="left" loading={false} />) expect(screen.queryByRole('status')).not.toBeInTheDocument() expect(screen.getByText('No views available')).toBeInTheDocument() })
it('renders tab bar even with a single view', async () => { workspace.registerViewType('view-a', (leaf) => new TestViewA(leaf)) const leaf = workspace.getLeftLeaf(true) await leaf.openView('view-a')
render(<SidebarViewContainer app={app} side="left" />)
// Tab bar should be present even for a single view expect(screen.getByRole('tablist')).toBeInTheDocument() // The single tab should be active const tab = screen.getByRole('tab', { name: 'View A' }) expect(tab).toHaveAttribute('aria-selected', 'true') // View content should render expect(screen.getByText('View A Content')).toBeInTheDocument() })
it('renders vertical tab bar with aria-orientation', async () => { workspace.registerViewType('view-a', (leaf) => new TestViewA(leaf)) workspace.registerViewType('view-b', (leaf) => new TestViewB(leaf)) const leafA = workspace.getLeftLeaf(true) await leafA.openView('view-a') const leafB = workspace.getLeftLeaf(true) await leafB.openView('view-b')
render(<SidebarViewContainer app={app} side="left" />)
const tablist = screen.getByRole('tablist') expect(tablist).toHaveAttribute('aria-orientation', 'vertical') })
it('renders icon SVG in tab buttons for known icons', async () => { workspace.registerViewType('view-a', (leaf) => new TestViewA(leaf)) const leaf = workspace.getLeftLeaf(true) await leaf.openView('view-a')
render(<SidebarViewContainer app={app} side="left" />)
// Tab should contain an SVG (from getIcon('folder')) const tab = screen.getByRole('tab', { name: 'View A' }) const svg = tab.querySelector('svg') expect(svg).not.toBeNull() })
it('renders text fallback for unknown icon names', async () => { workspace.registerViewType('view-unknown-icon', (leaf) => new TestViewUnknownIcon(leaf)) const leaf = workspace.getLeftLeaf(true) await leaf.openView('view-unknown-icon')
render(<SidebarViewContainer app={app} side="left" />)
const tab = screen.getByRole('tab', { name: 'Mystery View' }) // Should show fallback letter 'M' (first char of 'Mystery View') const fallback = tab.querySelector('.tab-icon-fallback') expect(fallback).not.toBeNull() expect(fallback!.textContent).toBe('M') })
it('shows title tooltip on tab buttons', async () => { workspace.registerViewType('view-a', (leaf) => new TestViewA(leaf)) workspace.registerViewType('view-b', (leaf) => new TestViewB(leaf)) const leafA = workspace.getLeftLeaf(true) await leafA.openView('view-a') const leafB = workspace.getLeftLeaf(true) await leafB.openView('view-b')
render(<SidebarViewContainer app={app} side="left" />)
const tabA = screen.getByRole('tab', { name: 'View A' }) expect(tabA).toHaveAttribute('title', 'View A') const tabB = screen.getByRole('tab', { name: 'View B' }) expect(tabB).toHaveAttribute('title', 'View B') })
it('shows tab aria-label as view display text', async () => { workspace.registerViewType('view-a', (leaf) => new TestViewA(leaf)) workspace.registerViewType('view-b', (leaf) => new TestViewB(leaf)) const leafA = workspace.getLeftLeaf(true) await leafA.openView('view-a') const leafB = workspace.getLeftLeaf(true) await leafB.openView('view-b')
render(<SidebarViewContainer app={app} side="left" />)
expect(screen.getByRole('tab', { name: 'View A' })).toBeInTheDocument() expect(screen.getByRole('tab', { name: 'View B' })).toBeInTheDocument() })
it('switches visible view when clicking a tab', async () => { workspace.registerViewType('view-a', (leaf) => new TestViewA(leaf)) workspace.registerViewType('view-b', (leaf) => new TestViewB(leaf)) const leafA = workspace.getLeftLeaf(true) await leafA.openView('view-a') const leafB = workspace.getLeftLeaf(true) await leafB.openView('view-b')
const user = userEvent.setup() const { container } = render(<SidebarViewContainer app={app} side="left" />)
// First tab should be active by default let visibility = getLeafVisibility(container) expect(visibility[0].visible).toBe('true') expect(visibility[1].visible).toBe('false')
// Click the second tab await user.click(screen.getByRole('tab', { name: 'View B' }))
visibility = getLeafVisibility(container) expect(visibility[0].visible).toBe('false') expect(visibility[1].visible).toBe('true') })
it('updates when views are dynamically added', async () => { render(<SidebarViewContainer app={app} side="left" />) expect(screen.getByText('No views available')).toBeInTheDocument()
await act(async () => { workspace.registerViewType('view-a', (leaf) => new TestViewA(leaf)) const leaf = workspace.getLeftLeaf(true) await leaf.openView('view-a') })
expect(screen.getByText('View A Content')).toBeInTheDocument() })
it('shows placeholder when all views are removed', async () => { workspace.registerViewType('view-a', (leaf) => new TestViewA(leaf)) const leaf = workspace.getLeftLeaf(true) await leaf.openView('view-a')
render(<SidebarViewContainer app={app} side="left" />) expect(screen.getByText('View A Content')).toBeInTheDocument()
act(() => { workspace.unregisterViewType('view-a') })
expect(screen.getByText('No views available')).toBeInTheDocument() })
it('renders ReactItemView inline in the React tree', async () => { workspace.registerViewType('react-view', (leaf) => new TestReactView(leaf)) const leaf = workspace.getLeftLeaf(true) await leaf.openView('react-view')
render(<SidebarViewContainer app={app} side="left" />)
expect(screen.getByTestId('react-view-content')).toBeInTheDocument() expect(screen.getByText('React View Content')).toBeInTheDocument() })
it('persists active tab to localStorage by view type', async () => { workspace.registerViewType('view-a', (leaf) => new TestViewA(leaf)) workspace.registerViewType('view-b', (leaf) => new TestViewB(leaf)) const leafA = workspace.getLeftLeaf(true) await leafA.openView('view-a') const leafB = workspace.getLeftLeaf(true) await leafB.openView('view-b')
const user = userEvent.setup() render(<SidebarViewContainer app={app} side="left" />)
await user.click(screen.getByRole('tab', { name: 'View B' }))
const stored = JSON.parse(localStorage.getItem('reams:sidebar-leaves') || '{}') expect(stored.version).toBe(2) expect(stored.activeViewTypes?.left).toBe('view-b') })
it('restores active tab from localStorage by view type', async () => { localStorage.setItem('reams:sidebar-leaves', JSON.stringify({ version: 2, leaves: [], activeViewTypes: { left: 'view-b' }, }))
workspace.registerViewType('view-a', (leaf) => new TestViewA(leaf)) workspace.registerViewType('view-b', (leaf) => new TestViewB(leaf)) const leafA = workspace.getLeftLeaf(true) await leafA.openView('view-a') const leafB = workspace.getLeftLeaf(true) await leafB.openView('view-b')
let container: HTMLElement // Use act() to flush all effects (bootstrap + subscription) in one batch await act(async () => { const result = render(<SidebarViewContainer app={app} side="left" />) container = result.container })
const tabB = screen.getByRole('tab', { name: 'View B' }) expect(tabB).toHaveAttribute('aria-selected', 'true') const tabA = screen.getByRole('tab', { name: 'View A' }) expect(tabA).toHaveAttribute('aria-selected', 'false')
const visibility = getLeafVisibility(container!) expect(visibility[0].visible).toBe('false') expect(visibility[1].visible).toBe('true') })
it('auto-selects next tab when active view is removed', async () => { workspace.registerViewType('view-a', (leaf) => new TestViewA(leaf)) workspace.registerViewType('view-b', (leaf) => new TestViewB(leaf)) const leafA = workspace.getLeftLeaf(true) await leafA.openView('view-a') const leafB = workspace.getLeftLeaf(true) await leafB.openView('view-b')
const user = userEvent.setup() const { container } = render(<SidebarViewContainer app={app} side="left" />)
// Select view-a as active await user.click(screen.getByRole('tab', { name: 'View A' }))
// Remove view-a (simulates plugin disable) act(() => { workspace.unregisterViewType('view-a') })
// view-b should now be active const tabB = screen.getByRole('tab', { name: 'View B' }) expect(tabB).toHaveAttribute('aria-selected', 'true') const visibility = getLeafVisibility(container) expect(visibility[0].visible).toBe('true') })
it('persists the auto-selected tab to localStorage when active view is removed', async () => { workspace.registerViewType('view-a', (leaf) => new TestViewA(leaf)) workspace.registerViewType('view-b', (leaf) => new TestViewB(leaf)) const leafA = workspace.getLeftLeaf(true) await leafA.openView('view-a') const leafB = workspace.getLeftLeaf(true) await leafB.openView('view-b')
const user = userEvent.setup() render(<SidebarViewContainer app={app} side="left" />)
// Select view-a as active (persists 'view-a') await user.click(screen.getByRole('tab', { name: 'View A' })) let stored = JSON.parse(localStorage.getItem('reams:sidebar-leaves') || '{}') expect(stored.activeViewTypes?.left).toBe('view-a')
// Remove view-a — should auto-select view-b and persist it act(() => { workspace.unregisterViewType('view-a') })
stored = JSON.parse(localStorage.getItem('reams:sidebar-leaves') || '{}') expect(stored.activeViewTypes?.left).toBe('view-b') })
it('falls back to first tab when persisted view type no longer exists', async () => { // Persist a view type that won't exist localStorage.setItem('reams:sidebar-leaves', JSON.stringify({ version: 2, leaves: [], activeViewTypes: { left: 'nonexistent' }, }))
workspace.registerViewType('view-a', (leaf) => new TestViewA(leaf)) workspace.registerViewType('view-b', (leaf) => new TestViewB(leaf)) const leafA = workspace.getLeftLeaf(true) await leafA.openView('view-a') const leafB = workspace.getLeftLeaf(true) await leafB.openView('view-b')
render(<SidebarViewContainer app={app} side="left" />)
// Should fall back to first tab const tabA = screen.getByRole('tab', { name: 'View A' }) expect(tabA).toHaveAttribute('aria-selected', 'true') })
it('ignores legacy sidebar and panel selection state', async () => { // Legacy state must not override the first current leaf's selected view. localStorage.setItem('reams:sidebar-leaves', JSON.stringify({ version: 1, leaves: [{ viewType: 'view-b', side: 'left' }], })) localStorage.setItem('reams:panel-views', JSON.stringify({ leftActiveViewType: 'view-b', }))
workspace.registerViewType('view-a', (leaf) => new TestViewA(leaf)) workspace.registerViewType('view-b', (leaf) => new TestViewB(leaf)) const leafA = workspace.getLeftLeaf(true) await leafA.openView('view-a') const leafB = workspace.getLeftLeaf(true) await leafB.openView('view-b')
await act(async () => { render(<SidebarViewContainer app={app} side="left" />) })
const tabA = screen.getByRole('tab', { name: 'View A' }) expect(tabA).toHaveAttribute('aria-selected', 'true') expect(screen.getByRole('tab', { name: 'View B' })).toHaveAttribute('aria-selected', 'false')
// Legacy storage is inert and preserved for any other legacy consumer. expect(JSON.parse(localStorage.getItem('reams:panel-views') || '{}')).toEqual({ leftActiveViewType: 'view-b', })
const stored = JSON.parse(localStorage.getItem('reams:sidebar-leaves') || '{}') expect(stored.version).toBe(2) expect(stored.activeViewTypes?.left).toBe('view-a') })
it('shows placeholder when plugin disable removes last view', async () => { workspace.registerViewType('view-a', (leaf) => new TestViewA(leaf)) const leaf = workspace.getLeftLeaf(true) await leaf.openView('view-a')
render(<SidebarViewContainer app={app} side="left" />) expect(screen.getByText('View A Content')).toBeInTheDocument()
act(() => { workspace.unregisterViewType('view-a') })
expect(screen.getByText('No views available')).toBeInTheDocument() }) })
describe('right panel', () => { it('reads from rightLeaves when side is right', async () => { workspace.registerViewType('view-a', (leaf) => new TestViewA(leaf)) const leaf = workspace.getRightLeaf(true) await leaf.openView('view-a')
render(<SidebarViewContainer app={app} side="right" />)
expect(screen.getByText('View A Content')).toBeInTheDocument() })
it('persists right panel active tab separately from left', async () => { workspace.registerViewType('view-a', (leaf) => new TestViewA(leaf)) workspace.registerViewType('view-b', (leaf) => new TestViewB(leaf)) const leafA = workspace.getRightLeaf(true) await leafA.openView('view-a') const leafB = workspace.getRightLeaf(true) await leafB.openView('view-b')
const user = userEvent.setup() render(<SidebarViewContainer app={app} side="right" />)
await user.click(screen.getByRole('tab', { name: 'View B' }))
const stored = JSON.parse(localStorage.getItem('reams:sidebar-leaves') || '{}') expect(stored.version).toBe(2) expect(stored.activeViewTypes?.right).toBe('view-b') expect(stored.activeViewTypes?.left).toBeUndefined() })
it('does not show left panel leaves when side is right', async () => { workspace.registerViewType('view-a', (leaf) => new TestViewA(leaf)) const leaf = workspace.getLeftLeaf(true) await leaf.openView('view-a')
render(<SidebarViewContainer app={app} side="right" />)
expect(screen.getByText('No views available')).toBeInTheDocument() })
it('has data-side attribute for CSS targeting', async () => { workspace.registerViewType('view-a', (leaf) => new TestViewA(leaf)) const leaf = workspace.getRightLeaf(true) await leaf.openView('view-a')
const { container } = render(<SidebarViewContainer app={app} side="right" />)
const svc = container.querySelector('.sidebar-view-container') expect(svc).toHaveAttribute('data-side', 'right') }) })
describe('view-header-actions-change', () => { // A view whose getHeaderActions output flips when its internal counter // changes. We use this to verify that requestHeaderActionsUpdate causes // the chrome row to re-render with the new content. class DynamicActionsView extends ItemView implements ReactItemView { private _label = 'initial' setLabel(label: string) { this._label = label } getViewType() { return 'dynamic-actions' } getDisplayText() { return 'Dynamic' } getReactComponent(): ReactNode { return createElement('div', { 'data-testid': 'dynamic-content' }, 'body') } getHeaderActions(): ReactNode { return createElement( 'button', { type: 'button', 'data-testid': 'dynamic-action' }, this._label, ) } }
it('re-renders header actions after requestHeaderActionsUpdate()', async () => { workspace.registerViewType('dynamic-actions', (leaf) => new DynamicActionsView(leaf)) const leaf = workspace.getLeftLeaf(true) await leaf.openView('dynamic-actions')
render(<SidebarViewContainer app={app} side="left" />)
expect(screen.getByTestId('dynamic-action').textContent).toBe('initial')
const view = leaf.view as DynamicActionsView act(() => { view.setLabel('updated') view.requestHeaderActionsUpdate() })
expect(screen.getByTestId('dynamic-action').textContent).toBe('updated') })
it('ignores requestHeaderActionsUpdate when the firing leaf is not the visible one', async () => { workspace.registerViewType('dynamic-actions', (leaf) => new DynamicActionsView(leaf)) workspace.registerViewType('view-b', (leaf) => new TestViewB(leaf)) const leafA = workspace.getLeftLeaf(true) await leafA.openView('dynamic-actions') const leafB = workspace.getLeftLeaf(true) await leafB.openView('view-b')
const user = userEvent.setup() render(<SidebarViewContainer app={app} side="left" />)
// Activate view-b (so the dynamic view is no longer the visible one). await user.click(screen.getByRole('tab', { name: 'View B' }))
// Calling requestHeaderActionsUpdate on the non-visible view must // not throw and must not affect what's currently displayed. const view = leafA.view as DynamicActionsView expect(() => act(() => { view.setLabel('hidden') view.requestHeaderActionsUpdate() }), ).not.toThrow()
// View B has no getHeaderActions, so no dynamic-action button is shown. expect(screen.queryByTestId('dynamic-action')).toBeNull() })
it('background-leaf reactivation: mutating a non-visible leaf then activating it shows the new actions', async () => { // Two leaves on the left side. We start with leaf-A's dynamic view // showing 'initial'. We swap to leaf-B (active), then mutate the // background leaf-A's label and call requestHeaderActionsUpdate // (which is a no-op for the chrome row right now because leaf-A is // not visible). When the user later switches back to leaf-A, the // chrome row must reflect the new label — i.e. the activeView // identity change must trigger a getHeaderActions() recompute. workspace.registerViewType('dynamic-actions', (leaf) => new DynamicActionsView(leaf)) workspace.registerViewType('view-b', (leaf) => new TestViewB(leaf)) const leafA = workspace.getLeftLeaf(true) await leafA.openView('dynamic-actions') const leafB = workspace.getLeftLeaf(true) await leafB.openView('view-b')
const user = userEvent.setup() render(<SidebarViewContainer app={app} side="left" />)
// Confirm leaf-A is initially active and shows 'initial'. expect(screen.getByTestId('dynamic-action').textContent).toBe('initial')
// Switch to leaf-B → leaf-A is now background. await user.click(screen.getByRole('tab', { name: 'View B' })) expect(screen.queryByTestId('dynamic-action')).toBeNull()
// Mutate background leaf-A and fire requestHeaderActionsUpdate // (which won't immediately re-render because leaf-A is hidden). const view = leafA.view as DynamicActionsView act(() => { view.setLabel('background-updated') view.requestHeaderActionsUpdate() })
// Still no dynamic-action button (leaf-A still hidden). expect(screen.queryByTestId('dynamic-action')).toBeNull()
// Reactivate leaf-A: its new label must surface in the chrome row. await user.click(screen.getByRole('tab', { name: 'Dynamic' })) expect(screen.getByTestId('dynamic-action').textContent).toBe('background-updated') }) })})