Skip to content

Commit 00541fb

Browse files
committed
test: strengthen TUI component test assertions and fix flaky patterns
1 parent 5c071ae commit 00541fb

6 files changed

Lines changed: 398 additions & 154 deletions

File tree

src/cli/tui/components/__tests__/ConfirmReview.test.tsx

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ import { describe, expect, it } from 'vitest';
66
describe('ConfirmReview', () => {
77
it('renders default title and help text', () => {
88
const { lastFrame } = render(<ConfirmReview fields={[{ label: 'Name', value: 'my-agent' }]} />);
9+
const frame = lastFrame()!;
910

10-
expect(lastFrame()).toContain('Review Configuration');
11-
expect(lastFrame()).toContain('Enter confirm');
12-
expect(lastFrame()).toContain('Esc back');
11+
expect(frame).toContain('Review Configuration');
12+
expect(frame).toContain('Enter confirm');
13+
expect(frame).toContain('Esc back');
1314
});
1415

1516
it('renders custom title', () => {
@@ -18,9 +19,10 @@ describe('ConfirmReview', () => {
1819
);
1920

2021
expect(lastFrame()).toContain('Review Deploy');
22+
expect(lastFrame()).not.toContain('Review Configuration');
2123
});
2224

23-
it('renders all fields', () => {
25+
it('renders each field as label: value on the same line', () => {
2426
const { lastFrame } = render(
2527
<ConfirmReview
2628
fields={[
@@ -30,21 +32,58 @@ describe('ConfirmReview', () => {
3032
]}
3133
/>
3234
);
35+
const lines = lastFrame()!.split('\n');
3336

34-
expect(lastFrame()).toContain('Name');
35-
expect(lastFrame()).toContain('my-agent');
36-
expect(lastFrame()).toContain('SDK');
37-
expect(lastFrame()).toContain('Strands');
38-
expect(lastFrame()).toContain('Language');
39-
expect(lastFrame()).toContain('Python');
37+
// Each label and its value should appear on the same line
38+
const nameLine = lines.find(l => l.includes('Name'))!;
39+
expect(nameLine).toContain('my-agent');
40+
41+
const sdkLine = lines.find(l => l.includes('SDK'))!;
42+
expect(sdkLine).toContain('Strands');
43+
44+
const langLine = lines.find(l => l.includes('Language'))!;
45+
expect(langLine).toContain('Python');
46+
});
47+
48+
it('renders label with colon separator', () => {
49+
const { lastFrame } = render(<ConfirmReview fields={[{ label: 'Region', value: 'us-east-1' }]} />);
50+
const lines = lastFrame()!.split('\n');
51+
52+
const regionLine = lines.find(l => l.includes('Region'))!;
53+
expect(regionLine).toMatch(/Region.*:.*us-east-1/);
4054
});
4155

42-
it('renders custom help text', () => {
56+
it('renders custom help text replacing default', () => {
4357
const { lastFrame } = render(
4458
<ConfirmReview fields={[{ label: 'Name', value: 'test' }]} helpText="Press Y to confirm" />
4559
);
4660

4761
expect(lastFrame()).toContain('Press Y to confirm');
4862
expect(lastFrame()).not.toContain('Enter confirm');
4963
});
64+
65+
it('renders multiple fields in order', () => {
66+
const { lastFrame } = render(
67+
<ConfirmReview
68+
fields={[
69+
{ label: 'First', value: 'A' },
70+
{ label: 'Second', value: 'B' },
71+
{ label: 'Third', value: 'C' },
72+
]}
73+
/>
74+
);
75+
const frame = lastFrame()!;
76+
77+
// All three labels should be present
78+
expect(frame).toContain('First');
79+
expect(frame).toContain('Second');
80+
expect(frame).toContain('Third');
81+
82+
// Verify ordering: First appears before Second
83+
const firstIdx = frame.indexOf('First');
84+
const secondIdx = frame.indexOf('Second');
85+
const thirdIdx = frame.indexOf('Third');
86+
expect(firstIdx).toBeLessThan(secondIdx);
87+
expect(secondIdx).toBeLessThan(thirdIdx);
88+
});
5089
});

src/cli/tui/components/__tests__/DeployStatus.test.tsx

Lines changed: 141 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,66 +9,168 @@ function makeMsg(
99
code = 'CDK_TOOLKIT_I5502',
1010
progress?: { completed: number; total: number }
1111
): DeployMessage {
12-
return { message, code, level: 'info', time: new Date(), timestamp: new Date(), progress } as DeployMessage;
12+
return {
13+
message,
14+
code,
15+
level: 'info',
16+
time: new Date(),
17+
timestamp: new Date(),
18+
progress,
19+
} as DeployMessage;
20+
}
21+
22+
function makeResourceMsg(resourceType: string, status: string): DeployMessage {
23+
return makeMsg(`MyStack | ${status} | AWS::${resourceType} | LogicalId`);
1324
}
1425

1526
describe('DeployStatus', () => {
16-
it('renders deploying state with gradient text', () => {
17-
const { lastFrame } = render(<DeployStatus messages={[]} isComplete={false} hasError={false} />);
27+
describe('header state', () => {
28+
it('shows "Deploying to AWS" when not complete', () => {
29+
const { lastFrame } = render(<DeployStatus messages={[]} isComplete={false} hasError={false} />);
1830

19-
expect(lastFrame()).toContain('Deploying to AWS');
20-
});
31+
expect(lastFrame()).toContain('Deploying to AWS');
32+
});
2133

22-
it('renders success state when complete', () => {
23-
const { lastFrame } = render(<DeployStatus messages={[]} isComplete={true} hasError={false} />);
34+
it('shows success message when complete without error', () => {
35+
const { lastFrame } = render(<DeployStatus messages={[]} isComplete={true} hasError={false} />);
36+
const frame = lastFrame()!;
2437

25-
expect(lastFrame()).toContain('Deploy to AWS Complete');
26-
});
38+
expect(frame).toContain('✓');
39+
expect(frame).toContain('Deploy to AWS Complete');
40+
});
2741

28-
it('renders failure state when complete with error', () => {
29-
const { lastFrame } = render(<DeployStatus messages={[]} isComplete={true} hasError={true} />);
42+
it('shows failure message when complete with error', () => {
43+
const { lastFrame } = render(<DeployStatus messages={[]} isComplete={true} hasError={true} />);
44+
const frame = lastFrame()!;
3045

31-
expect(lastFrame()).toContain('Deploy to AWS Failed');
46+
expect(frame).toContain('✗');
47+
expect(frame).toContain('Deploy to AWS Failed');
48+
});
3249
});
3350

34-
it('renders resource events during deployment', () => {
35-
const messages = [
36-
makeMsg('MyStack | CREATE_IN_PROGRESS | AWS::Lambda::Function | MyFunc'),
37-
makeMsg('MyStack | CREATE_COMPLETE | AWS::Lambda::Function | MyFunc'),
38-
];
51+
describe('resource event parsing', () => {
52+
it('displays parsed resource type and status from CDK event messages', () => {
53+
const messages = [
54+
makeResourceMsg('Lambda::Function', 'CREATE_IN_PROGRESS'),
55+
makeResourceMsg('Lambda::Function', 'CREATE_COMPLETE'),
56+
];
3957

40-
const { lastFrame } = render(<DeployStatus messages={messages} isComplete={false} hasError={false} />);
58+
const { lastFrame } = render(<DeployStatus messages={messages} isComplete={false} hasError={false} />);
59+
const frame = lastFrame()!;
4160

42-
expect(lastFrame()).toContain('Lambda::Function');
43-
expect(lastFrame()).toContain('CREATE_COMPLETE');
44-
});
61+
expect(frame).toContain('Lambda::Function');
62+
expect(frame).toContain('CREATE_COMPLETE');
63+
});
4564

46-
it('renders progress bar when progress data exists', () => {
47-
const messages = [makeMsg('deploying', 'CDK_TOOLKIT_I5502', { completed: 3, total: 10 })];
65+
it('strips AWS:: prefix from resource types', () => {
66+
const messages = [makeResourceMsg('S3::Bucket', 'CREATE_COMPLETE')];
4867

49-
const { lastFrame } = render(<DeployStatus messages={messages} isComplete={false} hasError={false} />);
68+
const { lastFrame } = render(<DeployStatus messages={messages} isComplete={false} hasError={false} />);
5069

51-
expect(lastFrame()).toContain('3/10');
52-
});
70+
expect(lastFrame()).toContain('S3::Bucket');
71+
expect(lastFrame()).not.toContain('AWS::S3::Bucket');
72+
});
73+
74+
it('skips CLEANUP messages', () => {
75+
const messages = [
76+
makeResourceMsg('Lambda::Function', 'CREATE_COMPLETE'),
77+
makeMsg('MyStack | CLEANUP_IN_PROGRESS | AWS::Lambda::Function | OldFunc'),
78+
];
79+
80+
const { lastFrame } = render(<DeployStatus messages={messages} isComplete={false} hasError={false} />);
81+
const frame = lastFrame()!;
82+
83+
expect(frame).toContain('CREATE_COMPLETE');
84+
expect(frame).not.toContain('CLEANUP');
85+
});
86+
87+
it('ignores non-resource-event messages (non-I5502 codes)', () => {
88+
const messages = [makeMsg('Some general info', 'CDK_TOOLKIT_I1234')];
89+
90+
const { lastFrame } = render(<DeployStatus messages={messages} isComplete={false} hasError={false} />);
91+
92+
// Should show deploying text but no resource lines
93+
expect(lastFrame()).toContain('Deploying to AWS');
94+
expect(lastFrame()).not.toContain('Some general info');
95+
});
5396

54-
it('skips CLEANUP messages', () => {
55-
const messages = [
56-
makeMsg('MyStack | CREATE_COMPLETE | AWS::Lambda::Function | MyFunc'),
57-
makeMsg('MyStack | CLEANUP_IN_PROGRESS | AWS::Lambda::Function | OldFunc'),
58-
];
97+
it('shows only last 8 resource events', () => {
98+
const messages = Array.from({ length: 12 }, (_, i) =>
99+
makeResourceMsg(`Service::Resource${i}`, 'CREATE_COMPLETE')
100+
);
59101

60-
const { lastFrame } = render(<DeployStatus messages={messages} isComplete={false} hasError={false} />);
102+
const { lastFrame } = render(<DeployStatus messages={messages} isComplete={false} hasError={false} />);
103+
const frame = lastFrame()!;
61104

62-
expect(lastFrame()).toContain('Lambda::Function');
63-
expect(lastFrame()).toContain('CREATE_COMPLETE');
105+
// First 4 should be trimmed (12 - 8 = 4)
106+
expect(frame).not.toContain('Resource0');
107+
expect(frame).not.toContain('Resource3');
108+
// Last 8 should be visible
109+
expect(frame).toContain('Resource4');
110+
expect(frame).toContain('Resource11');
111+
});
64112
});
65113

66-
it('ignores non-resource-event messages', () => {
67-
const messages = [makeMsg('Some general info', 'CDK_TOOLKIT_I1234')];
114+
describe('progress bar', () => {
115+
it('renders progress bar with completed/total count', () => {
116+
const messages = [makeMsg('deploying', 'CDK_TOOLKIT_I5502', { completed: 3, total: 10 })];
68117

69-
const { lastFrame } = render(<DeployStatus messages={messages} isComplete={false} hasError={false} />);
118+
const { lastFrame } = render(<DeployStatus messages={messages} isComplete={false} hasError={false} />);
119+
const frame = lastFrame()!;
120+
121+
expect(frame).toContain('3/10');
122+
expect(frame).toContain('█');
123+
expect(frame).toContain('░');
124+
});
125+
126+
it('shows full progress bar on completion', () => {
127+
const messages = [makeMsg('done', 'CDK_TOOLKIT_I5502', { completed: 10, total: 10 })];
128+
129+
const { lastFrame } = render(<DeployStatus messages={messages} isComplete={true} hasError={false} />);
130+
const frame = lastFrame()!;
131+
132+
// On completion, bar shows total/total
133+
expect(frame).toContain('10/10');
134+
});
135+
136+
it('does not show progress bar when no progress data', () => {
137+
const messages = [makeResourceMsg('Lambda::Function', 'CREATE_COMPLETE')];
138+
139+
const { lastFrame } = render(<DeployStatus messages={messages} isComplete={false} hasError={false} />);
140+
141+
expect(lastFrame()).not.toContain('█');
142+
expect(lastFrame()).not.toContain('░');
143+
});
144+
145+
it('uses most recent progress data', () => {
146+
const messages = [
147+
makeMsg('step1', 'CDK_TOOLKIT_I5502', { completed: 2, total: 10 }),
148+
makeMsg('step2', 'CDK_TOOLKIT_I5502', { completed: 7, total: 10 }),
149+
];
150+
151+
const { lastFrame } = render(<DeployStatus messages={messages} isComplete={false} hasError={false} />);
152+
153+
// Should show the latest progress
154+
expect(lastFrame()).toContain('7/10');
155+
});
156+
});
70157

71-
// Should still show the deploying text but no resource lines
72-
expect(lastFrame()).toContain('Deploying to AWS');
158+
describe('error state details', () => {
159+
it('shows last 3 resource events on failure', () => {
160+
const messages = [
161+
makeResourceMsg('Lambda::Function', 'CREATE_COMPLETE'),
162+
makeResourceMsg('IAM::Role', 'CREATE_COMPLETE'),
163+
makeResourceMsg('S3::Bucket', 'CREATE_COMPLETE'),
164+
makeResourceMsg('DynamoDB::Table', 'CREATE_FAILED'),
165+
];
166+
167+
const { lastFrame } = render(<DeployStatus messages={messages} isComplete={true} hasError={true} />);
168+
const frame = lastFrame()!;
169+
170+
// Last 3 of 4 resource events should show
171+
expect(frame).toContain('IAM::Role');
172+
expect(frame).toContain('S3::Bucket');
173+
expect(frame).toContain('DynamoDB::Table');
174+
});
73175
});
74176
});

0 commit comments

Comments
 (0)