fix(web): keep details closed by default

This commit is contained in:
kingwl
2026-07-30 14:42:39 +08:00
parent e29a4d7083
commit 4ef3e60d57
17 changed files with 135 additions and 57 deletions
@@ -139,7 +139,7 @@ afterEach(() => {
describe('AppFrame', () => {
it('renders three tracks from store state', () => {
const { frame } = mountFrame()
expect(tracks(frame)).toEqual([280, 360])
expect(tracks(frame)).toEqual([280, 0])
})
it('renders the session pair with empty owner shares (sessionId is framework-standard)', () => {
@@ -174,6 +174,9 @@ describe('AppFrame', () => {
it('ignores unselected states and closes only when the Session id changes', () => {
const { frame, instance, rerenderFrame } = mountFrame()
expect(tracks(frame)).toEqual([280, 0])
act(() => { instance.actions.openDetails() })
expect(tracks(frame)).toEqual([280, 360])
selectedSession.current = 's-next' as SessionId
@@ -200,15 +203,15 @@ describe('AppFrame', () => {
expect(tracks(frame)).toEqual([280, 0])
})
it('keeps the default details width when the first Session materializes', () => {
it('keeps details closed when the first Session materializes', () => {
selectedSession.current = undefined
const { frame, instance, rerenderFrame } = mountFrame()
expect(tracks(frame)).toEqual([280, 0])
expect(instance.getSnapshot().details).toBe(360)
expect(instance.getSnapshot().details).toBe(0)
selectedSession.current = 's-first' as SessionId
act(() => { rerenderFrame() })
expect(tracks(frame)).toEqual([280, 360])
expect(tracks(frame)).toEqual([280, 0])
})
it('sidebar slot receives live concession output as owner props', () => {
@@ -224,7 +227,8 @@ describe('AppFrame', () => {
})
it('details drag widens leftward (negative dx grows the panel)', () => {
const { frame } = mountFrame()
const { frame, instance } = mountFrame()
act(() => { instance.actions.openDetails() })
const handles = frame.querySelectorAll('[class*="handle"]')
drag(handles[1]!, 1560, 1500)
expect(tracks(frame)[1]).toBe(420)
@@ -233,6 +237,7 @@ describe('AppFrame', () => {
it('drag base is the rendered (concession-clamped) width, not the preference', () => {
frameWidth = 1250 // step-2 squeeze: details renders 330 while preference is 360
const { frame, instance } = mountFrame()
act(() => { instance.actions.openDetails() })
expect(tracks(frame)).toEqual([280, 330])
const handles = frame.querySelectorAll('[class*="handle"]')
drag(handles[1]!, 920, 930) // shrink by 10 from the rendered width
@@ -240,8 +245,7 @@ describe('AppFrame', () => {
})
it('details column stays mounted at zero width', () => {
const { frame, instance, getByTestId } = mountFrame()
act(() => { instance.actions.closeDetails() })
const { frame, getByTestId } = mountFrame()
expect(tracks(frame)).toEqual([280, 0])
expect(getByTestId('details-content')).toBeTruthy()
expect(frame.hasAttribute('data-details-collapsed')).toBe(true)
@@ -250,7 +254,7 @@ describe('AppFrame', () => {
it('closed sidebar keeps its compact rail with mounted slot content and collapsed owner props', () => {
const { frame, instance, slotCalls, getByTestId } = mountFrame()
act(() => { instance.actions.toggleSidebar() })
expect(tracks(frame)).toEqual([SIDEBAR_COLLAPSED, 360])
expect(tracks(frame)).toEqual([SIDEBAR_COLLAPSED, 0])
expect(getByTestId('sidebar-content')).toBeTruthy()
expect(frame.hasAttribute('data-sidebar-collapsed')).toBe(true)
const lastSidebarCall = slotCalls.filter(c => c.key === 'sidebar').at(-1)!
@@ -258,7 +262,8 @@ describe('AppFrame', () => {
})
it('viewport shrink triggers the concession chain via ResizeObserver', () => {
const { frame } = mountFrame()
const { frame, instance } = mountFrame()
act(() => { instance.actions.openDetails() })
frameWidth = 1250
act(() => { fireResize?.(); vi.advanceTimersByTime(20) })
expect(tracks(frame)).toEqual([280, 330])
@@ -269,6 +274,8 @@ describe('AppFrame', () => {
it('drag handles disappear for collapsed columns', () => {
const { frame, instance } = mountFrame()
expect(frame.querySelectorAll('[class*="handle"]')).toHaveLength(1)
act(() => { instance.actions.openDetails() })
expect(frame.querySelectorAll('[class*="handle"]')).toHaveLength(2)
act(() => { instance.actions.closeDetails() })
expect(frame.querySelectorAll('[class*="handle"]')).toHaveLength(1)
@@ -323,7 +330,7 @@ describe('AppFrame — guard branches', () => {
frameWidth = 0
act(() => { fireResize?.(); vi.advanceTimersByTime(20) })
// Track template still reflects the last non-zero viewport.
expect(tracks(frame)).toEqual([280, 360])
expect(tracks(frame)).toEqual([280, 0])
})
})
@@ -338,7 +345,8 @@ describe('AppFrame — unmount with an in-flight resize frame', () => {
})
it('double resize inside one frame rides the pending rAF (??= guard)', () => {
const { frame } = mountFrame()
const { frame, instance } = mountFrame()
act(() => { instance.actions.openDetails() })
frameWidth = 1250
act(() => { fireResize?.(); fireResize?.(); vi.advanceTimersByTime(20) })
expect(tracks(frame)).toEqual([280, 330])
@@ -17,9 +17,9 @@ const PERSIST_KEY = 'dsh.layout.panels'
beforeEach(() => { localStorage.clear() })
describe('createLayoutStore', () => {
it('initializes both panels at their default widths', () => {
it('initializes the sidebar at its default width and details closed', () => {
const { store } = createLayoutStore().create()
expect(store.getSnapshot()).toEqual({ sidebar: SIDEBAR_DEFAULT, details: DETAILS_DEFAULT })
expect(store.getSnapshot()).toEqual({ sidebar: SIDEBAR_DEFAULT, details: 0 })
})
it('each create() is an independent instance (factory is not a singleton)', () => {
@@ -50,9 +50,8 @@ describe('createLayoutStore', () => {
expect(store.getSnapshot().sidebar).toBe(SIDEBAR_DEFAULT)
})
it('openDetails is a no-op when already open; closeDetails zeroes', () => {
it('openDetails uses the contract default, preserves an open width, and closeDetails zeroes', () => {
const { store, actions } = createLayoutStore().create()
actions.closeDetails()
actions.openDetails()
expect(store.getSnapshot().details).toBe(DETAILS_DEFAULT)
actions.setDetails(500)
@@ -65,13 +64,14 @@ describe('createLayoutStore', () => {
it('does not persist panel geometry', () => {
const first = createLayoutStore().create()
first.actions.setSidebar(400)
first.actions.closeDetails()
first.actions.openDetails()
first.actions.setDetails(500)
expect(localStorage.getItem(PERSIST_KEY)).toBeNull()
const second = createLayoutStore().create()
expect(second.store.getSnapshot()).toEqual({
sidebar: SIDEBAR_DEFAULT,
details: DETAILS_DEFAULT,
details: 0,
})
})
})