feat(ui-workspace): reorder ungrouped sessions
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/client/ui-workspace/README.md
|
||||
README.md: 3308044abe32ba36d45b02ddacba93772265d315
|
||||
README.zh.md: 38ccb89e0a1fa03ce2d2bab08e7605d8c60f2472
|
||||
README.md: 98edb22dd173d8d6f119a4494baeb7e16cec98f6
|
||||
README.zh.md: 4f0a8f1950889bb9247899ea14d062bd0ee07c4d
|
||||
|
||||
@@ -4,7 +4,7 @@ English | [中文](README.zh.md)
|
||||
|
||||
Shared Workspace browser and picker plugin. `WorkspaceBrowser` fills the sidebar's `sidebar.workspaces` slot, while `WorkspacePicker` fills the page-local Session Intent hero's `conversation.hero.workspace` slot; both surfaces use the same Workspace menu and add flow.
|
||||
|
||||
The browser renders grouped or flat Session rows from the global runtime hooks and owns Workspace add/rename/reorder plus in-Workspace Session reorder. A Workspace remembers whether it is closed or showing Sessions; an open Workspace shows five Sessions by default, offers a transient **Show more** control for the remainder, and returns to five after the whole Workspace is closed and reopened. Once the Workspace list baseline is ready, browser-persisted expansion and Session-order records retain only current Workspace ids plus Ungrouped. View options combine grouping with one browser-persisted Session order: entering **Last updated** performs a complete recency sort and later user prompts or steers promote their Session once, while entering **Manual** preserves every current position and disables later promotion. Dragging edits the current order in either mode; Manual-mode drags also update the Host Workspace account. Workspace drag order is Host-durable in either Session order mode.
|
||||
The browser renders grouped or flat Session rows from the global runtime hooks and owns Workspace add/rename/reorder plus grouped Session reorder. A Workspace remembers whether it is closed or showing Sessions; an open Workspace shows five Sessions by default, offers a transient **Show more** control for the remainder, and returns to five after the whole Workspace is closed and reopened. Once the Workspace list baseline is ready, browser-persisted expansion and Session-order records retain only current Workspace ids plus Ungrouped. View options combine grouping with one browser-persisted Session order per group: real Workspaces initialize from `WorkspaceView.sessionIds`, while Ungrouped initializes from recency. Entering **Last updated** performs a complete recency sort and later user prompts or steers promote their Session once, while entering **Manual** preserves every current position and disables later promotion. Dragging edits the current order in either mode; Manual-mode drags for real Workspaces also update the Host Session account, while Ungrouped order remains browser-local because no Workspace owns those Sessions. Workspace drag order is Host-durable in either Session order mode.
|
||||
|
||||
Collapsed search is one header action beside the view and add actions. Activating it expands the field across the header; an outside click collapses only a query that is empty after trimming, while the clear control always resets and collapses it. A non-blank search query replaces either browsing mode with one flat result list: case-insensitive title and Workspace substring matches appear immediately, while a 250 ms debounced Host request adds ranked current-conversation content matches and snippets. The English search input and its defensive request path remove NUL, cap the query at the wire schema's 500 UTF-16 code units without splitting a surrogate pair, and preserve the existing debounce and cancellation behavior. Each new query aborts the preceding request; a failed content search leaves metadata matches visible with a warning. The list is capped at 20, asks the user to narrow broader queries, and opens the selected Session without clearing the query or jumping to a specific event.
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
共享 Workspace 浏览器与选择器插件。`WorkspaceBrowser` 填充侧边栏的 `sidebar.workspaces` slot,`WorkspacePicker` 则填充页面局部 Session Intent 主视觉区的 `conversation.hero.workspace` slot;两个界面使用同一套 Workspace 菜单和添加流程。
|
||||
|
||||
该浏览器通过全局运行时钩子将 Session 行渲染为分组或扁平形式,并负责 Workspace 添加/重命名/重排序以及 Workspace 内的 Session 重排序。每个 Workspace 会记住自身是关闭还是显示 Session;打开后默认显示五条 Session,其余条目通过临时的**展开其余**控件显示,而关闭并重新打开整个 Workspace 后会恢复为五条。Workspace 列表基线就绪后,浏览器持久化的展开状态与 Session 顺序记录只保留当前 Workspace id 和 Ungrouped。视图选项把分组方式和一份浏览器持久化的 Session 顺序放在一起:进入**最近更新**时执行一次完整的时间排序,后续 user prompt 或 steer 会将对应 Session 置顶一次;进入**手动排序**则保留所有当前位置并停用后续置顶。两种模式下的拖拽都会编辑当前顺序,手动模式下的拖拽还会更新 Host Workspace 记账。无论采用哪种 Session 顺序,Workspace 拖拽顺序都由 Host 持久化。
|
||||
该浏览器通过全局运行时钩子将 Session 行渲染为分组或扁平形式,并负责 Workspace 添加/重命名/重排序以及分组内的 Session 重排序。每个 Workspace 会记住自身是关闭还是显示 Session;打开后默认显示五条 Session,其余条目通过临时的**展开其余**控件显示,而关闭并重新打开整个 Workspace 后会恢复为五条。Workspace 列表基线就绪后,浏览器持久化的展开状态与 Session 顺序记录只保留当前 Workspace id 和 Ungrouped。视图选项把分组方式和每个分组各自的一份浏览器持久化 Session 顺序放在一起:真实 Workspace 从 `WorkspaceView.sessionIds` 初始化,Ungrouped 则从最近更新时间顺序初始化。进入**最近更新**时会执行一次完整的时间排序,后续 user prompt 或 steer 会将对应 Session 置顶一次;进入**手动排序**则保留所有当前位置并停用后续置顶。两种模式下的拖拽都会编辑当前顺序;真实 Workspace 在手动模式下的拖拽还会更新 Host Session 记账,而 Ungrouped 因没有 Workspace 持有其中的 Session,其顺序始终只保存在浏览器本地。无论采用哪种 Session 顺序,Workspace 拖拽顺序都由 Host 持久化。
|
||||
|
||||
折叠搜索是视图和添加操作旁的一枚区头按钮。激活后,输入框会扩展并占据区头;点击外部只会收起经清除首尾空白后为空的查询,而清除控件总会重置并收起搜索。非空白查询会以单一扁平结果列表替代任一浏览模式:不区分大小写的标题和 Workspace 子串匹配项会立即显示,经 250 ms 防抖的 Host 请求则会加入经过排序的当前对话内容匹配项及其摘要片段。英文搜索输入框及其防御性请求路径会移除 NUL,将查询限制在传输 schema 规定的 500 个 UTF-16 代码单元内且不会拆分代理项对,并保留现有的防抖与取消行为。每次新查询都会中止前一个请求;内容搜索失败时,元数据匹配项仍会显示,同时给出警告。列表最多显示 20 条结果,并会在查询过宽时提示用户缩小范围;打开所选 Session 时既不会清除查询,也不会跳转至特定事件。
|
||||
|
||||
|
||||
@@ -130,7 +130,8 @@ function ViewOptionsMenu({ groupBy, orderBy, onGroupPick, onOrderPick, t }: {
|
||||
|
||||
/** In-flight root-row drag: source identity plus the current insert marker. */
|
||||
interface DragState {
|
||||
workspaceId: WorkspaceId
|
||||
/** Workspace id, or {@link UNGROUPED_KEY} for the browser-local loose-session account. */
|
||||
workspaceKey: string
|
||||
sessionId: SessionNode['id']
|
||||
/** Row the marker sits on and which half (insert above/below it). */
|
||||
over: { id: SessionNode['id']; half: 'before' | 'after' } | null
|
||||
@@ -227,13 +228,22 @@ function SessionTree({
|
||||
() => Object.entries(workspaceExpansion).filter(([, expanded]) => expanded).map(([key]) => key),
|
||||
[workspaceExpansion],
|
||||
)
|
||||
const ungroupedSessionIds = useMemo(() => {
|
||||
const accounted = new Set(workspaces.flatMap(workspace => workspace.sessionIds))
|
||||
return list.ids.filter(id => list.byId[id] !== undefined && !accounted.has(id))
|
||||
}, [list, workspaces])
|
||||
useEffect(() => {
|
||||
if (list.phase !== 'ready') return
|
||||
const switchedToUpdated = previousOrderBy.current !== 'updated' && orderBy === 'updated'
|
||||
previousOrderBy.current = orderBy
|
||||
for (const workspace of workspaces) {
|
||||
const key = workspace.workspaceId as string
|
||||
const sessionIds = workspace.sessionIds.filter(id => list.byId[id] !== undefined)
|
||||
const accounts = [
|
||||
...workspaces.map(workspace => ({
|
||||
key: workspace.workspaceId as string,
|
||||
sessionIds: workspace.sessionIds.filter(id => list.byId[id] !== undefined),
|
||||
})),
|
||||
{ key: UNGROUPED_KEY, sessionIds: ungroupedSessionIds },
|
||||
]
|
||||
for (const { key, sessionIds } of accounts) {
|
||||
const previousOrder = recentSessionOrder[key]
|
||||
const previousUpdatedAt = recentSessionUpdatedAt[key] ?? {}
|
||||
let nextOrder = reconciledSessionOrder(sessionIds, previousOrder)
|
||||
@@ -266,7 +276,7 @@ function SessionTree({
|
||||
syncRecentSessions(key, nextOrder.map(id => id as string), nextUpdatedAt)
|
||||
}
|
||||
}
|
||||
}, [list, orderBy, recentSessionOrder, recentSessionUpdatedAt, syncRecentSessions, workspaces])
|
||||
}, [list, orderBy, recentSessionOrder, recentSessionUpdatedAt, syncRecentSessions, ungroupedSessionIds, workspaces])
|
||||
const orderedWorkspaces = useMemo(() => {
|
||||
return workspaces.map((workspace) => {
|
||||
const stored = recentSessionOrder[workspace.workspaceId as string]
|
||||
@@ -274,16 +284,25 @@ function SessionTree({
|
||||
return { ...workspace, sessionIds }
|
||||
})
|
||||
}, [recentSessionOrder, workspaces])
|
||||
const orderedUngroupedSessionIds = useMemo(
|
||||
() => reconciledSessionOrder(ungroupedSessionIds, recentSessionOrder[UNGROUPED_KEY]),
|
||||
[recentSessionOrder, ungroupedSessionIds],
|
||||
)
|
||||
const groups = useMemo(
|
||||
() => deriveGroups(list, orderedWorkspaces, archivedSessionIds, { expandedProjects }),
|
||||
[list, orderedWorkspaces, archivedSessionIds, expandedProjects],
|
||||
() => deriveGroups(list, orderedWorkspaces, archivedSessionIds, {
|
||||
expandedProjects,
|
||||
...(recentSessionOrder[UNGROUPED_KEY] === undefined
|
||||
? {}
|
||||
: { ungroupedOrder: recentSessionOrder[UNGROUPED_KEY] }),
|
||||
}),
|
||||
[list, orderedWorkspaces, archivedSessionIds, expandedProjects, recentSessionOrder],
|
||||
)
|
||||
const now = Date.now()
|
||||
const commitSessionDrag = (activeDrag: DragState, over: NonNullable<DragState['over']>): void => {
|
||||
if (sessionDropCommitted.current) return
|
||||
sessionDropCommitted.current = true
|
||||
setDrag(null)
|
||||
const group = groups.find(candidate => candidate.workspaceId === activeDrag.workspaceId)
|
||||
const group = groups.find(candidate => candidate.key === activeDrag.workspaceKey)
|
||||
if (group === undefined) return
|
||||
const targetIndex = group.sessions.findIndex(session => session.id === over.id)
|
||||
if (targetIndex === -1) return
|
||||
@@ -294,14 +313,16 @@ function SessionTree({
|
||||
? group.sessions.length
|
||||
: group.sessions.findIndex(session => session.id === anchor)
|
||||
if (sourceIndex !== -1 && (anchorIndex === sourceIndex || anchorIndex === sourceIndex + 1)) return
|
||||
const account = orderedWorkspaces.find(workspace => workspace.workspaceId === activeDrag.workspaceId)
|
||||
if (account === undefined) return
|
||||
const nextOrder = account.sessionIds.filter(id => id !== activeDrag.sessionId)
|
||||
const accountSessionIds = activeDrag.workspaceKey === UNGROUPED_KEY
|
||||
? orderedUngroupedSessionIds
|
||||
: orderedWorkspaces.find(workspace => workspace.workspaceId === activeDrag.workspaceKey)?.sessionIds
|
||||
if (accountSessionIds === undefined) return
|
||||
const nextOrder = accountSessionIds.filter(id => id !== activeDrag.sessionId)
|
||||
const insertAt = anchor === undefined ? nextOrder.length : nextOrder.indexOf(anchor)
|
||||
nextOrder.splice(insertAt === -1 ? nextOrder.length : insertAt, 0, activeDrag.sessionId)
|
||||
setRecentSessionOrder(activeDrag.workspaceId, nextOrder.map(id => id as string))
|
||||
if (orderBy === 'updated') return
|
||||
insertSessionBefore(activeDrag.workspaceId, activeDrag.sessionId, anchor).catch((reason: unknown) => {
|
||||
setRecentSessionOrder(activeDrag.workspaceKey, nextOrder.map(id => id as string))
|
||||
if (orderBy === 'updated' || activeDrag.workspaceKey === UNGROUPED_KEY) return
|
||||
insertSessionBefore(activeDrag.workspaceKey as WorkspaceId, activeDrag.sessionId, anchor).catch((reason: unknown) => {
|
||||
console.warn('session reorder rejected:', reason)
|
||||
})
|
||||
}
|
||||
@@ -427,15 +448,13 @@ function SessionTree({
|
||||
? group.sessions
|
||||
: group.sessions.slice(0, COLLAPSED_SESSION_LIMIT)
|
||||
).map((node) => {
|
||||
// Draggable: real-workspace session rows. The drag
|
||||
// never leaves its group — rows of other groups show no markers
|
||||
// and reject drops (visual movement confined to this section).
|
||||
const draggable = group.workspaceId !== undefined
|
||||
const sameGroupDrag = drag !== null && drag.workspaceId === group.workspaceId
|
||||
const dragProps = !draggable || group.workspaceId === undefined ? undefined : {
|
||||
// Session drag never leaves its group. Ungrouped writes only the
|
||||
// browser-local account; real Workspaces may also write Host order.
|
||||
const sameGroupDrag = drag !== null && drag.workspaceKey === group.key
|
||||
const dragProps = {
|
||||
start: () => {
|
||||
sessionDropCommitted.current = false
|
||||
setDrag({ workspaceId: group.workspaceId as WorkspaceId, sessionId: node.id, over: null })
|
||||
setDrag({ workspaceKey: group.key, sessionId: node.id, over: null })
|
||||
},
|
||||
active: sameGroupDrag,
|
||||
marker: sameGroupDrag && drag.over?.id === node.id ? drag.over.half : null,
|
||||
|
||||
@@ -78,6 +78,8 @@ export interface SearchResultSet {
|
||||
/** Viewing state consumed by the derivation. */
|
||||
export interface TreeView {
|
||||
expandedProjects: readonly string[]
|
||||
/** Browser-local order for Sessions without a backing Workspace account. */
|
||||
ungroupedOrder?: readonly string[]
|
||||
}
|
||||
|
||||
interface Group {
|
||||
@@ -139,21 +141,41 @@ function buildGroup(
|
||||
order: 'account' | 'recency',
|
||||
): Group {
|
||||
const sessions = [...members]
|
||||
// Workspace order is the caller-selected sessionIds; only Ungrouped lacks
|
||||
// an account order and therefore falls back to recency.
|
||||
// Real Workspace order comes from sessionIds. Ungrouped falls back to
|
||||
// recency until the browser supplies its persisted local order.
|
||||
if (order === 'recency') sessions.sort(byRecency)
|
||||
return { key, workspaceId, cwd, createdAt, label, sessions }
|
||||
}
|
||||
|
||||
/** Apply a stored Ungrouped order and append newly loose Sessions by recency. */
|
||||
function orderedUngrouped(members: readonly SessionSummary[], stored: readonly string[]): SessionSummary[] {
|
||||
const byId = new Map(members.map(session => [session.id as string, session]))
|
||||
const included = new Set<string>()
|
||||
const ordered: SessionSummary[] = []
|
||||
for (const key of stored) {
|
||||
const session = byId.get(key)
|
||||
if (session === undefined || included.has(key)) continue
|
||||
ordered.push(session)
|
||||
included.add(key)
|
||||
}
|
||||
for (const session of [...members].sort(byRecency)) {
|
||||
if (included.has(session.id)) continue
|
||||
ordered.push(session)
|
||||
}
|
||||
return ordered
|
||||
}
|
||||
|
||||
/**
|
||||
* Group Sessions by Host Workspace: one group per entity in stable Host
|
||||
* order, with members resolved from sessionIds in their stored order. Sessions
|
||||
* outside every Workspace trail in the recency-ordered Ungrouped bucket.
|
||||
* outside every Workspace trail in the browser-local Ungrouped order, which
|
||||
* falls back to recency before that order is initialized.
|
||||
*/
|
||||
function groupByWorkspace(
|
||||
list: SessionListState,
|
||||
workspaces: readonly WorkspaceView[],
|
||||
archived: ReadonlySet<SessionId>,
|
||||
ungroupedOrder: readonly string[] | undefined,
|
||||
): Group[] {
|
||||
const groups: Group[] = []
|
||||
const accounted = new Set<SessionId>()
|
||||
@@ -176,7 +198,15 @@ function groupByWorkspace(
|
||||
.filter((s): s is SessionSummary =>
|
||||
s !== undefined && !accounted.has(s.id) && sessionVisible(s, list.current, archived))
|
||||
if (stray.length > 0) {
|
||||
groups.push(buildGroup(UNGROUPED_KEY, undefined, undefined, undefined, UNGROUPED_LABEL, stray, 'recency'))
|
||||
groups.push(buildGroup(
|
||||
UNGROUPED_KEY,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
UNGROUPED_LABEL,
|
||||
ungroupedOrder === undefined ? stray : orderedUngrouped(stray, ungroupedOrder),
|
||||
ungroupedOrder === undefined ? 'recency' : 'account',
|
||||
))
|
||||
}
|
||||
return groups
|
||||
}
|
||||
@@ -225,7 +255,7 @@ export function deriveGroups(
|
||||
: (workspaces.find(w => w.sessionIds.includes(list.current as SessionId))?.workspaceId as string | undefined)
|
||||
?? UNGROUPED_KEY
|
||||
const groups: GroupNode[] = []
|
||||
for (const g of groupByWorkspace(list, workspaces, archived)) {
|
||||
for (const g of groupByWorkspace(list, workspaces, archived, view.ungroupedOrder)) {
|
||||
const expanded = expandedProjects.has(g.key)
|
||||
groups.push({
|
||||
key: g.key,
|
||||
|
||||
@@ -24,8 +24,9 @@ const workspace = (id: string, sessionIds: string[], title = id): WorkspaceView
|
||||
workspaceId: wid(id), path: `/projects/${id}`, title,
|
||||
sessionIds: sessionIds.map(sid), createdAt: '2026-01-01T00:00:00.000Z', updatedAt: '2026-01-01T00:00:00.000Z',
|
||||
})
|
||||
const view = (expandedProjects: readonly string[] = []) => ({
|
||||
const view = (expandedProjects: readonly string[] = [], ungroupedOrder?: readonly string[]) => ({
|
||||
expandedProjects,
|
||||
...(ungroupedOrder === undefined ? {} : { ungroupedOrder }),
|
||||
})
|
||||
const noArchive: readonly SessionId[] = []
|
||||
const archived = (...ids: string[]): readonly SessionId[] => ids.map(sid)
|
||||
@@ -54,6 +55,19 @@ describe('deriveGroups', () => {
|
||||
expect(groups[1]!.sessions.map(session => session.id)).toEqual([sid('loose')])
|
||||
})
|
||||
|
||||
it('applies stored Ungrouped order and appends new loose Sessions by recency', () => {
|
||||
const sessions = list(summary('one', 3), summary('two', 2), summary('new', 4))
|
||||
const groups = deriveGroups(
|
||||
sessions,
|
||||
[],
|
||||
noArchive,
|
||||
view([UNGROUPED_KEY], ['two', 'stale', 'two']),
|
||||
)
|
||||
expect(groups[0]!.sessions.map(session => session.id)).toEqual([
|
||||
sid('two'), sid('new'), sid('one'),
|
||||
])
|
||||
})
|
||||
|
||||
it('shows only the current blank session in its Workspace count and tree', () => {
|
||||
const currentBlank = { ...summary('current-blank', 5), blank: true }
|
||||
const staleBlank = { ...summary('stale-blank', 4), blank: true }
|
||||
|
||||
@@ -9,6 +9,7 @@ import { makeTranslate } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { zh as commonZh } from '@deepseek-ai/dsh-client-locale/src/locales/zh.ts'
|
||||
import type { WorkspaceBrowserProps } from '../src/client/contract/slots.ts'
|
||||
import { createWorkspaceViewStore } from '../src/client/stores.ts'
|
||||
import { UNGROUPED_KEY } from '../src/client/tree.ts'
|
||||
import { WorkspaceBrowser } from '../src/client/WorkspaceBrowser.tsx'
|
||||
import { zh } from '../src/client/locales.ts'
|
||||
|
||||
@@ -111,8 +112,8 @@ describe('WorkspaceBrowser', () => {
|
||||
rerender(b, { useWorkspaces: hook(workspaceState([])) })
|
||||
await waitFor(() => {
|
||||
expect(b.store.getSnapshot().workspaceExpansion).toEqual({})
|
||||
expect(b.store.getSnapshot().recentSessionOrder).toEqual({})
|
||||
expect(b.store.getSnapshot().recentSessionUpdatedAt).toEqual({})
|
||||
expect(b.store.getSnapshot().recentSessionOrder).toEqual({ [UNGROUPED_KEY]: [] })
|
||||
expect(b.store.getSnapshot().recentSessionUpdatedAt).toEqual({ [UNGROUPED_KEY]: {} })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -776,6 +777,55 @@ describe('WorkspaceBrowser', () => {
|
||||
expect(insertSessionBefore).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('persists Ungrouped drag order in both modes without writing a Host Workspace account', async () => {
|
||||
const insertSessionBefore = vi.fn(async () => {})
|
||||
const sessions = sessionState([summary('one', 3), summary('two', 2), summary('three', 1)])
|
||||
const b = mount({
|
||||
useSessions: hook(sessions),
|
||||
useWorkspaces: hook(workspaceState([])),
|
||||
insertSessionBefore,
|
||||
})
|
||||
fireEvent.click(screen.getByText('未分组'))
|
||||
|
||||
const dragAfter = (sourceTitle: string, targetTitle: string): void => {
|
||||
const source = screen.getByText(sourceTitle).closest('[role="treeitem"]') as HTMLElement
|
||||
const target = screen.getByText(targetTitle).closest('[role="treeitem"]') as HTMLElement
|
||||
target.getBoundingClientRect = () => ({
|
||||
top: 150, bottom: 184, left: 0, right: 200, width: 200, height: 34, x: 0, y: 150, toJSON: () => ({}),
|
||||
})
|
||||
fireEvent.dragStart(source, { dataTransfer: dragData() })
|
||||
fireDrag(target, 'drop', 180)
|
||||
}
|
||||
|
||||
dragAfter('one', 'three')
|
||||
expect(b.store.getSnapshot().recentSessionOrder[UNGROUPED_KEY]).toEqual(['two', 'three', 'one'])
|
||||
dragAfter('two', 'one')
|
||||
expect(b.store.getSnapshot().recentSessionOrder[UNGROUPED_KEY]).toEqual(['three', 'one', 'two'])
|
||||
expect(insertSessionBefore).not.toHaveBeenCalled()
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '视图选项' }))
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: '最近更新' }))
|
||||
await waitFor(() => {
|
||||
expect(b.store.getSnapshot().recentSessionOrder[UNGROUPED_KEY]).toEqual(['one', 'two', 'three'])
|
||||
})
|
||||
dragAfter('one', 'three')
|
||||
expect(b.store.getSnapshot().recentSessionOrder[UNGROUPED_KEY]).toEqual(['two', 'three', 'one'])
|
||||
expect(insertSessionBefore).not.toHaveBeenCalled()
|
||||
|
||||
b.view.unmount()
|
||||
const restored = mount({
|
||||
useSessions: hook(sessions),
|
||||
useWorkspaces: hook(workspaceState([])),
|
||||
insertSessionBefore,
|
||||
})
|
||||
expect(restored.store.getSnapshot().recentSessionOrder[UNGROUPED_KEY]).toEqual(['two', 'three', 'one'])
|
||||
expect(screen.getAllByRole('treeitem').slice(1).map(row => row.textContent)).toEqual([
|
||||
expect.stringContaining('two'),
|
||||
expect.stringContaining('three'),
|
||||
expect.stringContaining('one'),
|
||||
])
|
||||
})
|
||||
|
||||
it('still sends the reorder when the dragged row left the group mid-drag', () => {
|
||||
const insertSessionBefore = vi.fn(async () => {})
|
||||
const sessions = sessionState([summary('one', 2), summary('two', 1)])
|
||||
|
||||
Reference in New Issue
Block a user