mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
test(desktop): cover the workspace-page reveal bug class
Both layers, and the classification underneath them: a page route fronts the pane whether it carries a query or not, moving between two pages fronts it again even though `$workspaceIsPage` never changes, contributed routes count, and chat and overlay targets leave the tab alone.
This commit is contained in:
parent
f6ea8b462e
commit
ac9a10ccbb
1 changed files with 189 additions and 0 deletions
189
apps/desktop/src/app/routes.workspace-reveal.test.ts
Normal file
189
apps/desktop/src/app/routes.workspace-reveal.test.ts
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
/**
|
||||
* A full page (Capabilities/Messaging/Artifacts/a contributed route) renders
|
||||
* INSIDE the `workspace` pane, so navigating to one has to front that pane —
|
||||
* otherwise a main zone parked on a session tile keeps the tile on screen and
|
||||
* the click looks dead until the app restarts (#72602).
|
||||
*
|
||||
* Two layers, both covered here: `syncWorkspaceRoute` (the router location,
|
||||
* every entry point) and `navigateToWorkspacePage` (the re-click, where the
|
||||
* location doesn't change).
|
||||
*/
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { registry } from '@/contrib/registry'
|
||||
|
||||
import {
|
||||
$workspaceIsPage,
|
||||
AGENTS_ROUTE,
|
||||
appViewForPath,
|
||||
ARTIFACTS_ROUTE,
|
||||
CRON_ROUTE,
|
||||
MESSAGING_ROUTE,
|
||||
navigateToWorkspacePage,
|
||||
NEW_CHAT_ROUTE,
|
||||
routePathname,
|
||||
ROUTES_AREA,
|
||||
routeSessionId,
|
||||
sessionRoute,
|
||||
SETTINGS_ROUTE,
|
||||
SKILLS_ROUTE,
|
||||
syncWorkspaceRoute
|
||||
} from './routes'
|
||||
|
||||
vi.mock('@/components/pane-shell/tree/store', async importOriginal => ({
|
||||
...(await importOriginal<Record<string, unknown>>()),
|
||||
noteActiveTreeGroup: vi.fn(),
|
||||
revealTreePane: vi.fn()
|
||||
}))
|
||||
|
||||
const { noteActiveTreeGroup, revealTreePane } = await import('@/components/pane-shell/tree/store')
|
||||
|
||||
const CONTRIBUTED_ROUTE = '/kanban'
|
||||
|
||||
function contributeRoute(): () => void {
|
||||
return registry.register({
|
||||
area: ROUTES_AREA,
|
||||
data: { path: CONTRIBUTED_ROUTE },
|
||||
id: 'test-route',
|
||||
render: () => null
|
||||
})
|
||||
}
|
||||
|
||||
/** Did the workspace pane get fronted? Both calls, or the tab stays put. */
|
||||
const fronted = () =>
|
||||
vi.mocked(revealTreePane).mock.calls.some(([pane]) => pane === 'workspace') &&
|
||||
vi.mocked(noteActiveTreeGroup).mock.calls.some(([group]) => group === null)
|
||||
|
||||
beforeEach(() => {
|
||||
vi.mocked(revealTreePane).mockClear()
|
||||
vi.mocked(noteActiveTreeGroup).mockClear()
|
||||
$workspaceIsPage.set(false)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
$workspaceIsPage.set(false)
|
||||
})
|
||||
|
||||
describe('routePathname', () => {
|
||||
it('keeps a bare path and drops a query or hash', () => {
|
||||
expect(routePathname(SKILLS_ROUTE)).toBe('/skills')
|
||||
expect(routePathname('/skills?tab=mcp')).toBe('/skills')
|
||||
expect(routePathname('/skills?tab=mcp&server=ctx7')).toBe('/skills')
|
||||
expect(routePathname('/settings#keys')).toBe('/settings')
|
||||
})
|
||||
|
||||
it('leaves an encoded session id alone', () => {
|
||||
const route = sessionRoute('a?b#c')
|
||||
|
||||
expect(routePathname(route)).toBe(route)
|
||||
expect(routeSessionId(route)).toBe('a?b#c')
|
||||
})
|
||||
})
|
||||
|
||||
describe('classification of targets carrying a query', () => {
|
||||
// The palette navigates to every one of these (Capabilities tabs, MCP
|
||||
// servers), and Settings redirects old /settings?tab=mcp deep links to the
|
||||
// last one. Unstripped, they parsed as SESSION ids and read as 'chat'.
|
||||
it.each([
|
||||
[`${SKILLS_ROUTE}?tab=skills`, 'skills'],
|
||||
[`${SKILLS_ROUTE}?tab=toolsets`, 'skills'],
|
||||
[`${SKILLS_ROUTE}?tab=mcp&server=ctx7`, 'skills'],
|
||||
[`${SETTINGS_ROUTE}?tab=keys`, 'settings']
|
||||
])('%s is not a session route', (to, view) => {
|
||||
expect(routeSessionId(to)).toBeNull()
|
||||
expect(appViewForPath(to)).toBe(view)
|
||||
})
|
||||
})
|
||||
|
||||
describe('syncWorkspaceRoute', () => {
|
||||
it('publishes and fronts on a page route', () => {
|
||||
syncWorkspaceRoute(SKILLS_ROUTE)
|
||||
|
||||
expect($workspaceIsPage.get()).toBe(true)
|
||||
expect(fronted()).toBe(true)
|
||||
})
|
||||
|
||||
it('fronts on a page route reached with a query', () => {
|
||||
syncWorkspaceRoute(`${SKILLS_ROUTE}?tab=mcp`)
|
||||
|
||||
expect($workspaceIsPage.get()).toBe(true)
|
||||
expect(fronted()).toBe(true)
|
||||
})
|
||||
|
||||
it('fronts when moving between two pages — the atom never changes, the tab must', () => {
|
||||
syncWorkspaceRoute(ARTIFACTS_ROUTE)
|
||||
vi.mocked(revealTreePane).mockClear()
|
||||
vi.mocked(noteActiveTreeGroup).mockClear()
|
||||
|
||||
syncWorkspaceRoute(MESSAGING_ROUTE)
|
||||
|
||||
expect($workspaceIsPage.get()).toBe(true)
|
||||
expect(fronted()).toBe(true)
|
||||
})
|
||||
|
||||
it('fronts on a contributed page route', () => {
|
||||
const dispose = contributeRoute()
|
||||
|
||||
try {
|
||||
syncWorkspaceRoute(CONTRIBUTED_ROUTE)
|
||||
|
||||
expect(appViewForPath(CONTRIBUTED_ROUTE)).toBe('extension')
|
||||
expect(fronted()).toBe(true)
|
||||
} finally {
|
||||
dispose()
|
||||
}
|
||||
})
|
||||
|
||||
it.each([
|
||||
['a session route', sessionRoute('sess-a')],
|
||||
['the new-chat route', NEW_CHAT_ROUTE],
|
||||
['an overlay', SETTINGS_ROUTE],
|
||||
['an overlay with a query', `${SETTINGS_ROUTE}?tab=keys`],
|
||||
['another overlay', CRON_ROUTE],
|
||||
['yet another overlay', AGENTS_ROUTE]
|
||||
])('leaves the tab alone on %s', (_label, to) => {
|
||||
syncWorkspaceRoute(to)
|
||||
|
||||
expect($workspaceIsPage.get()).toBe(false)
|
||||
expect(revealTreePane).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('navigateToWorkspacePage', () => {
|
||||
it('navigates and fronts, so a re-click on the page you are already on still shows it', () => {
|
||||
const navigate = vi.fn()
|
||||
|
||||
navigateToWorkspacePage(navigate, SKILLS_ROUTE)
|
||||
|
||||
expect(navigate).toHaveBeenCalledWith(SKILLS_ROUTE, undefined)
|
||||
expect(fronted()).toBe(true)
|
||||
})
|
||||
|
||||
it.each([`${SKILLS_ROUTE}?tab=skills`, `${SKILLS_ROUTE}?tab=toolsets`, `${SKILLS_ROUTE}?tab=mcp&server=ctx7`])(
|
||||
'fronts for the palette target %s',
|
||||
to => {
|
||||
navigateToWorkspacePage(vi.fn(), to)
|
||||
|
||||
expect(fronted()).toBe(true)
|
||||
}
|
||||
)
|
||||
|
||||
it('passes navigation options through', () => {
|
||||
const navigate = vi.fn()
|
||||
|
||||
navigateToWorkspacePage(navigate, ARTIFACTS_ROUTE, { replace: true })
|
||||
|
||||
expect(navigate).toHaveBeenCalledWith(ARTIFACTS_ROUTE, { replace: true })
|
||||
})
|
||||
|
||||
it('navigates without fronting for chat and overlay targets', () => {
|
||||
const navigate = vi.fn()
|
||||
|
||||
navigateToWorkspacePage(navigate, sessionRoute('sess-a'))
|
||||
navigateToWorkspacePage(navigate, SETTINGS_ROUTE)
|
||||
|
||||
expect(navigate).toHaveBeenCalledTimes(2)
|
||||
expect(revealTreePane).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue