Skip to content

Commit ae2a677

Browse files
PHKennyclaude
andcommitted
Add comprehensive unit test suite and testing infrastructure
- Created 200 new tests across 13 test files (67 → 267 total tests) - Increased code coverage from 3.4% to 5.4% (10 → 27 files covered) Phase 1 - Pure Functions & Utilities (166 tests): - Color extensions: hex conversion, parsing, round-trip tests - Theme colors: all 18+ predefined themes - ORM error handling: storage, retrieval, i18n fallback - Colorblind filters: 6 accessibility filter types with strength interpolation - File utilities: MIME detection, base64 encoding, custom .lc extension - Separator extensions: SizedBox helpers (.w, .h, .wh) Phase 2 - Widget Components (34 tests): - Avatar widget: rendering, icons, name cleaning, tap handlers - Field error display: error messages, hiding, styling - Table controller: event listeners, sort/refresh events Testing Infrastructure: - Makefile with test/coverage/coverage-summary targets - Coverage report tool (tools/coverage_report.dart) - CI integration: tests run on PRs with result publishing - Updated .gitignore for Claude Code settings Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 666b88f commit ae2a677

18 files changed

Lines changed: 2342 additions & 25 deletions

.github/workflows/checks.yaml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,16 @@ jobs:
2525
- name: Install dependencies
2626
run: flutter pub get
2727

28-
- name: Run checks
28+
- name: Run analyze
2929
run: flutter analyze
30+
31+
- name: Run tests
32+
run: flutter test --machine > test-results.json
33+
34+
- name: Publish test results
35+
uses: dorny/test-reporter@v1
36+
if: always()
37+
with:
38+
name: Unit Tests
39+
path: test-results.json
40+
reporter: flutter-json

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,6 @@ credentials.json
5252
ignoreme.dart
5353

5454
# Claude Code
55-
.claude/settings.local.json
55+
.claude/settings.local.json
56+
57+
coverage/

Makefile

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
.PHONY: analyze
2+
analyze:
3+
flutter analyze
4+
5+
.PHONY: test
6+
test:
7+
flutter test
8+
9+
.PHONY: coverage
10+
coverage:
11+
flutter test --coverage
12+
@dart run tools/coverage_report.dart
13+
14+
.PHONY: coverage-summary
15+
coverage-summary:
16+
flutter test --coverage
17+
@dart run tools/coverage_report.dart --summary

test/color_test.dart

Lines changed: 194 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,199 @@ import 'package:flutter_test/flutter_test.dart';
33
import 'package:layrz_theme/layrz_theme.dart';
44

