test(session): compare what the window shows before and after the shared session loader (#28842)

* test(session): compare what the window shows before and after the shared session loader

Boots the window's real startup hydration on each saved-profile fixture as
stored and as the shared loader rewrites it (load then save), and compares
tabs, groups, active tabs, pane trees, terminal bindings, editor and browser
tabs. Today's eight differences are listed with the loader rule behind each;
the list may only shrink, so any new difference fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(session): let the parity test own its store so the harness reads no host focus

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(session): restore the parity boot's UUID spy even when the boot throws

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jinwoo Hong
2026-10-14 17:31:04 -04:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 47b63b4ea1
commit f8cbfef4fd
5 changed files with 521 additions and 23 deletions
@@ -0,0 +1,141 @@
// Window parity: for each stored profile, the window's startup hydration of the session as stored
// and of the shared Loader's rewrite of it (load then save) must show the same thing. Where they
// differ today, the difference is listed below with the Loader rule that causes it.
import { describe, expect, it, vi } from 'vitest'
import type * as AgentStatusModule from '@/lib/agent-status'
import type { ExecutionHostId } from '../../../shared/execution-host'
import type { WorkspaceSessionState } from '../../../shared/workspace-session-state-types'
import { LEGACY_WORKSPACE_LAYOUT_PROFILES } from '../../../shared/workspace-layout/workspace-layout-legacy-profile.test-fixture'
import { loadWorkspaceLayout } from '../../../shared/workspace-layout/workspace-layout-load'
import { WORKSPACE_LAYOUT_PROFILES } from '../../../shared/workspace-layout/workspace-layout-profile.test-fixture'
import { saveWorkspaceLayout } from '../../../shared/workspace-layout/workspace-layout-save'
import { createStoreSessionMockApi } from '../store/slices/store-session-test-harness'
import { createTestStore } from '../store/slices/store-test-helpers'
import {
bootWindowFromPartitions,
diffStartupWindowViews,
projectStartupWindowView
} from './startup-window-view.test-fixture'
vi.mock('sonner', () => ({ toast: { info: vi.fn(), success: vi.fn(), error: vi.fn() } }))
vi.mock('@/lib/agent-status', async (importOriginal) => {
const actual = await importOriginal<typeof AgentStatusModule>()
return { ...actual, detectAgentStatusFromTitle: vi.fn().mockReturnValue(null) }
})
createStoreSessionMockApi()
const GIT = 'repo-1::/Users/dev/orca'
const terminal = (tabId: string) =>
['title', 'panes', 'focused pane', 'reattaches'].map(
(field) => `${GIT} terminal ${tabId} ${field}`
)
type KnownDifference = {
fixture: string
/** The Loader rule that makes the window show something else. */
rule: string
/** View paths that differ; see `projectStartupWindowView`. */
paths: string[]
}
/** Today's differences. Entries may only be removed: fix the rule, then delete its entry. */
const KNOWN_DIFFERENCES: readonly KnownDifference[] = [
{
fixture: 'row without a tab-bar entry or a terminal',
rule: 'ghost tab: the Loader gives every row a tab-bar entry; the window adopts only rows it can reattach',
paths: [`${GIT} group 0 tabs`, `${GIT} tab tab-b`, ...terminal('tab-b')]
},
{
fixture: 'legacy row repeating a tab-bar tab’s terminal',
rule: 'duplicate tab: the window drops a legacy row whose terminal a tab-bar tab holds; the Loader keeps it as a tab with an unbound pane',
paths: [`${GIT} group 0 tabs`, `${GIT} tab tab-legacy`, ...terminal('tab-legacy')]
},
{
fixture: 'row terminal differs from its focused pane’s',
rule: 'row.ptyId rewrite: the Loader sets the row terminal to its focused pane’s, which changes what the tab reattaches',
paths: [`${GIT} terminal tab-a reattaches`]
},
{
fixture: 'row with no terminal while its pane has one',
rule: 'row.ptyId rewrite: the Loader sets the row terminal to its focused pane’s, which changes what the tab reattaches',
paths: [`${GIT} terminal tab-a reattaches`]
},
{
fixture: 'tab-bar entry declaring a group that does not list it',
rule: 'group owner: the window keeps a tab in the group its entry declares; the Loader moves it to the first group listing it',
paths: [
`${GIT} groups`,
`${GIT} group 0 tabs`,
`${GIT} group 1 tabs`,
`${GIT} group 1 active tab`,
`${GIT} tab tab-b`
]
},
{
fixture: 'session from before the tab bar, with an open file',
rule: 'old sessions: the window builds tabs from rows and open files; the Loader builds them from rows only, losing editor tabs',
paths: [
`${GIT} group 0 tabs`,
`${GIT} group 0 active tab`,
`${GIT} tab /Users/dev/orca/src/app.ts`
]
},
{
fixture: 'legacy surface tombstone on a pane still showing its terminal',
rule: 'tombstones: the Loader closes a pane a legacy surface tombstone names; the window ignores the tombstone and shows the pane',
paths: [`${GIT} group 0 tabs`, `${GIT} tab tab-b`, ...terminal('tab-b')]
},
{
fixture: 'tab-bar label differing from the row title',
rule: 'label over title: the Loader writes the tab-bar label into the row title the window shows',
paths: [`${GIT} terminal tab-a title`]
}
]
/** The shared Loader's round trip, as main would persist it. */
function normalize(hostId: ExecutionHostId, session: WorkspaceSessionState): WorkspaceSessionState {
let next = 0
const loaded = loadWorkspaceLayout(hostId, structuredClone(session), {
mintId: () => `minted-${++next}`,
mintLeafId: () => `00000000-0000-4000-9000-${String(++next).padStart(12, '0')}`
})
return saveWorkspaceLayout(loaded)
}
const PROFILES = [...WORKSPACE_LAYOUT_PROFILES, ...LEGACY_WORKSPACE_LAYOUT_PROFILES]
describe('the window shows the same session before and after the shared Loader rewrites it', () => {
it.each(PROFILES.map((profile) => [profile.name, profile] as const))(
'%s',
async (name, { hostId, build }) => {
const stored = build()
const storedView = projectStartupWindowView(
await bootWindowFromPartitions(createTestStore(), [{ hostId, session: stored }])
)
const normalizedView = projectStartupWindowView(
await bootWindowFromPartitions(createTestStore(), [
{ hostId, session: normalize(hostId, stored) }
])
)
// The window must show the stored workspaces at all, or an empty view hides differences.
for (const [key, rows] of Object.entries(stored.tabsByWorktree)) {
if (rows.length > 0) {
expect(storedView, key).toHaveProperty([`${key} group 0 tabs`])
}
}
const differences = diffStartupWindowViews(storedView, normalizedView)
const known = KNOWN_DIFFERENCES.filter((entry) => entry.fixture === name)
expect(
differences.map((difference) => difference.path).sort(),
`window differences (stored vs normalized):\n${JSON.stringify(differences, null, 2)}`
).toEqual(known.flatMap((entry) => entry.paths).sort())
}
)
it('lists differences only for fixtures that exist', () => {
const names = new Set(PROFILES.map((profile) => profile.name))
expect(KNOWN_DIFFERENCES.filter((entry) => !names.has(entry.fixture))).toEqual([])
})
})
@@ -0,0 +1,212 @@
// Boots a test store through the window's startup session hydration and reduces the result to
// what the window shows, so two stored sessions can be compared by what a user would see.
import { vi } from 'vitest'
import { getDefaultWorkspaceSession } from '../../../shared/constants'
import {
LOCAL_EXECUTION_HOST_ID,
parseExecutionHostId,
type ExecutionHostId
} from '../../../shared/execution-host'
import type { TabGroupLayoutNode } from '../../../shared/tab-types'
import type {
TerminalLayoutSnapshot,
TerminalPaneLayoutNode
} from '../../../shared/terminal-tab-types'
import type { WorkspaceSessionState } from '../../../shared/workspace-session-state-types'
import { getRepoIdFromWorktreeId } from '../../../shared/worktree/id'
import { fetchWorkspaceSessionWithRuntimeHostOwners } from '@/lib/workspace-session-host-hydration'
import { collectFolderWorkspaceKeysFromSession } from '@/lib/workspace-session-hydration-keys'
import type { AppState } from '../store/types'
import type { createTestStore } from '../store/slices/store-test-helpers'
import { reconcileHydratedWorkspaceTabModels } from './reconcile-hydrated-workspace-tab-models'
/** One stored session partition and the host it is stored under. */
export type StoredPartition = { hostId: ExecutionHostId; session: WorkspaceSessionState }
/**
* The local catalog at first paint: a repo for each git workspace the partitions name, with no
* worktree listing yet. Remote-server repos are absent, as startup skips remote catalogs.
*/
function firstPaintRepos(partitions: readonly StoredPartition[]): AppState['repos'] {
const repos = new Map<string, AppState['repos'][number]>()
for (const { hostId, session } of partitions) {
const host = parseExecutionHostId(hostId)
if (host?.kind === 'runtime') {
continue
}
for (const key of Object.keys(session.tabsByWorktree)) {
const id = getRepoIdFromWorktreeId(key)
if (!key.startsWith(`${id}::`) || repos.has(id)) {
continue
}
repos.set(id, {
id,
path: key.slice(id.length + 2),
displayName: id,
badgeColor: '#000',
addedAt: 0,
connectionId: host?.kind === 'ssh' ? host.targetId : null,
executionHostId: hostId
})
}
}
return [...repos.values()]
}
/**
* Runs the same hydration calls, in the same order, as `use-app-startup-hydration.ts`, on a fresh
* test store the caller creates.
*/
export async function bootWindowFromPartitions(
store: ReturnType<typeof createTestStore>,
partitions: readonly StoredPartition[]
): Promise<AppState> {
let minted = 0
// Group and tab ids the window mints must match across the two boots being compared.
const randomUUID = vi
.spyOn(globalThis.crypto, 'randomUUID')
.mockImplementation(() => `00000000-0000-4000-8000-${String(++minted).padStart(12, '0')}`)
try {
store.setState({ repos: firstPaintRepos(partitions) })
const read = async (hostId: ExecutionHostId = LOCAL_EXECUTION_HOST_ID) =>
structuredClone(
partitions.find((partition) => partition.hostId === hostId)?.session ??
getDefaultWorkspaceSession()
)
const hostIdsOfKind = (kind: 'ssh' | 'runtime') =>
partitions
.map((partition) => partition.hostId)
.filter((hostId) => parseExecutionHostId(hostId)?.kind === kind)
const sessionRead = await fetchWorkspaceSessionWithRuntimeHostOwners(
{ get: read, listHostIds: async () => hostIdsOfKind('ssh') },
store.getState().repos,
hostIdsOfKind('runtime')
)
const actions = store.getState()
const options = {
additionalValidWorkspaceKeys: collectFolderWorkspaceKeysFromSession(sessionRead.session)
}
actions.hydrateWorkspaceSession(sessionRead.session, {
...options,
runtimeHostIdByWorkspaceSessionKey: sessionRead.runtimeHostIdByWorkspaceSessionKey,
contestedHostWorkspaceSessions: sessionRead.contestedHostWorkspaceSessions,
contestedPrimaryHostBySessionKey: sessionRead.contestedPrimaryHostBySessionKey
})
actions.hydrateTabsSession(sessionRead.session, options)
actions.hydrateEditorSession(sessionRead.session, options)
actions.hydrateBrowserSession(sessionRead.session, options)
reconcileHydratedWorkspaceTabModels(
sessionRead.session,
store.getState().reconcileWorktreeTabModels
)
return store.getState()
} finally {
randomUUID.mockRestore()
}
}
type PaneView = { pane: string | null } | { split: string; first: PaneView; second: PaneView }
/** Panes by position, each naming the terminal it binds; leaf ids are not shown. */
function paneView(node: TerminalPaneLayoutNode, layout: TerminalLayoutSnapshot): PaneView {
if (node.type === 'leaf') {
return { pane: layout.ptyIdsByLeafId?.[node.leafId] ?? null }
}
return {
split: node.direction,
first: paneView(node.first, layout),
second: paneView(node.second, layout)
}
}
function leafPosition(
node: TerminalPaneLayoutNode,
leafId: string | null,
path = ''
): string | null {
if (node.type === 'leaf') {
return node.leafId === leafId ? path || 'root' : null
}
return (
leafPosition(node.first, leafId, `${path}1`) ?? leafPosition(node.second, leafId, `${path}2`)
)
}
type GroupLayoutView = number | { split: string; first: GroupLayoutView; second: GroupLayoutView }
function groupLayoutView(node: TabGroupLayoutNode, groupIds: string[]): GroupLayoutView {
if (node.type === 'leaf') {
return groupIds.indexOf(node.groupId)
}
return {
split: node.direction,
first: groupLayoutView(node.first, groupIds),
second: groupLayoutView(node.second, groupIds)
}
}
/**
* What the window shows after startup, as `path -> value`. Groups are named by position, since
* a group id is never shown; tab ids stay, since a tab keeps its id across restarts.
*/
export function projectStartupWindowView(state: AppState): Record<string, unknown> {
const view: Record<string, unknown> = {
'active.workspace': state.activeWorktreeId,
'active.terminal': state.activeTabId
}
const workspaceKeys = new Set([
...Object.keys(state.tabsByWorktree),
...Object.keys(state.unifiedTabsByWorktree),
...state.openFiles.map((file) => file.worktreeId),
...Object.keys(state.browserTabsByWorktree)
])
for (const key of [...workspaceKeys].sort()) {
const groups = state.groupsByWorktree[key] ?? []
const groupIds = groups.map((group) => group.id)
const layout = state.layoutByWorktree[key]
view[`${key} groups`] = layout ? groupLayoutView(layout, groupIds) : groupIds.length
groups.forEach((group, index) => {
view[`${key} group ${index} tabs`] = group.tabOrder
view[`${key} group ${index} active tab`] = group.activeTabId
})
view[`${key} active group`] = groupIds.indexOf(state.activeGroupIdByWorktree[key] ?? '')
view[`${key} active terminal`] = state.activeTabIdByWorktree[key] ?? null
view[`${key} active file`] = state.activeFileIdByWorktree[key] ?? null
view[`${key} active kind`] = state.activeTabTypeByWorktree[key] ?? null
for (const tab of state.unifiedTabsByWorktree[key] ?? []) {
view[`${key} tab ${tab.id}`] = {
kind: tab.contentType,
shows: tab.entityId,
group: groupIds.indexOf(tab.groupId),
label: tab.customLabel ?? tab.label
}
}
for (const row of state.tabsByWorktree[key] ?? []) {
const panes = state.terminalLayoutsByTabId[row.id]
const terminal = `${key} terminal ${row.id}`
view[`${terminal} title`] = row.customTitle ?? row.title
view[`${terminal} panes`] = panes?.root ? paneView(panes.root, panes) : null
view[`${terminal} focused pane`] = panes?.root
? leafPosition(panes.root, panes.activeLeafId)
: null
view[`${terminal} reattaches`] = state.pendingReconnectPtyIdByTabId[row.id] ?? null
}
view[`${key} open files`] = state.openFiles
.filter((file) => file.worktreeId === key)
.map((file) => file.filePath)
view[`${key} browser tabs`] = (state.browserTabsByWorktree[key] ?? []).map((tab) => tab.url)
}
return view
}
/** Each path whose value differs between two views, with both values. */
export function diffStartupWindowViews(
stored: Record<string, unknown>,
normalized: Record<string, unknown>
): { path: string; stored: unknown; normalized: unknown }[] {
const paths = [...new Set([...Object.keys(stored), ...Object.keys(normalized)])].sort()
return paths
.filter((path) => JSON.stringify(stored[path]) !== JSON.stringify(normalized[path]))
.map((path) => ({ path, stored: stored[path], normalized: normalized[path] }))
}
@@ -0,0 +1,121 @@
// Stored partitions older writers leave disagreeing with themselves, one Loader rule each, so a
// test can compare what the window shows for each before and after the Loader rewrites it.
import { LOCAL_EXECUTION_HOST_ID } from '../execution-host'
import type { WorkspaceSessionState } from '../workspace-session-state-types'
import type { WorkspaceLayoutProfile } from './workspace-layout-profile.test-fixture'
import { addWorkspace, emptySession, GIT_KEY, leaf } from './workspace-layout-session.test-fixture'
function twoTabs(): WorkspaceSessionState {
return addWorkspace(emptySession(), GIT_KEY, [
{
id: 'g1',
tabs: [
{ id: 'tab-a', leaves: [[leaf(1), 'pty-a']] },
{ id: 'tab-b', leaves: [[leaf(2), 'pty-b']] }
]
}
])
}
/** Drops a terminal's tab-bar entry, leaving its row and pane layout. */
function withoutTabBarEntry(session: WorkspaceSessionState, tabId: string): WorkspaceSessionState {
session.unifiedTabs![GIT_KEY] = session.unifiedTabs![GIT_KEY]!.filter((tab) => tab.id !== tabId)
const group = session.tabGroups![GIT_KEY]![0]!
group.tabOrder = group.tabOrder.filter((id) => id !== tabId)
return session
}
const local = (name: string, build: () => WorkspaceSessionState): WorkspaceLayoutProfile => ({
name,
hostId: LOCAL_EXECUTION_HOST_ID,
build
})
export const LEGACY_WORKSPACE_LAYOUT_PROFILES: readonly WorkspaceLayoutProfile[] = [
local('row without a tab-bar entry or a terminal', () => {
const session = withoutTabBarEntry(twoTabs(), 'tab-b')
session.tabsByWorktree[GIT_KEY]![1]!.ptyId = null
session.terminalLayoutsByTabId['tab-b']!.ptyIdsByLeafId = {}
return session
}),
// Control for the case above: the window adopts a row it can reattach, as the Loader does.
local('row without a tab-bar entry, still bound to a terminal', () =>
withoutTabBarEntry(twoTabs(), 'tab-b')
),
local('legacy row repeating a tab-bar tab’s terminal', () => {
const session = twoTabs()
session.tabsByWorktree[GIT_KEY]!.push({
...session.tabsByWorktree[GIT_KEY]![0]!,
id: 'tab-legacy',
title: 'Terminal 3',
sortOrder: 2,
createdAt: 1_700_000_009_000
})
session.terminalLayoutsByTabId['tab-legacy'] = {
root: { type: 'leaf', leafId: leaf(3) },
activeLeafId: leaf(3),
expandedLeafId: null,
ptyIdsByLeafId: { [leaf(3)]: 'pty-a' }
}
return session
}),
local('row terminal differs from its focused pane’s', () => {
const session = twoTabs()
session.tabsByWorktree[GIT_KEY]![0]!.ptyId = 'pty-stale'
return session
}),
local('row with no terminal while its pane has one', () => {
const session = twoTabs()
session.tabsByWorktree[GIT_KEY]![0]!.ptyId = null
return session
}),
local('tab-bar entry declaring a group that does not list it', () => {
const session = addWorkspace(emptySession(), GIT_KEY, [
{ id: 'g1', tabs: [{ id: 'tab-a', leaves: [[leaf(1), 'pty-a']] }] },
{ id: 'g2', tabs: [{ id: 'tab-b', leaves: [[leaf(2), 'pty-b']] }] }
])
// g1 lists tab-b; its entry still says g2, and g2 lists it too.
session.tabGroups![GIT_KEY]![0]!.tabOrder.push('tab-b')
return session
}),
local('session from before the tab bar, with an open file', () => {
const session = twoTabs()
delete session.unifiedTabs
delete session.tabGroups
delete session.tabGroupLayouts
delete session.activeGroupIdByWorktree
session.openFilesByWorktree = {
[GIT_KEY]: [
{
filePath: '/Users/dev/orca/src/app.ts',
relativePath: 'src/app.ts',
worktreeId: GIT_KEY,
language: 'typescript'
}
]
}
session.activeFileIdByWorktree = { [GIT_KEY]: '/Users/dev/orca/src/app.ts' }
session.activeTabTypeByWorktree = { [GIT_KEY]: 'editor' }
return session
}),
local('legacy surface tombstone on a pane still showing its terminal', () => {
const session = twoTabs()
session.terminalSurfaceTombstonesByPaneKey = {
[`tab-b:${leaf(2)}`]: {
worktreeId: GIT_KEY,
parentTabId: 'tab-b',
leafId: leaf(2),
ptyId: 'pty-b',
incarnationId: 'inc',
retiredAt: 1
}
}
return session
}),
local('tab-bar label differing from the row title', () => {
const session = twoTabs()
session.unifiedTabs![GIT_KEY]![0]!.label = 'vim'
return session
})
]
@@ -1,7 +1,13 @@
// Real-shaped stored partitions: the desktop's own, a relay-fallback SSH host's, a remote server's.
import type { SleepingAgentSessionRecord } from '../agent-session-resume'
import { toRuntimeExecutionHostId, toSshExecutionHostId } from '../execution-host'
import { FLOATING_TERMINAL_WORKTREE_ID } from '../constants'
import {
LOCAL_EXECUTION_HOST_ID,
toRuntimeExecutionHostId,
toSshExecutionHostId,
type ExecutionHostId
} from '../execution-host'
import { toRemoteRuntimePtyId } from '../remote-runtime-pty-id'
import { toAppSshPtyId } from '../ssh-pty-id'
import type { WorkspaceSessionState } from '../workspace-session-state-types'
@@ -242,3 +248,38 @@ export function serverRuntimeSession(environmentId: string): WorkspaceSessionSta
toRuntimeExecutionHostId(environmentId)
)
}
export type WorkspaceLayoutProfile = {
name: string
/** The partition the session is stored under. */
hostId: ExecutionHostId
build: () => WorkspaceSessionState
}
/** One stored partition per kind of writer today. */
export const WORKSPACE_LAYOUT_PROFILES: readonly WorkspaceLayoutProfile[] = [
{
name: 'local desktop (git worktree, folder workspace, all tab kinds)',
hostId: LOCAL_EXECUTION_HOST_ID,
build: localDesktopSession
},
{
name: 'floating terminal',
hostId: LOCAL_EXECUTION_HOST_ID,
build: () =>
addWorkspace(emptySession(), FLOATING_TERMINAL_WORKTREE_ID, [
{ id: 'group-floating', tabs: [{ id: 'tab-floating', leaves: [[leaf(1), 'floating@@1']] }] }
])
},
{
name: 'relay SSH host partition',
hostId: toSshExecutionHostId('target-1'),
build: () => relaySshSession('target-1')
},
{
name: 'remote server partition',
hostId: toRuntimeExecutionHostId('env-1'),
build: () => serverRuntimeSession('env-1')
},
{ name: 'empty profile', hostId: LOCAL_EXECUTION_HOST_ID, build: emptySession }
]
@@ -1,5 +1,4 @@
import { describe, expect, it } from 'vitest'
import { FLOATING_TERMINAL_WORKTREE_ID } from '../constants'
import { LOCAL_EXECUTION_HOST_ID, type ExecutionHostId } from '../execution-host'
import type { WorkspaceSessionState } from '../workspace-session-state-types'
import { loadWorkspaceLayout } from './workspace-layout-load'
@@ -8,10 +7,9 @@ import { checkWorkspaceLayoutRules } from './workspace-layout-rules'
import { saveWorkspaceLayout } from './workspace-layout-save'
import {
localDesktopSession,
relaySshSession,
serverRuntimeSession
WORKSPACE_LAYOUT_PROFILES
} from './workspace-layout-profile.test-fixture'
import { addWorkspace, emptySession, leaf } from './workspace-layout-session.test-fixture'
import { leaf } from './workspace-layout-session.test-fixture'
/** What reaches disk: the profile documents are JSON, so an undefined field is a missing one. */
const onDisk = (session: WorkspaceSessionState): unknown => JSON.parse(JSON.stringify(session))
@@ -34,24 +32,9 @@ function roundTrip(hostId: ExecutionHostId, session: WorkspaceSessionState) {
return { loaded, saved: saveWorkspaceLayout(loaded) }
}
const PROFILES: [string, ExecutionHostId, () => WorkspaceSessionState][] = [
[
'local desktop (git worktree, folder workspace, all tab kinds)',
LOCAL_EXECUTION_HOST_ID,
localDesktopSession
],
[
'floating terminal',
LOCAL_EXECUTION_HOST_ID,
() =>
addWorkspace(emptySession(), FLOATING_TERMINAL_WORKTREE_ID, [
{ id: 'group-floating', tabs: [{ id: 'tab-floating', leaves: [[leaf(1), 'floating@@1']] }] }
])
],
['relay SSH host partition', 'ssh:target-1', () => relaySshSession('target-1')],
['remote server partition', 'runtime:env-1', () => serverRuntimeSession('env-1')],
['empty profile', LOCAL_EXECUTION_HOST_ID, emptySession]
]
const PROFILES = WORKSPACE_LAYOUT_PROFILES.map(
(profile) => [profile.name, profile.hostId, profile.build] as const
)
describe('workspace layout Loader and Serializer', () => {
it.each(PROFILES)(