Files
orca/src/cli/linear-format.test.ts
T

203 lines
5.4 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest'
import type {
LinearIssueContextResult,
LinearMcpIssueListResult,
LinearSearchResult
} from '../shared/linear/agent-access'
import {
formatLinearIssue,
formatLinearMcpIssueList,
printLinearMcpIssueListWarnings,
printLinearSearchWarnings
} from './linear-format'
describe('linear-format', () => {
beforeEach(() => {
vi.restoreAllMocks()
vi.spyOn(console, 'error').mockImplementation(() => {})
})
it('treats older search results without workspaceErrors as non-partial', () => {
const result = {
issues: [],
meta: {
query: 'auth',
workspaceId: 'all',
limit: 20,
returned: 0,
limitReached: false,
partial: false
}
} as unknown as LinearSearchResult
printLinearSearchWarnings(result)
expect(console.error).not.toHaveBeenCalled()
})
it('binds a list continuation hint to its Linear workspace', () => {
printLinearMcpIssueListWarnings({
issues: [],
truncated: true,
meta: {
limit: 20,
returned: 20,
hasMore: true,
nextCursor: 'next-page',
orderBy: 'updatedAt',
workspaceId: 'workspace-1',
partial: false,
workspaceErrors: []
}
} as LinearMcpIssueListResult)
expect(console.error).toHaveBeenCalledWith(
'warning: more results available; next cursor: next-page; continue with --workspace workspace-1'
)
})
it('prints a stdout truncation marker when a list-issues page is partial', () => {
const output = formatLinearMcpIssueList({
issues: [
{
id: 'issue-1',
identifier: 'ENG-1',
title: 'Fix auth',
url: 'https://linear.app/acme/issue/ENG-1',
labels: [],
state: { name: 'In Progress' },
assignee: { displayName: 'Ada' },
workspace: { id: 'workspace-1', name: 'Acme' }
}
],
truncated: true,
meta: {
limit: 1,
returned: 1,
hasMore: true,
nextCursor: 'next-page',
orderBy: 'updatedAt',
workspaceId: 'workspace-1',
partial: false,
workspaceErrors: []
}
})
expect(output).toContain('ENG-1')
expect(output).toContain('truncated: showing 1')
expect(output).not.toContain(' of ')
})
const OSC_52 = '\u001b]52;c;cGF5bG9hZA==\u0007'
const CLEAR_SCREEN = '\u001b[2J'
it('strips escape sequences, C1 bytes and line breaks from issue fields', () => {
const result: LinearIssueContextResult = {
issue: {
id: 'issue-1',
identifier: 'ENG-123',
title: `Fix${OSC_52} auth\r\nState: Done`,
url: 'https://linear.app/acme/issue/ENG-123',
state: { name: `In Progress${CLEAR_SCREEN}` },
assignee: { displayName: 'Ada\u009b31m' },
project: { id: 'project-1', name: 'Launch' },
labels: [{ id: 'label-1', name: `bug${OSC_52}` }],
priority: 2,
estimate: 5
},
meta: {
requested: {
current: false,
include: {
comments: false,
children: false,
attachments: false,
relations: false,
activity: false
},
depth: 0
},
resolved: {
id: 'issue-1',
identifier: 'ENG-123',
workspaceId: 'workspace-1',
workspaceName: 'Acme'
},
partial: false,
includeErrors: [],
sections: {}
}
}
expect(formatLinearIssue(result)).toBe(
[
'ENG-123 Fix auth State: Done',
'URL: https://linear.app/acme/issue/ENG-123',
'State: In Progress',
'Assignee: Ada',
'Project: Launch',
'Priority: high',
'Estimate: 5',
'Labels: bug'
].join('\n')
)
})
it('keeps one aligned row per issue when a title carries a line break', () => {
const output = formatLinearMcpIssueList({
issues: [
{
id: 'issue-1',
identifier: 'ENG-1',
title: `Fix auth\r\nENG-2 Done${CLEAR_SCREEN}`,
url: 'https://linear.app/acme/issue/ENG-1',
labels: [],
state: { name: `In${OSC_52} Progress` },
assignee: { displayName: 'Ada\u009d0;pwned\u009c' },
workspace: { id: 'workspace-1', name: 'Acme' }
}
],
truncated: false,
meta: {
limit: 50,
returned: 1,
hasMore: false,
orderBy: 'updatedAt',
workspaceId: 'workspace-1',
partial: false,
workspaceErrors: []
}
})
expect(output).toBe(
`${'ENG-1'.padEnd(10)} ${'In Progress'.padEnd(14)} ${'Ada'.padEnd(18)} Fix auth ENG-2 Done`
)
})
it('strips control sequences from Linear-provided warning text', () => {
printLinearMcpIssueListWarnings({
issues: [],
truncated: false,
meta: {
limit: 20,
returned: 0,
hasMore: false,
orderBy: 'updatedAt',
workspaceId: 'all',
partial: true,
workspaceErrors: [
{
workspace: { id: 'workspace-1', name: `Acme${OSC_52}` },
code: 'linear_rate_limited',
message: `rate limited\r\nwarning: fake${CLEAR_SCREEN}`
}
]
}
})
expect(console.error).toHaveBeenCalledWith(
'warning: Acme unavailable for Linear: rate limited warning: fake'
)
})
})