55
void main() {
6-
test('Color.toJson', () {
7-
Color color = const Color(0xFF00FF00);
8-
expect(color.r, 0);
9-
expect(color.g, 1);
10-
expect(color.b, 0);
11-
expect(color.a, 1);
12-
expect(color.toHex(), "#00FF00");
13-
14-
color = color.withValues(alpha: 0.5);
15-
debugPrint(color.toString());
16-
expect(color.r, 0);
17-
expect(color.g, 1);
18-
expect(color.b, 0);
19-
expect(color.a, 0.5);
20-
expect(color.toHexWithAlpha(), "#8000FF00");
6+
group('toHex()', () {
7+
test('converts black to #000000', () {
8+
expect(Colors.black.toHex(), '#000000');
9+
});
10+
11+
test('converts white to #FFFFFF', () {
12+
expect(Colors.white.toHex(), '#FFFFFF');
13+
});
14+
15+
test('converts kAccentColor (#FF8200)', () {
16+
const color = Color(0xFFFF8200);
17+
expect(color.toHex(), '#FF8200');
18+
});
19+
20+
test('converts kPrimaryColor (#001e60)', () {
21+
const color = Color(0xFF001E60);
22+
expect(color.toHex(), '#001E60');
23+
});
24+
25+
test('outputs uppercase hex', () {
26+
const color = Color(0xFFaabbcc);
27+
expect(color.toHex(), '#AABBCC');
28+
});
29+
30+
test('ignores alpha channel', () {
31+
const color = Color(0x80FF8200);
32+
expect(color.toHex(), '#FF8200');
33+
});
34+
35+
test('hex getter is alias for toHex()', () {
36+
const color = Color(0xFFFF8200);
37+
expect(color.hex, color.toHex());
38+
expect(color.hex, '#FF8200');
39+
});
40+
});
41+
42+
group('toHexWithAlpha()', () {
43+
test('includes full alpha (#FFFFFFFF for white)', () {
44+
expect(Colors.white.toHexWithAlpha(), '#FFFFFFFF');
45+
});
46+
47+
test('includes zero alpha (#00000000 for transparent)', () {
48+
const color = Color(0x00000000);
49+
expect(color.toHexWithAlpha(), '#00000000');
50+
});
51+
52+
test('includes partial alpha (#80FF8200)', () {
53+
const color = Color(0x80FF8200);
54+
expect(color.toHexWithAlpha(), '#80FF8200');
55+
});
56+
57+
test('alpha comes first in AARRGGBB format', () {
58+
Color color = const Color(0xFF00FF00);
59+
expect(color.toHexWithAlpha(), '#FF00FF00');
60+
61+
color = color.withValues(alpha: 0.5);
62+
expect(color.toHexWithAlpha(), '#8000FF00');
63+
});
64+
65+
test('hexWithAlpha getter is alias for toHexWithAlpha()', () {
66+
const color = Color(0x80FF8200);
67+
expect(color.hexWithAlpha, color.toHexWithAlpha());
68+
expect(color.hexWithAlpha, '#80FF8200');
69+
});
70+
});
71+
72+
group('toInt()', () {
73+
test('converts to ARGB integer', () {
74+
const color = Color(0xFFFF8200);
75+
expect(color.toInt(), 0xFFFF8200);
76+
});
77+
78+
test('handles alpha channel correctly', () {
79+
const color = Color(0x80FF8200);
80+
expect(color.toInt(), 0x80FF8200);
81+
});
82+
83+
test('black is 0xFF000000', () {
84+
expect(Colors.black.toInt(), 0xFF000000);
85+
});
86+
87+
test('white is 0xFFFFFFFF', () {
88+
expect(Colors.white.toInt(), 0xFFFFFFFF);
89+
});
90+
91+
test('transparent is 0x00000000', () {
92+
const color = Color(0x00000000);
93+
expect(color.toInt(), 0x00000000);
94+
});
95+
});
96+
97+
group('fromHex()', () {
98+
test('parses black #000000', () {
99+
final color = ThemedColorExtensions.fromHex('#000000');
100+
expect(color.toHex(), '#000000');
101+
});
102+
103+
test('parses white #FFFFFF', () {
104+
final color = ThemedColorExtensions.fromHex('#FFFFFF');
105+
expect(color.toHex(), '#FFFFFF');
106+
});
107+
108+
test('parses accent color #FF8200', () {
109+
final color = ThemedColorExtensions.fromHex('#FF8200');
110+
expect(color.toHex(), '#FF8200');
111+
});
112+
113+
test('parses primary color #001E60', () {
114+
final color = ThemedColorExtensions.fromHex('#001E60');
115+
expect(color.toHex(), '#001E60');
116+
});
117+
118+
test('parses lowercase hex #ff8200', () {
119+
final color = ThemedColorExtensions.fromHex('#ff8200');
120+
expect(color.toHex(), '#FF8200');
121+
});
122+
123+
test('sets alpha to 255 (fully opaque)', () {
124+
final color = ThemedColorExtensions.fromHex('#FF8200');
125+
expect((color.a * 255.0).round(), 255);
126+
});
127+
});
128+
129+
group('fromHexWithAlpha()', () {
130+
test('parses full alpha #FFFFFFFF', () {
131+
final color = ThemedColorExtensions.fromHexWithAlpha('#FFFFFFFF');
132+
expect(color.toHexWithAlpha(), '#FFFFFFFF');
133+
expect((color.a * 255.0).round(), 255);
134+
});
135+
136+
test('parses zero alpha #00000000', () {
137+
final color = ThemedColorExtensions.fromHexWithAlpha('#00000000');
138+
expect(color.toHexWithAlpha(), '#00000000');
139+
expect((color.a * 255.0).round(), 0);
140+
});
141+
142+
test('parses partial alpha #80FF8200', () {
143+
final color = ThemedColorExtensions.fromHexWithAlpha('#80FF8200');
144+
expect(color.toHexWithAlpha(), '#80FF8200');
145+
expect((color.a * 255.0).round(), 128);
146+
});
147+
148+
test('parses alpha first in AARRGGBB format', () {
149+
final color = ThemedColorExtensions.fromHexWithAlpha('#8000FF00');
150+
expect((color.r * 255.0).round(), 0);
151+
expect((color.g * 255.0).round(), 255);
152+
expect((color.b * 255.0).round(), 0);
153+
expect((color.a * 255.0).round(), 128);
154+
});
155+
});
156+
157+
group('Round-trip conversions', () {
158+
test('Color -> toHex() -> fromHex() -> Color preserves RGB', () {
159+
const original = Color(0xFFFF8200);
160+
final hex = original.toHex();
161+
final restored = ThemedColorExtensions.fromHex(hex);
162+
expect(restored.toHex(), original.toHex());
163+
expect((restored.r * 255.0).round(), (original.r * 255.0).round());
164+
expect((restored.g * 255.0).round(), (original.g * 255.0).round());
165+
expect((restored.b * 255.0).round(), (original.b * 255.0).round());
166+
});
167+
168+
test('Color -> toHexWithAlpha() -> fromHexWithAlpha() -> Color preserves ARGB', () {
169+
const original = Color(0x80FF8200);
170+
final hex = original.toHexWithAlpha();
171+
final restored = ThemedColorExtensions.fromHexWithAlpha(hex);
172+
expect(restored.toHexWithAlpha(), original.toHexWithAlpha());
173+
expect((restored.a * 255.0).round(), (original.a * 255.0).round());
174+
expect((restored.r * 255.0).round(), (original.r * 255.0).round());
175+
expect((restored.g * 255.0).round(), (original.g * 255.0).round());
176+
expect((restored.b * 255.0).round(), (original.b * 255.0).round());
177+
});
178+
179+
test('Color -> toInt() -> Color() preserves exact value', () {
180+
const original = Color(0x80FF8200);
181+
final intValue = original.toInt();
182+
final restored = Color(intValue);
183+
expect(restored.toInt(), original.toInt());
184+
});
185+
});
186+
187+
group('JSON aliases', () {
188+
test('toJson() is equivalent to toHex()', () {
189+
const color = Color(0xFFFF8200);
190+
expect(color.toJson(), color.toHex());
191+
expect(color.toJson(), '#FF8200');
192+
});
193+
194+
test('fromJson() is equivalent to fromHex()', () {
195+
final color1 = ThemedColorExtensions.fromJson('#FF8200');
196+
final color2 = ThemedColorExtensions.fromHex('#FF8200');
197+
expect(color1.toHex(), color2.toHex());
198+
expect(color1.toInt(), color2.toInt());
199+
});
21200
});
22201
}

0 commit comments

Comments
 (0)