196 lines
5.3 KiB
TypeScript
196 lines
5.3 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import {
|
|
getKanbanColumnFromReviewState,
|
|
getReviewStateFromTask,
|
|
isNeedsFixTask,
|
|
normalizeReviewState,
|
|
} from '../../../src/shared/utils/reviewState';
|
|
|
|
describe('reviewState utils', () => {
|
|
it('normalizes needsFix as a first-class review state', () => {
|
|
expect(normalizeReviewState('needsFix')).toBe('needsFix');
|
|
expect(getReviewStateFromTask({ reviewState: 'needsFix' })).toBe('needsFix');
|
|
expect(isNeedsFixTask({ reviewState: 'needsFix' })).toBe(true);
|
|
});
|
|
|
|
it('does not map needsFix to a kanban column', () => {
|
|
expect(getKanbanColumnFromReviewState('needsFix')).toBeUndefined();
|
|
});
|
|
|
|
it('derives review state from review_started history event', () => {
|
|
expect(
|
|
getReviewStateFromTask({
|
|
historyEvents: [
|
|
{
|
|
id: '1',
|
|
timestamp: '2026-01-01T00:00:00Z',
|
|
type: 'review_started',
|
|
from: 'none',
|
|
to: 'review',
|
|
actor: 'alice',
|
|
},
|
|
],
|
|
})
|
|
).toBe('review');
|
|
});
|
|
|
|
it('resets derived review state after work resumes following requested changes', () => {
|
|
expect(
|
|
getReviewStateFromTask({
|
|
historyEvents: [
|
|
{
|
|
id: '1',
|
|
timestamp: '2026-01-01T00:00:00Z',
|
|
type: 'review_changes_requested',
|
|
from: 'review',
|
|
to: 'needsFix',
|
|
actor: 'reviewer',
|
|
},
|
|
{
|
|
id: '2',
|
|
timestamp: '2026-01-01T00:01:00Z',
|
|
type: 'status_changed',
|
|
from: 'pending',
|
|
to: 'in_progress',
|
|
actor: 'owner',
|
|
},
|
|
],
|
|
})
|
|
).toBe('none');
|
|
});
|
|
|
|
it('keeps needsFix across the pending status written by review_request_changes', () => {
|
|
expect(
|
|
getReviewStateFromTask({
|
|
historyEvents: [
|
|
{
|
|
id: '1',
|
|
timestamp: '2026-01-01T00:00:00Z',
|
|
type: 'review_changes_requested',
|
|
from: 'review',
|
|
to: 'needsFix',
|
|
actor: 'alice',
|
|
},
|
|
{
|
|
id: '2',
|
|
timestamp: '2026-01-01T00:01:00Z',
|
|
type: 'status_changed',
|
|
from: 'completed',
|
|
to: 'pending',
|
|
actor: 'alice',
|
|
},
|
|
],
|
|
})
|
|
).toBe('needsFix');
|
|
});
|
|
|
|
it('clears approved state when a task is explicitly reopened to pending', () => {
|
|
expect(
|
|
getReviewStateFromTask({
|
|
reviewState: 'approved',
|
|
historyEvents: [
|
|
{
|
|
id: '1',
|
|
timestamp: '2026-01-01T00:00:00Z',
|
|
type: 'review_approved',
|
|
from: 'review',
|
|
to: 'approved',
|
|
actor: 'alice',
|
|
},
|
|
{
|
|
id: '2',
|
|
timestamp: '2026-01-01T00:01:00Z',
|
|
type: 'status_changed',
|
|
from: 'completed',
|
|
to: 'pending',
|
|
actor: 'alice',
|
|
},
|
|
],
|
|
})
|
|
).toBe('none');
|
|
});
|
|
|
|
it('lets canonical pending status clear stale review history when no reopen event exists', () => {
|
|
expect(
|
|
getReviewStateFromTask({
|
|
status: 'pending',
|
|
historyEvents: [
|
|
{
|
|
id: '1',
|
|
timestamp: '2026-01-01T00:00:00Z',
|
|
type: 'review_approved',
|
|
from: 'review',
|
|
to: 'approved',
|
|
actor: 'alice',
|
|
},
|
|
],
|
|
})
|
|
).toBe('none');
|
|
expect(
|
|
getReviewStateFromTask({
|
|
status: 'pending',
|
|
historyEvents: [
|
|
{
|
|
id: '1',
|
|
timestamp: '2026-01-01T00:00:00Z',
|
|
type: 'review_requested',
|
|
from: 'none',
|
|
to: 'review',
|
|
reviewer: 'bob',
|
|
},
|
|
],
|
|
})
|
|
).toBe('none');
|
|
});
|
|
|
|
it('falls back to persisted legacy reviewState when history has no review signal', () => {
|
|
expect(
|
|
getReviewStateFromTask({
|
|
reviewState: 'approved',
|
|
status: 'completed',
|
|
historyEvents: [
|
|
{
|
|
id: '1',
|
|
timestamp: '2026-01-01T00:00:00Z',
|
|
type: 'task_created',
|
|
status: 'completed',
|
|
},
|
|
],
|
|
})
|
|
).toBe('approved');
|
|
});
|
|
|
|
it('ignores stale terminal review fallback on active or deleted statuses', () => {
|
|
expect(getReviewStateFromTask({ reviewState: 'approved', status: 'pending' })).toBe('none');
|
|
expect(getReviewStateFromTask({ reviewState: 'review', status: 'in_progress' })).toBe('none');
|
|
expect(getReviewStateFromTask({ kanbanColumn: 'approved', status: 'deleted' })).toBe('none');
|
|
});
|
|
|
|
it('keeps legacy pending needsFix as actionable fallback', () => {
|
|
expect(getReviewStateFromTask({ reviewState: 'needsFix', status: 'pending' })).toBe(
|
|
'needsFix'
|
|
);
|
|
});
|
|
|
|
it('keeps completed needsFix as a non-final review correction state', () => {
|
|
expect(getReviewStateFromTask({ reviewState: 'needsFix', status: 'completed' })).toBe(
|
|
'needsFix'
|
|
);
|
|
expect(
|
|
getReviewStateFromTask({
|
|
status: 'completed',
|
|
historyEvents: [
|
|
{
|
|
id: '1',
|
|
timestamp: '2026-01-01T00:00:00Z',
|
|
type: 'review_changes_requested',
|
|
from: 'review',
|
|
to: 'needsFix',
|
|
actor: 'reviewer',
|
|
},
|
|
],
|
|
})
|
|
).toBe('needsFix');
|
|
});
|
|
});
|