diff --git a/config/scripts/ci-shard-timings.json b/config/scripts/ci-shard-timings.json
index 035297f2833..e0b04bde0a1 100644
--- a/config/scripts/ci-shard-timings.json
+++ b/config/scripts/ci-shard-timings.json
@@ -7227,7 +7227,6 @@
"src/renderer/src/components/github-item-dialog/load-item-details/use-github-task-refresh.test.tsx": 1070,
"src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog.task-record.test.tsx": 1391,
"src/renderer/src/components/github-link-copy-state.test.ts": 41,
- "src/renderer/src/components/github-pr-merge-state.test.ts": 243,
"src/renderer/src/components/github-project/GhAuthErrorHelp.test.ts": 479,
"src/renderer/src/components/github-project/ProjectBoard.test.tsx": 988,
"src/renderer/src/components/github-project/ProjectRoadmap.test.tsx": 2369,
diff --git a/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog.task-record.test.tsx b/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog.task-record.test.tsx
index f973d03bc05..44d44b31b8d 100644
--- a/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog.task-record.test.tsx
+++ b/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog.task-record.test.tsx
@@ -13,7 +13,7 @@ import type { AppState } from '@/store/types'
import type { Repo } from '../../../../../shared/repo-types'
import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types'
import { buildTaskSourceContextFromRepo } from '../../../../../shared/task-source-context'
-import { useGitHubTaskItems } from '@/hooks/useGitHubTaskItems'
+
import { useTaskPageGitHubWorkItemMutation } from '@/hooks/useTaskPageGitHubWorkItemMutation'
import { parseTaskQuery } from '../../../../../shared/task-query'
import type { GitHubProjectTable } from '../../../../../shared/github/project-types'
@@ -220,10 +220,7 @@ function renderDialog() {
)
})
}
-function TasksRow({ rows }: { rows: GitHubWorkItem[] }) {
- const [projected] = useGitHubTaskItems(rows)
- return
-}
+
const text = (field: string) => node.querySelector(`[data-field="${field}"]`)?.textContent
async function click(label: string) {
const button = [...node.querySelectorAll('button')].find((b) => b.textContent === label)
@@ -262,38 +259,6 @@ describe('issue dialog edits reach the Tasks row through the shared record', ()
node.remove()
})
- it('assigns in the dialog, keeps it through a stale search, and starts with the item', async () => {
- const rows = answered(await mocks.store!.getState().fetchWorkItems(repo.id, repo.path, 20, ''))
- await act(async () => {
- root.render(
- <>
-
- {}}
- />
- >
- )
- })
- await click('Assign me')
- await act(async () => {
- await Promise.resolve()
- })
- expect(mocks.update).toHaveBeenCalledOnce()
- expect(text('tasks')).toBe('alice')
- expect(text('dialog')).toBe('alice')
- const stale = answered(
- await mocks.store!.getState().fetchWorkItems(repo.id, repo.path, 20, '', { force: true })
- )
- expect(stale[0].assignees?.map((u) => u.login)).toEqual(['alice'])
- await click('Start')
- expect(mocks.onUse).toHaveBeenCalledWith(expect.objectContaining({ id: row.id }))
- })
-
it.each([
['tasks', false],
['tasks', true],
diff --git a/src/renderer/src/components/github-pr-merge-state.test.ts b/src/renderer/src/components/github-pr-merge-state.test.ts
deleted file mode 100644
index dfc8a6be064..00000000000
--- a/src/renderer/src/components/github-pr-merge-state.test.ts
+++ /dev/null
@@ -1,288 +0,0 @@
-import { summarizeProviderChecks } from '../../../shared/source-control/checks/provider-check-summary'
-import { describe, expect, it } from 'vitest'
-import type { GitHubPRMergeStateInput } from '../../../shared/github/pull-request-merge-state'
-import { presentGitHubPRMergeState } from './github-pr-merge-state'
-
-function pr(overrides: Partial = {}): GitHubPRMergeStateInput {
- return {
- state: 'open',
- mergeable: 'MERGEABLE',
- mergeStateStatus: 'CLEAN',
- checksSummary: { state: 'success', total: 1, passed: 1, failed: 0, pending: 0, neutral: 0 },
- autoMergeAllowed: true,
- ...overrides
- }
-}
-
-describe('presentGitHubPRMergeState', () => {
- it('blocks direct merge when approval is required or changes are requested', () => {
- expect(presentGitHubPRMergeState(pr({ reviewDecision: 'REVIEW_REQUIRED' }))).toMatchObject({
- label: 'Approval required',
- directMergeAvailable: false
- })
- expect(presentGitHubPRMergeState(pr({ reviewDecision: 'CHANGES_REQUESTED' }))).toMatchObject({
- label: 'Changes requested',
- directMergeAvailable: false
- })
- })
-
- it('uses the merge-queue label for auto-merge when a queue is required', () => {
- expect(presentGitHubPRMergeState(pr({ mergeQueueRequired: true }))).toMatchObject({
- label: 'Merge when ready',
- directMergeAvailable: false,
- autoMergeAction: { kind: 'enable', label: 'Merge when ready' }
- })
- })
-
- it('blocks conflicts on merge-queue branches', () => {
- expect(
- presentGitHubPRMergeState(
- pr({ mergeQueueRequired: true, mergeable: 'UNKNOWN', mergeStateStatus: 'DIRTY' })
- )
- ).toMatchObject({
- label: 'Conflicts',
- directMergeAvailable: false,
- autoMergeAction: null
- })
- })
-
- it('offers enable auto-merge for open PRs that are waiting on requirements', () => {
- expect(presentGitHubPRMergeState(pr({ mergeQueueRequired: null })).autoMergeAction).toBeNull()
- expect(
- presentGitHubPRMergeState(
- pr({ reviewDecision: 'REVIEW_REQUIRED', mergeStateStatus: 'BLOCKED' })
- ).autoMergeAction
- ).toMatchObject({ kind: 'enable', label: 'Enable auto-merge' })
- expect(
- presentGitHubPRMergeState(
- pr({
- mergeStateStatus: 'BLOCKED',
- checksSummary: {
- state: 'pending',
- total: 1,
- passed: 0,
- failed: 0,
- pending: 1,
- neutral: 0
- }
- })
- ).autoMergeAction
- ).toMatchObject({ kind: 'enable', label: 'Enable auto-merge' })
- })
-
- it('does not offer enable auto-merge when only optional checks are pending', () => {
- expect(
- presentGitHubPRMergeState(
- pr({
- checksSummary: {
- state: 'pending',
- total: 1,
- passed: 0,
- failed: 0,
- pending: 1,
- neutral: 0
- }
- })
- )
- ).toMatchObject({
- label: 'Checks pending',
- directMergeAvailable: true,
- autoMergeAction: null
- })
- })
-
- it.each(['UNSTABLE', 'HAS_HOOKS'])(
- 'allows a direct merge for %s even before conflict detection finishes',
- (mergeStateStatus) => {
- expect(
- presentGitHubPRMergeState(
- pr({
- mergeable: 'UNKNOWN',
- mergeStateStatus,
- checksSummary: {
- state: 'failure',
- total: 1,
- passed: 0,
- failed: 1,
- pending: 0,
- neutral: 0
- }
- })
- )
- ).toMatchObject({
- label: 'Checks failed',
- directMergeAvailable: true,
- autoMergeAction: null
- })
- }
- )
-
- it('does not offer enable auto-merge on conflicting PRs (GitHub would reject it)', () => {
- expect(
- presentGitHubPRMergeState(pr({ mergeable: 'CONFLICTING', mergeStateStatus: 'DIRTY' }))
- .autoMergeAction
- ).toBeNull()
- expect(
- presentGitHubPRMergeState(pr({ mergeable: 'UNKNOWN', mergeStateStatus: 'DIRTY' }))
- .autoMergeAction
- ).toBeNull()
- })
-
- it('does not offer enable auto-merge when GitHub reports the repository disallows it', () => {
- expect(
- presentGitHubPRMergeState(
- pr({ autoMergeAllowed: false, mergeable: 'UNKNOWN', mergeStateStatus: 'BLOCKED' })
- ).autoMergeAction
- ).toBeNull()
- expect(
- presentGitHubPRMergeState(
- pr({ autoMergeAllowed: undefined, mergeable: 'UNKNOWN', mergeStateStatus: 'BLOCKED' })
- ).autoMergeAction
- ).toMatchObject({ kind: 'enable', label: 'Enable auto-merge' })
- })
-
- it('offers disable auto-merge when GitHub reports auto-merge is already enabled', () => {
- expect(presentGitHubPRMergeState(pr({ autoMergeEnabled: true }))).toMatchObject({
- autoMergeAction: { kind: 'disable', label: 'Disable auto-merge' }
- })
- })
-
- it('suppresses enable auto-merge but keeps disable when direct merge is available', () => {
- // GitHub rejects enabling auto-merge on directly mergeable PRs with a clean-status error.
- expect(
- presentGitHubPRMergeState({
- state: 'open',
- checksSummary: { state: 'success', total: 1, passed: 1, failed: 0, pending: 0, neutral: 0 }
- })
- ).toMatchObject({ label: 'Checks passed', directMergeAvailable: true, autoMergeAction: null })
- expect(presentGitHubPRMergeState(pr())).toMatchObject({
- directMergeAvailable: true,
- autoMergeAction: null
- })
- expect(presentGitHubPRMergeState(pr({ autoMergeEnabled: true })).autoMergeAction).toMatchObject(
- { kind: 'disable' }
- )
- })
-
- it('blocks conflicts and behind branches, but not optional aggregate check failures', () => {
- expect(
- presentGitHubPRMergeState(pr({ mergeable: 'CONFLICTING', mergeStateStatus: 'DIRTY' }))
- ).toMatchObject({ label: 'Conflicts', directMergeAvailable: false })
- expect(
- presentGitHubPRMergeState(pr({ mergeable: 'UNKNOWN', mergeStateStatus: 'DIRTY' }))
- ).toMatchObject({ label: 'Conflicts', directMergeAvailable: false })
- expect(presentGitHubPRMergeState(pr({ mergeStateStatus: 'BEHIND' }))).toMatchObject({
- label: 'Behind',
- directMergeAvailable: false
- })
- expect(
- presentGitHubPRMergeState(
- pr({
- checksSummary: {
- state: 'pending',
- total: 1,
- passed: 0,
- failed: 0,
- pending: 1,
- neutral: 0
- }
- })
- )
- ).toMatchObject({ label: 'Checks pending', directMergeAvailable: true })
- expect(
- presentGitHubPRMergeState(
- pr({
- checksSummary: {
- state: 'failure',
- total: 1,
- passed: 0,
- failed: 1,
- pending: 0,
- neutral: 0
- }
- })
- )
- ).toMatchObject({ label: 'Checks failed', directMergeAvailable: true })
- expect(presentGitHubPRMergeState(pr())).toMatchObject({
- label: 'Able to merge',
- directMergeAvailable: true
- })
- })
-
- it('labels unresolved GitHub mergeability as checking', () => {
- expect(
- presentGitHubPRMergeState(
- pr({
- mergeable: 'UNKNOWN',
- mergeStateStatus: null,
- checksSummary: {
- state: 'pending',
- total: 1,
- passed: 0,
- failed: 0,
- pending: 1,
- neutral: 0
- }
- })
- )
- ).toMatchObject({
- label: 'Checking',
- directMergeAvailable: false
- })
- })
-
- it('allows direct merge when GitHub mergeability is unavailable but checks have passed', () => {
- expect(
- presentGitHubPRMergeState({
- state: 'open',
- checksSummary: { state: 'success', total: 3, passed: 3, failed: 0, pending: 0, neutral: 0 }
- })
- ).toMatchObject({
- label: 'Checks passed',
- directMergeAvailable: true
- })
- expect(
- presentGitHubPRMergeState(
- pr({
- mergeable: 'UNKNOWN',
- mergeStateStatus: null,
- checksSummary: {
- state: 'success',
- total: 3,
- passed: 3,
- failed: 0,
- pending: 0,
- neutral: 0
- }
- })
- )
- ).toMatchObject({
- label: 'Checks passed',
- directMergeAvailable: true
- })
- })
-
- it('suppresses auto-merge actions for non-open PR states', () => {
- expect(
- presentGitHubPRMergeState(pr({ state: 'closed', mergeQueueRequired: true })).autoMergeAction
- ).toBeNull()
- expect(
- presentGitHubPRMergeState(pr({ state: 'merged', autoMergeEnabled: true })).autoMergeAction
- ).toBeNull()
- expect(
- presentGitHubPRMergeState(pr({ state: 'draft', mergeQueueRequired: true })).autoMergeAction
- ).toBeNull()
- })
- it('names cancelled checks accurately without changing the provider merge permission', () => {
- const checksSummary = summarizeProviderChecks([
- { status: 'completed', conclusion: 'success' },
- { status: 'completed', conclusion: 'cancelled' }
- ])
- expect(presentGitHubPRMergeState(pr({ checksStatus: 'failure', checksSummary }))).toMatchObject(
- {
- label: 'Cancelled',
- directMergeAvailable: true
- }
- )
- })
-})
diff --git a/src/renderer/src/components/github-project/ProjectRoadmap.test.tsx b/src/renderer/src/components/github-project/ProjectRoadmap.test.tsx
index 2a7585f33c6..f4b04c435f4 100644
--- a/src/renderer/src/components/github-project/ProjectRoadmap.test.tsx
+++ b/src/renderer/src/components/github-project/ProjectRoadmap.test.tsx
@@ -1,12 +1,12 @@
// @vitest-environment happy-dom
import type { ReactNode } from 'react'
-import { act, cleanup, fireEvent, render, screen } from '@testing-library/react'
+import { act, cleanup, render, screen } from '@testing-library/react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import ProjectRoadmap from './ProjectRoadmap'
import { i18n, loadRendererUiLanguage, setRendererPluginLanguagePacks } from '@/i18n/i18n'
import { DEFAULT_LOCALE } from '@/i18n/supported-languages'
-import { pluginLanguageResourceId } from '../../../../shared/plugins/artifacts/plugin-language-pack-artifact'
+
import type {
GitHubProjectField,
GitHubProjectFieldValue,
@@ -32,12 +32,6 @@ const TARGET_FIELD: GitHubProjectField = {
name: 'Target date',
dataType: 'DATE'
}
-const TITLE_FIELD: GitHubProjectField = {
- kind: 'field',
- id: 'f_title',
- name: 'Title',
- dataType: 'TITLE'
-}
function row(id: string, title: string, values: GitHubProjectFieldValue[]): GitHubProjectRow {
const fieldValuesByFieldId: Record = {}
@@ -106,56 +100,6 @@ afterEach(async () => {
})
describe('ProjectRoadmap', () => {
- it.each([
- ['pt-BR', 'pt-BR'],
- ['en-US-US', DEFAULT_LOCALE]
- ])('renders date ticks and bars with plugin locale %s', async (packLocale, dateLocale) => {
- const packId = 'plugin:stablyai.orca-portuguese/pt-BR' as const
- const resourceLanguage = pluginLanguageResourceId(packId)
- setRendererPluginLanguagePacks([
- {
- id: packId,
- resourceLanguage,
- pluginKey: 'stablyai.orca-portuguese',
- locale: packLocale,
- catalog: { common: { cancel: 'Cancelar' } },
- coverage: 100
- }
- ])
- await i18n.changeLanguage(await loadRendererUiLanguage(packId))
- expect(i18n.resolvedLanguage).toBe(resourceLanguage)
-
- render(
- list}
- />
- )
-
- const month = new Intl.DateTimeFormat(dateLocale, {
- month: 'short',
- timeZone: 'UTC'
- }).format(new Date(Date.UTC(2026, 8, 1)))
- expect(screen.getAllByText(month, { exact: false }).length).toBeGreaterThan(0)
- const dateFormat = new Intl.DateTimeFormat(dateLocale, {
- year: 'numeric',
- month: 'short',
- day: 'numeric',
- timeZone: 'UTC'
- })
- const start = dateFormat.format(new Date(Date.UTC(2026, 8, 1)))
- const end = dateFormat.format(new Date(Date.UTC(2026, 9, 15)))
- expect(screen.getByRole('button', { name: `Scheduled — ${start} – ${end}` })).toBeTruthy()
- })
-
it('moves the today marker across local midnight without resetting scroll and cleans up its timer', () => {
vi.useFakeTimers()
vi.setSystemTime(new Date(2026, 8, 5, 23, 59, 59))
@@ -179,165 +123,4 @@ describe('ProjectRoadmap', () => {
unmount()
expect(vi.getTimerCount()).toBe(0)
})
-
- it.each([false, true])(
- 'centers when an initially empty view gains dated rows (fields hidden: %s)',
- (hidden) => {
- vi.useFakeTimers()
- vi.setSystemTime(new Date(2026, 8, 5, 12))
- const fields = hidden ? [TITLE_FIELD] : [TITLE_FIELD, START_FIELD, TARGET_FIELD]
- const { rerender } = render(
- list} />
- )
- const populated = table(fields, [
- row('one', 'Arrived', [
- { kind: 'date', fieldId: 'f_start', date: '2026-01-01' },
- { kind: 'date', fieldId: 'f_end', date: '2026-09-10' }
- ])
- ])
- rerender(list} />)
- const scroller = screen.getByTestId('project-roadmap-scroller')
- expect(scroller.scrollLeft).toBeGreaterThan(1000)
- scroller.scrollLeft = 123
- rerender(
- list}
- />
- )
- expect(scroller.scrollLeft).toBe(123)
- fireEvent.click(screen.getByRole('button', { name: 'Year' }))
- expect(scroller.scrollLeft).not.toBe(123)
- expect(window.localStorage.getItem('orca.githubProject.roadmapZoom')).toBe('year')
- }
- )
-
- it('places a dated row on the timeline and names the fields driving it', () => {
- render(
- list}
- />
- )
- expect(screen.getByText('Placed by Start date → Target date')).toBeTruthy()
- expect(screen.getByLabelText(/^Ship the thing — /)).toBeTruthy()
- expect(screen.queryByText('list')).toBeNull()
- })
-
- it('keeps an undated row in place and flags it rather than hiding it', () => {
- render(
- list}
- />
- )
- expect(screen.getByText('No dates')).toBeTruthy()
- expect(screen.getByText('1 without dates')).toBeTruthy()
- expect(screen.queryByLabelText(/^Undated — /)).toBeNull()
- })
-
- it('opens the row dialog when a bar is clicked', () => {
- const onOpenDialog = vi.fn()
- render(
- list}
- />
- )
- fireEvent.click(screen.getByLabelText(/^Ship the thing — /))
- expect(onOpenDialog).toHaveBeenCalledTimes(1)
- expect(onOpenDialog.mock.calls[0]?.[0]).toMatchObject({ id: 'PVTI_1' })
- })
-
- it('places items from row-carried dates when the view hides its date fields', () => {
- render(
- list}
- />
- )
- expect(screen.getByText('Placed by Start date → Target date')).toBeTruthy()
- expect(screen.getByLabelText(/^Hidden-field item — /)).toBeTruthy()
- expect(screen.queryByText('list')).toBeNull()
- })
-
- it('announces restricted items by name in the bar label', () => {
- const redacted: GitHubProjectRow = {
- ...row('PVTI_9', '', [
- { kind: 'date', fieldId: 'f_start', date: '2026-03-02' },
- { kind: 'date', fieldId: 'f_end', date: '2026-03-05' }
- ]),
- itemType: 'REDACTED'
- }
- render(
- list}
- />
- )
- expect(screen.getByLabelText(/^Restricted item — /)).toBeTruthy()
- })
-
- it('falls back to the caller-supplied list when no field can place items', () => {
- render(list} />)
- expect(screen.getByText('list')).toBeTruthy()
- expect(
- screen.getByText(
- 'This roadmap view has no date or iteration field to place items on, so Orca is listing them instead.'
- )
- ).toBeTruthy()
- })
-
- it('reports an empty filter result instead of drawing an empty grid', () => {
- render(
- list}
- />
- )
- expect(screen.getByText("No items match this view's filter.")).toBeTruthy()
- expect(screen.queryByText('list')).toBeNull()
- })
-
- it('does not blame a filter an unfiltered roadmap does not have', () => {
- render(
- list}
- />
- )
- expect(screen.getByText('This view has no items yet.')).toBeTruthy()
- expect(screen.queryByText("No items match this view's filter.")).toBeNull()
- })
})
diff --git a/src/renderer/src/components/github-project/group-sort.test.ts b/src/renderer/src/components/github-project/group-sort.test.ts
index 96d2c6c6c3f..1bd86e19e60 100644
--- a/src/renderer/src/components/github-project/group-sort.test.ts
+++ b/src/renderer/src/components/github-project/group-sort.test.ts
@@ -19,17 +19,6 @@ const singleSelectField: GitHubProjectField = {
]
}
-const iterationField: GitHubProjectField = {
- kind: 'iteration',
- id: 'F_iter',
- name: 'Iteration',
- dataType: 'ITERATION',
- iterations: [
- { id: 'iter_1', title: 'Sprint 1', startDate: '2026-01-01', duration: 14, completed: false },
- { id: 'iter_2', title: 'Sprint 2', startDate: '2026-01-15', duration: 14, completed: false }
- ]
-}
-
const assigneesField: GitHubProjectField = {
kind: 'field',
id: 'F_assignees',
@@ -37,20 +26,6 @@ const assigneesField: GitHubProjectField = {
dataType: 'ASSIGNEES'
}
-const labelsField: GitHubProjectField = {
- kind: 'field',
- id: 'F_labels',
- name: 'Labels',
- dataType: 'LABELS'
-}
-
-const textField: GitHubProjectField = {
- kind: 'field',
- id: 'F_text',
- name: 'Notes',
- dataType: 'TEXT'
-}
-
const numberField: GitHubProjectField = {
kind: 'field',
id: 'F_points',
@@ -117,35 +92,6 @@ function makeTable(view: GitHubProjectView, rows: GitHubProjectRow[]): GitHubPro
}
describe('sortRows', () => {
- it('orders rows by single-select option order', () => {
- const view = makeView(singleSelectField, {
- direction: 'ASC',
- field: singleSelectField
- })
- const rows = [
- makeRow('r2', 1, {
- F_status: {
- kind: 'single-select',
- fieldId: 'F_status',
- optionId: 'opt_b',
- name: 'In Progress',
- color: 'YELLOW'
- }
- }),
- makeRow('r1', 0, {
- F_status: {
- kind: 'single-select',
- fieldId: 'F_status',
- optionId: 'opt_a',
- name: 'Todo',
- color: 'GRAY'
- }
- })
- ]
- const sorted = sortRows(makeTable(view, rows), rows)
- expect(sorted.map((r) => r.id)).toEqual(['r1', 'r2'])
- })
-
it('does not produce NaN when both rows reference unknown single-select options', () => {
// Avoid Infinity - Infinity producing NaN and bypassing the position tie-break for orphaned options.
const view = makeView(singleSelectField, {
@@ -176,58 +122,6 @@ describe('sortRows', () => {
expect(sorted.map((r) => r.id)).toEqual(['rA', 'rB'])
})
- it('does not produce NaN when both rows reference unknown iteration ids', () => {
- const view = makeView(iterationField, {
- direction: 'ASC',
- field: iterationField
- })
- const rows = [
- makeRow('rB', 5, {
- F_iter: {
- kind: 'iteration',
- fieldId: 'F_iter',
- iterationId: 'gone_b',
- title: 'Gone B',
- startDate: '2025-01-01',
- duration: 14
- }
- }),
- makeRow('rA', 1, {
- F_iter: {
- kind: 'iteration',
- fieldId: 'F_iter',
- iterationId: 'gone_a',
- title: 'Gone A',
- startDate: '2025-01-01',
- duration: 14
- }
- })
- ]
- const sorted = sortRows(makeTable(view, rows), rows)
- expect(sorted.map((r) => r.id)).toEqual(['rA', 'rB'])
- })
-
- it('places rows missing the sort field after rows that have it', () => {
- const view = makeView(singleSelectField, {
- direction: 'ASC',
- field: singleSelectField
- })
- const rows = [
- makeRow('rEmpty', 0, {}),
- makeRow('rHas', 1, {
- F_status: {
- kind: 'single-select',
- fieldId: 'F_status',
- optionId: 'opt_a',
- name: 'Todo',
- color: 'GRAY'
- }
- })
- ]
- const sorted = sortRows(makeTable(view, rows), rows)
- expect(sorted.map((r) => r.id)).toEqual(['rHas', 'rEmpty'])
- })
-
it('sorts an empty user list last in both directions, like a missing value', () => {
const rows = [
makeRow('empty-list', 0, {
@@ -249,55 +143,6 @@ describe('sortRows', () => {
expect(sorted.map((r) => r.id)).toEqual(['alice', 'empty-list', 'no-value'])
}
})
-
- it('sorts an empty label list last in both directions, like a missing value', () => {
- const rows = [
- makeRow('empty-list', 0, {
- F_labels: { kind: 'labels', fieldId: 'F_labels', labels: [] }
- }),
- makeRow('bug', 1, {
- F_labels: {
- kind: 'labels',
- fieldId: 'F_labels',
- labels: [{ name: 'bug', color: 'ff0000' }]
- }
- }),
- makeRow('no-value', 2, {})
- ]
-
- for (const direction of ['ASC', 'DESC'] as const) {
- const view = makeView(labelsField, { direction, field: labelsField })
- const sorted = sortRows(makeTable(view, rows), rows)
- expect(sorted.map((r) => r.id)).toEqual(['bug', 'empty-list', 'no-value'])
- }
- })
-
- it('sorts a blank text value last in both directions, like a missing value', () => {
- // Why reachable: the normalizer turns a null GitHub text/date into ''.
- const rows = [
- makeRow('blank', 0, { F_text: { kind: 'text', fieldId: 'F_text', text: '' } }),
- makeRow('alpha', 1, { F_text: { kind: 'text', fieldId: 'F_text', text: 'alpha' } }),
- makeRow('no-value', 2, {})
- ]
-
- for (const direction of ['ASC', 'DESC'] as const) {
- const view = makeView(textField, { direction, field: textField })
- const sorted = sortRows(makeTable(view, rows), rows)
- expect(sorted.map((r) => r.id)).toEqual(['alpha', 'blank', 'no-value'])
- }
- })
-
- it('keeps sort fallback finite when row positions are absent', () => {
- const view = makeView(singleSelectField)
- const rows = [
- { ...makeRow('rA', 0, {}), position: undefined as unknown as number },
- { ...makeRow('rB', 0, {}), position: undefined as unknown as number }
- ]
-
- const sorted = sortRows(makeTable(view, rows), rows)
-
- expect(sorted.map((r) => r.id)).toEqual(['rA', 'rB'])
- })
})
describe('groupRows', () => {
@@ -337,74 +182,6 @@ describe('groupRows', () => {
expect(groups.map((group) => group.label)).toEqual(['-10', '-2', '1', '2', '8', '10'])
})
-
- it('places the empty group last', () => {
- const view = {
- ...makeView(singleSelectField),
- groupByFields: [singleSelectField]
- }
- const rows = [
- makeRow('rNone', 0, {}),
- makeRow('rA', 1, {
- F_status: {
- kind: 'single-select',
- fieldId: 'F_status',
- optionId: 'opt_a',
- name: 'Todo',
- color: 'GRAY'
- }
- })
- ]
- const groups = groupRows(makeTable(view, rows), rows)
- expect(groups.map((g) => g.key)).toEqual(['opt_a', '__empty__'])
- })
-})
-
-it('indexes field ordering once for grouping and sorting a large project table', () => {
- let reads = 0
- const field: GitHubProjectField = {
- kind: 'single-select',
- id: 'field',
- name: 'Status',
- dataType: 'SINGLE_SELECT',
- options: Array.from({ length: 1000 }, (_, i) => ({
- get id() {
- reads++
- return `option-${i}`
- },
- name: String(i),
- color: 'GRAY'
- }))
- }
- const rows = Array.from({ length: 1000 }, (_, i) =>
- makeRow(String(i), i, {
- field: {
- kind: 'single-select',
- fieldId: 'field',
- optionId: `option-${(i * 173) % 1000}`,
- name: String((i * 173) % 1000),
- color: 'GRAY'
- }
- })
- )
- const view = { ...makeView(field, { field, direction: 'ASC' }), groupByFields: [field] }
- const table = makeTable(view, rows)
- const sorted = sortRows(table, rows)
- expect(reads).toBe(1000)
- expect(
- sorted.map((row) =>
- Number(
- row.fieldValuesByFieldId.field.kind === 'single-select' &&
- row.fieldValuesByFieldId.field.name
- )
- )
- ).toEqual(Array.from({ length: 1000 }, (_, i) => i))
- reads = 0
- const groups = groupRows(table, rows)
- expect(reads).toBe(1000)
- expect(groups.map((group) => group.key)).toEqual(
- Array.from({ length: 1000 }, (_, i) => `option-${i}`)
- )
})
it('uses the first iteration ordering and metadata when legacy field IDs repeat', () => {
diff --git a/src/renderer/src/components/github-project/project-row-filtering.test.ts b/src/renderer/src/components/github-project/project-row-filtering.test.ts
index 790fe544e5f..9bbdcf1f61f 100644
--- a/src/renderer/src/components/github-project/project-row-filtering.test.ts
+++ b/src/renderer/src/components/github-project/project-row-filtering.test.ts
@@ -1,10 +1,7 @@
import { describe, expect, it, vi } from 'vitest'
import type { GitHubProjectRow, GitHubProjectTable } from '../../../../shared/github/project-types'
import type { Repo } from '../../../../shared/repo-types'
-import {
- filterProjectTableRowsBySelectedRepos,
- resolveSelectedProjectRowRepo
-} from './project-row-filtering'
+import { filterProjectTableRowsBySelectedRepos } from './project-row-filtering'
function repo(id: string): Repo {
return {
@@ -125,177 +122,4 @@ describe('filterProjectTableRowsBySelectedRepos', () => {
expect(filtered.rows).toEqual([...drafts, ...restricted, issue, pullRequest])
expect(filtered.totalCount).toBe(71)
})
-
- it('keeps a row when at least one slug match is selected', () => {
- const rows = [row('visible', 'acme/orca'), row('hidden', 'acme/tool')]
- const filtered = filterProjectTableRowsBySelectedRepos(
- table(rows),
- (slug) =>
- originOnly(slug?.toLowerCase() === 'acme/orca' ? [repo('repo-1')] : [repo('repo-2')]),
- true,
- new Set(['repo-1'])
- )
-
- expect(filtered.rows.map((r) => r.id)).toEqual(['visible'])
- expect(filtered.totalCount).toBe(1)
- })
-
- it('filters a row when only unselected repos match', () => {
- const rows = [row('hidden', 'acme/orca')]
- const filtered = filterProjectTableRowsBySelectedRepos(
- table(rows),
- () => originOnly([repo('repo-2')]),
- true,
- new Set(['repo-1'])
- )
-
- expect(filtered.rows).toEqual([])
- expect(filtered.totalCount).toBe(0)
- })
-
- it('keeps a row whose only selected match is a fork of the row s repo', () => {
- const rows = [row('fork-only', 'acme/orca')]
- const filtered = filterProjectTableRowsBySelectedRepos(
- table(rows),
- () => ({ origin: [], upstream: [repo('fork')] }),
- true,
- new Set(['fork'])
- )
-
- expect(filtered.rows.map((r) => r.id)).toEqual(['fork-only'])
- expect(filtered.totalCount).toBe(1)
- })
-
- it('keeps a row with multiple selected matches for action ambiguity handling', () => {
- const rows = [row('ambiguous', 'acme/orca')]
- const filtered = filterProjectTableRowsBySelectedRepos(
- table(rows),
- () => originOnly([repo('repo-1'), repo('repo-2'), repo('repo-3')]),
- true,
- new Set(['repo-1', 'repo-2'])
- )
-
- expect(filtered.rows.map((r) => r.id)).toEqual(['ambiguous'])
- })
-})
-
-describe('resolveSelectedProjectRowRepo', () => {
- it('reports loading without reading stale slug matches', () => {
- const resolution = resolveSelectedProjectRowRepo({
- row: row('loading', 'acme/orca'),
- lookupSlugMatches: () => {
- throw new Error('should not read stale matches')
- },
- slugIndexReady: false,
- selectedRepoIds: new Set(['repo-1'])
- })
-
- expect(resolution.status).toBe('loading')
- })
-
- it('reports invalid slug for rows without a repository', () => {
- const resolution = resolveSelectedProjectRowRepo({
- row: row('missing-slug', null),
- lookupSlugMatches: () => originOnly([repo('repo-1')]),
- slugIndexReady: true,
- selectedRepoIds: new Set(['repo-1'])
- })
-
- expect(resolution.status).toBe('invalid_slug')
- })
-
- it('reports no global match when Orca has no repo for the slug', () => {
- const resolution = resolveSelectedProjectRowRepo({
- row: row('missing', 'acme/orca'),
- lookupSlugMatches: () => originOnly([]),
- slugIndexReady: true,
- selectedRepoIds: new Set(['repo-1'])
- })
-
- expect(resolution.status).toBe('no_global_match')
- })
-
- it('reports global-only matches when the repo is not selected', () => {
- const resolution = resolveSelectedProjectRowRepo({
- row: row('unselected', 'acme/orca'),
- lookupSlugMatches: () => originOnly([repo('repo-2')]),
- slugIndexReady: true,
- selectedRepoIds: new Set(['repo-1'])
- })
-
- expect(resolution.status).toBe('unselected_match')
- })
-
- it('returns the selected match when exactly one matching repo is selected', () => {
- const resolution = resolveSelectedProjectRowRepo({
- row: row('selected', 'acme/orca'),
- lookupSlugMatches: () => originOnly([repo('repo-1'), repo('repo-2')]),
- slugIndexReady: true,
- selectedRepoIds: new Set(['repo-2'])
- })
-
- expect(resolution).toMatchObject({ status: 'selected_match', repo: { id: 'repo-2' } })
- })
-
- it('passes the project host into repository matching', () => {
- const lookupSlugMatches = vi.fn(() => originOnly([repo('repo-1')]))
-
- expect(
- resolveSelectedProjectRowRepo({
- row: row('enterprise', 'acme/orca'),
- lookupSlugMatches,
- host: 'ghe.example:8443',
- slugIndexReady: true,
- selectedRepoIds: new Set(['repo-1'])
- })
- ).toMatchObject({ status: 'selected_match' })
- expect(lookupSlugMatches).toHaveBeenCalledWith('acme/orca', 'ghe.example:8443')
- })
-
- it('reports ambiguity when multiple matching repos are selected', () => {
- const resolution = resolveSelectedProjectRowRepo({
- row: row('ambiguous', 'acme/orca'),
- lookupSlugMatches: () => originOnly([repo('repo-1'), repo('repo-2')]),
- slugIndexReady: true,
- selectedRepoIds: new Set(['repo-1', 'repo-2'])
- })
-
- expect(resolution.status).toBe('ambiguous_selected_match')
- })
-
- it('falls through to a selected fork when the upstream clone is unselected', () => {
- const resolution = resolveSelectedProjectRowRepo({
- row: row('fork-selected', 'acme/orca'),
- lookupSlugMatches: () => ({ origin: [repo('upstream')], upstream: [repo('fork')] }),
- slugIndexReady: true,
- selectedRepoIds: new Set(['fork'])
- })
-
- expect(resolution).toMatchObject({ status: 'selected_match', repo: { id: 'fork' } })
- })
-
- it('prefers the selected upstream clone over a selected fork of it', () => {
- const resolution = resolveSelectedProjectRowRepo({
- row: row('both-selected', 'acme/orca'),
- lookupSlugMatches: () => ({ origin: [repo('upstream')], upstream: [repo('fork')] }),
- slugIndexReady: true,
- selectedRepoIds: new Set(['upstream', 'fork'])
- })
-
- expect(resolution).toMatchObject({ status: 'selected_match', repo: { id: 'upstream' } })
- })
-
- it('still reports no selection when neither the upstream clone nor the fork is selected', () => {
- const resolution = resolveSelectedProjectRowRepo({
- row: row('neither', 'acme/orca'),
- lookupSlugMatches: () => ({ origin: [repo('upstream')], upstream: [repo('fork')] }),
- slugIndexReady: true,
- selectedRepoIds: new Set(['other'])
- })
-
- expect(resolution).toMatchObject({
- status: 'unselected_match',
- globalMatches: [{ id: 'upstream' }, { id: 'fork' }]
- })
- })
})
diff --git a/src/renderer/src/components/integrations/task-trackers/linear-integration-card.test.tsx b/src/renderer/src/components/integrations/task-trackers/linear-integration-card.test.tsx
index 088da40ceb1..204abd49804 100644
--- a/src/renderer/src/components/integrations/task-trackers/linear-integration-card.test.tsx
+++ b/src/renderer/src/components/integrations/task-trackers/linear-integration-card.test.tsx
@@ -3,15 +3,12 @@
import { act } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
-import { getExecutionHostLabel } from '../../../../../shared/execution-host'
-import es from '@/i18n/locales/es.json'
-import { i18n, loadRendererUiLanguage, translate } from '@/i18n/i18n'
+
+import { i18n, loadRendererUiLanguage } from '@/i18n/i18n'
import { getProviderRuntimeContextKey } from '@/lib/provider-runtime-context'
import { TooltipProvider } from '../../ui/tooltip'
import { LinearIntegrationCard } from './linear-integration-card'
-const LOCAL_HOST_LABEL = getExecutionHostLabel('local')
-
type StoreState = {
linearStatus: {
connected: boolean
@@ -126,114 +123,6 @@ describe('LinearIntegrationCard account scope', () => {
Reflect.deleteProperty(window, 'api')
})
- it.each(['en', 'ko'] as const)(
- 'renders complete %s connection counts without an English suffix',
- async (language) => {
- await i18n.changeLanguage(await loadRendererUiLanguage(language))
- for (const count of [0, 1, 2]) {
- const state = installStore(true)
- state.linearStatus.workspaces = Array.from({ length: count }, (_, index) => ({
- id: `workspace-${index}`,
- organizationName: 'Fixture',
- displayName: 'Fixture workspace'
- }))
- const rendered = await renderCard()
- const description =
- rendered.querySelector('[data-settings-section="integrations-linear"]')?.textContent ?? ''
- const expected =
- language === 'ko'
- ? `워크스페이스 ${count}개 연결됨`
- : `${count} workspace${count === 1 ? '' : 's'} connected`
- expect(description).toContain(expected)
- expect(description).not.toMatch(/개(?: 사이트| 워크스페이스)?s/)
- await act(async () => {
- root?.unmount()
- })
- root = null
- container?.remove()
- container = null
- }
- }
- )
-
- it('translates a coded connection failure at the display site', async () => {
- await i18n.changeLanguage(await loadRendererUiLanguage('es'))
- const state = installStore(true)
- state.testLinearConnection = vi.fn(async () => ({
- ok: false,
- error: 'Wire detail from the host',
- errorCode: 'linear.noApiKey'
- }))
- const rendered = await renderCard()
- const label = translate(
- 'auto.components.settings.task.tracker.integration.cards.c24e56c532',
- 'Test'
- )
- const button = Array.from(rendered.querySelectorAll('button')).find(
- (button) => button.textContent === label
- )
- expect(button).toBeDefined()
- await act(async () => {
- button?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
- })
- expect(rendered.textContent).toContain(es.taskProviders.errors.linear.noApiKey)
- expect(rendered.textContent).not.toContain('Wire detail from the host')
- })
-
- it('shows local-client account ownership when Linear is disconnected', async () => {
- const state = installStore(false)
-
- const rendered = await renderCard()
-
- expect(rendered.querySelector('[data-settings-section="integrations-linear"]')).not.toBeNull()
- expect(rendered.textContent).toContain(`Account scope: ${LOCAL_HOST_LABEL}`)
- expect(rendered.textContent).toContain(
- 'Credentials and account checks for this provider are owned by this desktop client. To use a server, change Settings > Remote Orca Servers > New projects open on.'
- )
- expect(rendered.textContent).toContain('Open Remote Servers')
- expect(rendered.textContent).toContain('Add access with a Personal API key')
-
- await act(async () => {
- Array.from(rendered.querySelectorAll('button'))
- .find((button) => button.textContent === 'Re-check')
- ?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
- })
-
- expect(state.checkLinearConnection).toHaveBeenCalledWith(true)
- })
-
- it('shows remote-server account ownership and connected workspace rows', async () => {
- const state = installStore(true, { activeRuntimeEnvironmentId: 'runtime-1' })
-
- const rendered = await renderCard()
-
- expect(rendered.textContent).toContain('Account scope: Remote server: runtime-1')
- expect(rendered.textContent).toContain(
- 'Credentials and account checks for this provider are owned by this remote server. To use another host, change Settings > Remote Orca Servers > New projects open on.'
- )
- await act(async () => {
- Array.from(rendered.querySelectorAll('button'))
- .find((button) => button.textContent === 'Open Remote Servers')
- ?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
- })
- expect(state.openSettingsPage).toHaveBeenCalledTimes(1)
- expect(state.openSettingsTarget).toHaveBeenCalledWith({
- pane: 'servers',
- repoId: null,
- sectionId: 'default-runtime'
- })
- expect(rendered.textContent).toContain('Acme')
- expect(rendered.textContent).toContain('Acme workspace · linear@example.test')
-
- await act(async () => {
- Array.from(rendered.querySelectorAll('button'))
- .find((button) => button.textContent === 'Test')
- ?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
- })
-
- expect(state.testLinearConnection).toHaveBeenCalledWith('workspace-1')
- })
-
it('clears verification state after adding another Linear workspace', async () => {
installStore(true)
const rendered = await renderCard()
diff --git a/src/renderer/src/components/jira-issue-workspace-image-owner.test.tsx b/src/renderer/src/components/jira-issue-workspace-image-owner.test.tsx
index 67dca8d9d12..58376048888 100644
--- a/src/renderer/src/components/jira-issue-workspace-image-owner.test.tsx
+++ b/src/renderer/src/components/jira-issue-workspace-image-owner.test.tsx
@@ -228,19 +228,6 @@ afterEach(async () => {
})
describe('Jira issue workspace image ownership', () => {
- it('keeps explicit ownership without duplicate reads when the default host changes', async () => {
- const selected = issue()
- const owner: RuntimeClientTarget = { kind: 'environment', environmentId: 'environment-1' }
- await render(selected)
- boundary.settings = { activeRuntimeEnvironmentId: 'environment-2' }
- await render(selected)
- for (const reads of [boundary.issues, boundary.comments, boundary.transitions]) {
- expect(reads).toHaveLength(1)
- expect(reads[0]?.target).toEqual(owner)
- }
- expect(text('image-owner')).toBe(JSON.stringify(owner))
- })
-
it('fences late detail, comments and transitions when the same issue switches owners', async () => {
const selected = issue()
await render(selected)
@@ -272,20 +259,4 @@ describe('Jira issue workspace image ownership', () => {
expect(text('comments')).toBe('new')
expect(text('transitions')).toBe('new')
})
-
- it('uses one account owner for row-less detail, comments, transitions and images', async () => {
- await render(issue(), null)
- const detail = take(boundary.issues)
- const comments = take(boundary.comments)
- const transitions = take(boundary.transitions)
- expect(detail.target).toEqual({ kind: 'environment', environmentId: 'environment-1' })
- expect(comments.target).toBe(detail.target)
- expect(transitions.target).toBe(detail.target)
- expect(text('image-owner')).toBe(JSON.stringify(detail.target))
- await act(async () => {
- detail.resolve(null)
- comments.resolve([])
- transitions.resolve([])
- })
- })
})
diff --git a/src/renderer/src/components/jira-issue-workspace-request-lifetime.test.tsx b/src/renderer/src/components/jira-issue-workspace-request-lifetime.test.tsx
index 3fdb1aea828..f7f3d4d8ad1 100644
--- a/src/renderer/src/components/jira-issue-workspace-request-lifetime.test.tsx
+++ b/src/renderer/src/components/jira-issue-workspace-request-lifetime.test.tsx
@@ -392,32 +392,6 @@ describe('Jira issue workspace request lifetime', () => {
expect(text('comments-loading')).toBe('false')
})
- it('discards late detail and comments after closing the mounted workspace', async () => {
- await render(issue())
- await render(null)
- await act(async () => {
- take(boundary.issues).resolve({ ...issue(), description: 'Screenshot data'.repeat(32_768) })
- take(boundary.comments).resolve([{ ...comment('late'), body: 'Image data'.repeat(32_768) }])
- })
- expect(container.querySelector('[data-open]')?.getAttribute('data-open')).toBe('false')
- expect(container.querySelector('[data-testid="detail"]')).toBeNull()
- expect(boundary.composer).toBeNull()
- })
-
- it('clears an already hydrated issue and comments when it closes', async () => {
- await render(issue())
- await act(async () => {
- take(boundary.issues).resolve(issue('JIR-1', 'Hydrated'))
- take(boundary.comments).resolve([comment('loaded')])
- })
- expect(text('title')).toBe('Hydrated')
- expect(text('comments')).toBe('loaded')
- expect(text('issue-loading')).toBe('false')
- expect(text('comments-loading')).toBe('false')
- await render(null)
- expect(container.querySelector('[data-testid="detail"]')).toBeNull()
- })
-
it('ignores old replies and accepts the reopened issue replies', async () => {
await render(issue())
const oldIssue = take(boundary.issues)
diff --git a/src/renderer/src/components/linear-issue-attribute-filter-dropdowns.test.tsx b/src/renderer/src/components/linear-issue-attribute-filter-dropdowns.test.tsx
index ad2cfb30571..768062ad3ca 100644
--- a/src/renderer/src/components/linear-issue-attribute-filter-dropdowns.test.tsx
+++ b/src/renderer/src/components/linear-issue-attribute-filter-dropdowns.test.tsx
@@ -4,13 +4,8 @@ import { act } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { LinearTeam } from '../../../shared/linear/workspace-types'
+
import {
- clearLinearIssueAttributeFacet,
- countLinearIssueAttributeFilters,
- linearIssueAttributeFilterPillLabels
-} from './linear-issue-attribute-filter-pills'
-import {
- LINEAR_ISSUE_ATTRIBUTE_FILTER_MAX_LABEL_IDS,
LINEAR_ISSUE_ATTRIBUTE_FILTER_MAX_STATE_IDS,
type LinearIssueAttributeFilter
} from '../../../shared/linear/issue-attribute-filter'
@@ -61,208 +56,6 @@ afterEach(() => {
metadataMocks.useTeamsMembers.mockReset()
})
-const sample: LinearIssueAttributeFilter = {
- stateIds: ['s1', 's2'],
- priorities: [0, 1],
- assignee: { kind: 'unassigned' },
- labelIds: ['l1']
-}
-
-describe('linear-issue-attribute-filter helpers', () => {
- it('counts active facets and clears individual facets', () => {
- expect(countLinearIssueAttributeFilters(sample)).toBe(4)
- expect(clearLinearIssueAttributeFacet(sample, 'status').stateIds).toEqual([])
- expect(clearLinearIssueAttributeFacet(sample, 'priority').priorities).toEqual([])
- expect(clearLinearIssueAttributeFacet(sample, 'assignee').assignee).toBeNull()
- expect(clearLinearIssueAttributeFacet(sample, 'labels').labelIds).toEqual([])
- })
-
- it('collapses same-named status ids into one pill label', () => {
- const pills = linearIssueAttributeFilterPillLabels({
- value: { stateIds: ['be-todo', 'fe-todo'], priorities: [], assignee: null, labelIds: [] },
- stateNamesById: new Map([
- ['be-todo', 'Todo'],
- ['fe-todo', 'Todo']
- ]),
- memberNamesById: new Map(),
- labelNamesById: new Map(),
- statusOptions: [{ key: 'be-todo', primary: 'Todo', ids: ['be-todo', 'fe-todo'] }],
- labelOptions: [],
- statusTruncated: false,
- labelsTruncated: false
- })
- expect(pills[0]?.value).toBe('Todo')
- })
-
- it('builds pill labels from metadata maps', () => {
- const pills = linearIssueAttributeFilterPillLabels({
- value: sample,
- stateNamesById: new Map([
- ['s1', 'Todo'],
- ['s2', 'In Progress']
- ]),
- memberNamesById: new Map(),
- labelNamesById: new Map([['l1', 'Bug']]),
- statusOptions: [],
- labelOptions: [],
- statusTruncated: false,
- labelsTruncated: false
- })
- expect(pills.map((p) => p.key)).toEqual(['status', 'priority', 'assignee', 'labels'])
- expect(pills[0]?.value).toContain('Todo')
- expect(pills[2]?.value).toMatch(/Unassigned/i)
- expect(pills[3]?.value).toBe('Bug')
- })
-})
-
-describe('LinearIssueAttributeFilterDropdowns', () => {
- it('keeps metadata lazy while closed with only static filter labels', () => {
- const team: LinearTeam = { id: 'team-1', name: 'Engineering', key: 'ENG' }
- const container = document.createElement('div')
- document.body.appendChild(container)
- const root = createRoot(container)
- roots.push(root)
-
- act(() => {
- root.render(
- undefined}
- workspaceId="workspace-1"
- primaryTeam={team}
- selectedTeamIds={[]}
- availableTeams={[team]}
- teamsSettled
- />
- )
- })
-
- expect(metadataMocks.useTeamsStates).toHaveBeenCalledWith([], undefined, null)
- expect(metadataMocks.useTeamsLabels).toHaveBeenCalledWith([], undefined, null)
- expect(metadataMocks.useTeamsMembers).toHaveBeenCalledWith([], undefined, null)
- })
-
- it('keeps readable metadata names available after the popover closes', () => {
- const value: LinearIssueAttributeFilter = {
- stateIds: ['state-1'],
- priorities: [],
- assignee: { kind: 'user', id: 'member-1' },
- labelIds: ['label-1']
- }
- const team: LinearTeam = { id: 'team-1', name: 'Engineering', key: 'ENG' }
- const container = document.createElement('div')
- document.body.appendChild(container)
- const root = createRoot(container)
- roots.push(root)
-
- act(() => {
- root.render(
- undefined}
- workspaceId="workspace-1"
- primaryTeam={team}
- selectedTeamIds={[]}
- availableTeams={[team]}
- teamsSettled
- />
- )
- })
-
- expect(container.textContent).toContain('Todo')
- expect(container.textContent).toContain('Ada Lovelace')
- expect(container.textContent).toContain('Bug')
- expect(container.textContent).not.toContain('state-1')
- expect(metadataMocks.useTeamsStates).toHaveBeenCalledWith(['team-1'], undefined, 'workspace-1')
- expect(metadataMocks.useTeamsLabels).toHaveBeenCalledWith(['team-1'], undefined, 'workspace-1')
- expect(metadataMocks.useTeamsMembers).toHaveBeenCalledWith(['team-1'], undefined, 'workspace-1')
- })
-
- it.each([['all'], [null]])(
- 'offers the workspace picker hint instead of filter sections for workspaceId %s',
- (workspaceId) => {
- const team: LinearTeam = { id: 'team-1', name: 'Engineering', key: 'ENG' }
- const container = document.createElement('div')
- document.body.appendChild(container)
- const root = createRoot(container)
- roots.push(root)
-
- act(() => {
- root.render(
- undefined}
- workspaceId={workspaceId}
- primaryTeam={team}
- selectedTeamIds={[]}
- availableTeams={[team]}
- teamsSettled
- />
- )
- })
-
- const trigger = container.querySelector('button')
- act(() => {
- trigger?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
- })
-
- expect(document.body.textContent).toContain('Select one workspace')
- expect(metadataMocks.useTeamsStates).toHaveBeenCalledWith([], undefined, null)
- }
- )
-
- // Wait for the full team fetch; pruning against issue-scraped teams would permanently discard restored facets.
- it('prunes unknown facet ids only once teams settle on the mounted component', () => {
- const team: LinearTeam = { id: 'team-1', name: 'Engineering', key: 'ENG' }
- const onChange = vi.fn()
- const container = document.createElement('div')
- document.body.appendChild(container)
- const root = createRoot(container)
- roots.push(root)
-
- const renderWith = (teamsSettled: boolean): void => {
- act(() => {
- root.render(
-
- )
- })
- }
-
- renderWith(false)
- expect(onChange).not.toHaveBeenCalled()
-
- renderWith(true)
- expect(onChange).toHaveBeenCalledWith(
- expect.objectContaining({ stateIds: [], labelIds: [], assignee: null })
- )
- })
-})
-
-const multiTeamStates = [
- { id: 'be-backlog', name: 'Backlog', type: 'backlog' },
- { id: 'be-todo', name: 'Todo', type: 'unstarted' },
- { id: 'fe-backlog', name: 'Backlog', type: 'backlog' },
- { id: 'fe-todo', name: 'Todo', type: 'unstarted' }
-]
-
const teamBe: LinearTeam = { id: 'team-be', name: 'Backend', key: 'BE' }
const teamFe: LinearTeam = { id: 'team-fe', name: 'Frontend', key: 'FE' }
@@ -326,159 +119,7 @@ function pickerRowsNamed(name: string): HTMLElement[] {
)
}
-describe('LinearIssueAttributeFilterDropdowns status options across teams', () => {
- it('lists one row per status name instead of one per team state id', () => {
- metadataMocks.useTeamsStates.mockImplementation(() => ({
- data: multiTeamStates,
- loading: false,
- error: null
- }))
-
- renderDropdowns(emptyFilter)
- openSectionNamed('Status')
-
- expect(pickerRowsNamed('Todo')).toHaveLength(1)
- expect(pickerRowsNamed('Backlog')).toHaveLength(1)
- })
-
- it('selects every team state id behind the picked status name', () => {
- metadataMocks.useTeamsStates.mockImplementation(() => ({
- data: multiTeamStates,
- loading: false,
- error: null
- }))
- const { onChange } = renderDropdowns(emptyFilter)
- openSectionNamed('Status')
- act(() => {
- pickerRowsNamed('Todo')[0]?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
- })
-
- expect(onChange).toHaveBeenCalledWith(
- expect.objectContaining({ stateIds: ['be-todo', 'fe-todo'] })
- )
- })
-
- it('keeps the expanded status selection within the transport id cap', () => {
- const overCap = LINEAR_ISSUE_ATTRIBUTE_FILTER_MAX_STATE_IDS + 20
- metadataMocks.useTeamsStates.mockImplementation(() => ({
- data: Array.from({ length: overCap }, (_unused, index) => ({
- id: `team-${index}-todo`,
- name: 'Todo',
- type: 'unstarted'
- })),
- loading: false,
- error: null
- }))
- const { onChange } = renderDropdowns(emptyFilter)
- openSectionNamed('Status')
- act(() => {
- pickerRowsNamed('Todo')[0]?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
- })
-
- expect(onChange.mock.calls[0]?.[0].stateIds).toHaveLength(
- LINEAR_ISSUE_ATTRIBUTE_FILTER_MAX_STATE_IDS
- )
- })
-})
-
-function sameNamedMetadata(name: string, count: number): { id: string; name: string }[] {
- return Array.from({ length: count }, (_unused, index) => ({ id: `team-${index}-${name}`, name }))
-}
-
describe('LinearIssueAttributeFilterDropdowns transport-cap coverage notice', () => {
- it('reports the team statuses left out once a picked status exceeds the id cap', () => {
- const overCap = LINEAR_ISSUE_ATTRIBUTE_FILTER_MAX_STATE_IDS + 20
- metadataMocks.useTeamsStates.mockImplementation(() => ({
- data: sameNamedMetadata('todo', overCap),
- loading: false,
- error: null
- }))
-
- const { rerender, onChange } = renderDropdowns(emptyFilter)
- openSectionNamed('Status')
- act(() => {
- pickerRowsNamed('todo')[0]?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
- })
-
- const bounded = onChange.mock.calls[0]?.[0] as LinearIssueAttributeFilter
- expect(bounded.stateIds).toHaveLength(LINEAR_ISSUE_ATTRIBUTE_FILTER_MAX_STATE_IDS)
- rerender(bounded)
-
- expect(pickerRowsNamed('todo')).toHaveLength(1)
- expect(document.body.textContent).toContain(
- `Filtering ${LINEAR_ISSUE_ATTRIBUTE_FILTER_MAX_STATE_IDS} of ${overCap} team statuses`
- )
- })
-
- it('reports the shrunken coverage of a picked status when a second status is added', () => {
- const perStatus = LINEAR_ISSUE_ATTRIBUTE_FILTER_MAX_STATE_IDS + 20
- metadataMocks.useTeamsStates.mockImplementation(() => ({
- data: [...sameNamedMetadata('todo', perStatus), ...sameNamedMetadata('doing', perStatus)],
- loading: false,
- error: null
- }))
-
- const { rerender, onChange } = renderDropdowns(emptyFilter)
- openSectionNamed('Status')
- act(() => {
- pickerRowsNamed('todo')[0]?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
- })
- rerender(onChange.mock.calls[0]?.[0] as LinearIssueAttributeFilter)
- act(() => {
- pickerRowsNamed('doing')[0]?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
- })
- rerender(onChange.mock.calls[1]?.[0] as LinearIssueAttributeFilter)
-
- expect(document.body.textContent).toContain(
- `Filtering ${LINEAR_ISSUE_ATTRIBUTE_FILTER_MAX_STATE_IDS} of ${perStatus * 2} team statuses`
- )
- })
-
- it('stays silent while every team id behind the picked status is applied', () => {
- metadataMocks.useTeamsStates.mockImplementation(() => ({
- data: multiTeamStates,
- loading: false,
- error: null
- }))
-
- const { rerender, onChange } = renderDropdowns(emptyFilter)
- openSectionNamed('Status')
- act(() => {
- pickerRowsNamed('Todo')[0]?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
- })
- rerender(onChange.mock.calls[0]?.[0] as LinearIssueAttributeFilter)
-
- expect(document.body.textContent).not.toContain('team statuses')
- openSectionNamed('Back')
- expect(document.body.textContent).not.toContain('partial')
- })
-
- it('flags the trimmed status filter in the section menu and the pill', () => {
- const overCap = LINEAR_ISSUE_ATTRIBUTE_FILTER_MAX_STATE_IDS + 20
- metadataMocks.useTeamsStates.mockImplementation(() => ({
- data: sameNamedMetadata('todo', overCap),
- loading: false,
- error: null
- }))
-
- const { rerender, onChange } = renderDropdowns(emptyFilter)
- openSectionNamed('Status')
- act(() => {
- pickerRowsNamed('todo')[0]?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
- })
- rerender(onChange.mock.calls[0]?.[0] as LinearIssueAttributeFilter)
- openSectionNamed('Back')
-
- expect(document.body.textContent).toContain('1 selected')
- expect(document.body.textContent).toContain('· partial')
- // Use a keyboard-reachable, named marker; a title alone cannot provide accessible coverage feedback.
- const pillMarkers = [...document.body.querySelectorAll('button')].filter(
- (button) => button.textContent === 'partial'
- )
- expect(pillMarkers).toHaveLength(1)
- expect(pillMarkers[0]?.getAttribute('data-slot')).toBe('tooltip-trigger')
- })
-
it('keeps and counts a picked status whose ids all sort past the cap', () => {
const cap = LINEAR_ISSUE_ATTRIBUTE_FILTER_MAX_STATE_IDS
metadataMocks.useTeamsStates.mockImplementation(() => ({
@@ -509,64 +150,6 @@ describe('LinearIssueAttributeFilterDropdowns transport-cap coverage notice', ()
expect(bounded.stateIds).toContain('z-000')
expect(document.body.textContent).toContain(`Filtering ${cap} of ${cap + 1} team statuses`)
})
-
- it('says the status filter is full instead of claiming coverage it cannot have', () => {
- const cap = LINEAR_ISSUE_ATTRIBUTE_FILTER_MAX_STATE_IDS
- const states = Array.from({ length: cap + 1 }, (_unused, index) => ({
- id: `s-${String(index).padStart(3, '0')}`,
- name: `Status ${String(index).padStart(3, '0')}`
- }))
- metadataMocks.useTeamsStates.mockImplementation(() => ({
- data: states,
- loading: false,
- error: null
- }))
-
- const { rerender, onChange } = renderDropdowns({
- ...emptyFilter,
- stateIds: states.slice(0, cap).map((state) => state.id)
- })
- openSectionNamed('Status')
- act(() => {
- pickerRowsNamed(`Status ${String(cap).padStart(3, '0')}`)[0]?.dispatchEvent(
- new MouseEvent('click', { bubbles: true })
- )
- })
-
- const bounded = onChange.mock.calls[0]?.[0] as LinearIssueAttributeFilter
- expect(bounded.stateIds).toHaveLength(cap)
- rerender(bounded)
-
- expect(document.body.textContent).toContain(
- `Filtering the most this can carry: ${cap} team statuses`
- )
- openSectionNamed('Back')
- expect(document.body.textContent).toContain(`${cap} selected`)
- expect(document.body.textContent).toContain('· partial')
- })
-
- it('reports the team labels left out once a picked label exceeds the id cap', () => {
- const overCap = LINEAR_ISSUE_ATTRIBUTE_FILTER_MAX_LABEL_IDS + 20
- metadataMocks.useTeamsLabels.mockImplementation(() => ({
- data: sameNamedMetadata('bug', overCap),
- loading: false,
- error: null
- }))
-
- const { rerender, onChange } = renderDropdowns(emptyFilter)
- openSectionNamed('Labels')
- act(() => {
- pickerRowsNamed('bug')[0]?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
- })
-
- const bounded = onChange.mock.calls[0]?.[0] as LinearIssueAttributeFilter
- expect(bounded.labelIds).toHaveLength(LINEAR_ISSUE_ATTRIBUTE_FILTER_MAX_LABEL_IDS)
- rerender(bounded)
-
- expect(document.body.textContent).toContain(
- `Filtering ${LINEAR_ISSUE_ATTRIBUTE_FILTER_MAX_LABEL_IDS} of ${overCap} team labels`
- )
- })
})
// Exactly the cap is complete here; bounded ids alone cannot prove that truncation happened.
@@ -678,39 +261,6 @@ describe('LinearIssueAttributeFilterDropdowns coverage at exactly the transport
expect(document.body.textContent).toContain('Filtering the most this can carry')
})
- it('keeps the labels truncation when an unrelated facet is picked', () => {
- const labelCap = LINEAR_ISSUE_ATTRIBUTE_FILTER_MAX_LABEL_IDS
- const labels = Array.from({ length: labelCap + 1 }, (_unused, index) => ({
- id: `l-${String(index).padStart(3, '0')}`,
- name: rowNamed(index)
- }))
- metadataMocks.useTeamsLabels.mockImplementation(() => ({
- data: labels,
- loading: false,
- error: null
- }))
-
- const { rerender, onChange } = renderDropdowns({
- ...emptyFilter,
- labelIds: labels.slice(0, labelCap).map((label) => label.id)
- })
- openSectionNamed('Labels')
- clickRow(rowNamed(labelCap))
- rerender(onChange.mock.calls[0]?.[0] as LinearIssueAttributeFilter)
- expect(document.body.textContent).toContain('Filtering the most this can carry')
-
- openSectionNamed('Back')
- openSectionNamed('Priority')
- clickRow('Urgent')
- const withPriority = onChange.mock.calls[1]?.[0] as LinearIssueAttributeFilter
- rerender(withPriority)
-
- expect(withPriority.labelIds).toHaveLength(labelCap)
- openSectionNamed('Back')
- openSectionNamed('Labels')
- expect(document.body.textContent).toContain('Filtering the most this can carry')
- })
-
it('drops the recorded truncation when the facet is cleared', () => {
const states = Array.from({ length: cap + 1 }, (_unused, index) => ({
id: `s-${String(index).padStart(3, '0')}`,
diff --git a/src/renderer/src/components/native-chat-resume-bulk-progress.test.tsx b/src/renderer/src/components/native-chat-resume-bulk-progress.test.tsx
index 0afd02fb518..87f590750ae 100644
--- a/src/renderer/src/components/native-chat-resume-bulk-progress.test.tsx
+++ b/src/renderer/src/components/native-chat-resume-bulk-progress.test.tsx
@@ -9,7 +9,7 @@ import {
import { act } from 'react'
import { expect, it, vi, type Mock } from 'vitest'
import { NativeChatResumeOnRestartModal } from './NativeChatResumeRuntimeModal'
-import { chatBox } from './native-chat-resume-on-restart-modal.test-support'
+
import { NativeChatResumeStatusSegment } from './status-bar/NativeChatResumeStatusSegment'
import type { ResumeCandidate, ResumeFailure } from './native-chat-resume-on-restart-grouping'
import type { AgentSessionStatusSummary } from '../../../shared/agent-session/agent-session-wire'
@@ -66,29 +66,6 @@ function emitPhase(
})
}
-it('resumes a 21-chat selection with one action and no redundant listing', async () => {
- const candidates = Array.from({ length: 21 }, (_, index) => ({
- ...offered[0]!,
- sessionId: `chat-${index}`
- }))
- rpc.mockImplementation(async (_target, method) =>
- method === 'agentSession.restartResumable'
- ? { sessions: candidates, failed: [] }
- : {
- sessions: [],
- failed: [],
- continued: candidates.map(({ sessionId }) => ({ sessionId, outcome: 'continued' }))
- }
- )
- await refreshNativeChatRestartOffer()
- await continueNativeChatRestartOffer(candidates.map(({ sessionId }) => sessionId))
- expect(rpc.mock.calls.map((call) => [call[1], call[2]])).toEqual([
- ['agentSession.restartResumable', undefined],
- ['agentSession.restartContinue', { sessionIds: candidates.map(({ sessionId }) => sessionId) }]
- ])
- expect(toasts()).toEqual([['Resumed 21 chats']])
-})
-
it('uses the host unconfirmed result after a lost bulk reply without reporting a failure', async () => {
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
let reads = 0
@@ -119,30 +96,6 @@ it('uses the host unconfirmed result after a lost bulk reply without reporting a
}
})
-it('publishes a refusal reply without needing a second host read', async () => {
- let reads = 0
- rpc.mockImplementation(async (_target, method) => {
- if (method === 'agentSession.restartResumable') {
- if (reads++ > 0) {
- throw new Error('listing unavailable')
- }
- return { sessions: offered, failed: [] }
- }
- return {
- sessions: [offered[1]],
- failed: [failure('a')],
- continued: [{ sessionId: 'a', outcome: 'refused' }]
- }
- })
- await mount()
- await act(async () => chatBox('b').click())
- await act(async () => button('Resume 1 chat').click())
- expect(reads).toBe(1)
- expect(offerIds()).toEqual(['b'])
- expect(getNativeChatRestartOffer().failed.map((row) => row.sessionId)).toEqual(['a'])
- expect(toasts()).toEqual([['1 chat couldn’t be resumed']])
-})
-
it('keeps the run in flight after every feed verdict until the fallback list is published', async () => {
const answer = Promise.withResolvers<{
continued: { sessionId: string; outcome: 'continued' }[]