import { describe, expect, test } from 'bun:test' /** Assertions are host-agnostic: the base URL is `getAppBaseUrl`'s concern, not this module's. */ function path(url: string): string { return url.replace(/^https?:\/\/[^/]+/, '') } describe('entity urls', () => { test('a canvas defaults to the craft tab', async () => { const { getCanvasTabUrl } = await import('./links.ts') expect(path(getCanvasTabUrl('c1'))).toBe('/prompt/c1/craft') }) test('canvas tabs are routes, so each one links', async () => { const { getCanvasTabUrl } = await import('./links.ts') expect(path(getCanvasTabUrl('c1', 'test'))).toBe('/prompt/c1/test') expect(path(getCanvasTabUrl('c1', 'workstation'))).toBe('/prompt/c1/workstation') }) test('a workflow with no options is the bare page', async () => { const { getWorkflowTabUrl } = await import('./links.ts') expect(path(getWorkflowTabUrl('w1'))).toBe('/workflows/w1') }) test('a workflow carries the deep-link query params', async () => { const { getWorkflowTabUrl } = await import('./links.ts') expect(path(getWorkflowTabUrl('w1', { tab: 'craft', nodeId: 'n1' }))) .toBe('/workflows/w1/craft?nodeId=n1') expect(path(getWorkflowTabUrl('w1', { tab: 'test', promptVersionId: 'v1', executionId: 'e1' }))) .toBe('/workflows/w1/test?promptVersionId=v1&executionId=e1') }) test('empty options never leave a dangling question mark', async () => { const { getWorkflowTabUrl, getAgentPageUrl } = await import('./links.ts') expect(getWorkflowTabUrl('w1', { tab: 'craft' })).not.toContain('?') expect(path(getAgentPageUrl('a1'))).toBe('/agent/a1') }) test('an agent takes only branch, and a session has its own route', async () => { const { getAgentPageUrl, getAgentSessionUrl } = await import('./links.ts') expect(path(getAgentPageUrl('a1', { branch: 'main' }))).toBe('/agent/a1?branch=main') expect(path(getAgentSessionUrl('s1'))).toBe('/agent/run/s1') }) }) describe('link sets', () => { test('a bare canvas offers only the editor', async () => { const { getCanvasLinks } = await import('./links.ts') expect(getCanvasLinks('c1')).toHaveLength(1) }) test('test cases and a workstation add their own entry points', async () => { const { getCanvasLinks } = await import('./links.ts') const links = getCanvasLinks('c1', { hasTestCases: true, hasWorkstation: true }) expect(links.map(link => path(link.url))).toEqual([ '/prompt/c1/craft', '/prompt/c1/test', '/prompt/c1/workstation', ]) }) test('a failing node changes both the label and the link', async () => { const { getWorkflowLinks } = await import('./links.ts') const [first] = getWorkflowLinks('w1', { nodeId: 'n1' }) expect(first!.label).toContain('step selected') expect(first!.url).toContain('nodeId=n1') }) test('a known run is preferred over the generic test tab', async () => { const { getWorkflowLinks } = await import('./links.ts') const links = getWorkflowLinks('w1', { executionId: 'e1', hasTestCases: true }) expect(links).toHaveLength(2) expect(links[1]!.url).toContain('executionId=e1') }) test('an agent session is offered alongside the agent', async () => { const { getAgentLinks } = await import('./links.ts') expect(getAgentLinks('a1', { sessionId: 's1' }).map(link => path(link.url))).toEqual([ '/agent/a1', '/agent/run/s1', ]) }) }) describe('formatLinks', () => { test('a single link reads as one sentence', async () => { const { formatLinks } = await import('./links.ts') expect(formatLinks([{ label: 'edit the prompt', url: 'https://x' }])) .toBe('You can see and debug it here: https://x') }) test('several links become a list with their labels', async () => { const { formatLinks } = await import('./links.ts') const output = formatLinks([ { label: 'edit the prompt', url: 'https://a' }, { label: 'review results', url: 'https://b' }, ]) expect(output).toContain('- edit the prompt: https://a') expect(output).toContain('- review results: https://b') }) test('the lead can be reworded for context', async () => { const { formatLinks } = await import('./links.ts') expect(formatLinks([{ label: 'x', url: 'https://a' }], 'Review it here')) .toBe('Review it here: https://a') }) test('no links renders as nothing rather than a stray heading', async () => { const { formatLinks } = await import('./links.ts') expect(formatLinks([])).toBe('') }) })