Skip to content

Commit e91e1b7

Browse files
committed
v1.6.1
1 parent 5544ebc commit e91e1b7

4 files changed

Lines changed: 63 additions & 74 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
## [1.6.1] - 2026-03-29
2+
3+
### Changed
4+
- Test command: redesigned output to group results by test suite with bold suite headers (e.g. `Test\AuthTest`)
5+
- Test command: results now stream in real-time as each test completes, rather than buffering all output
6+
- Test command: updated summary format to show `Tests: X passed` and `Duration: Xs`
7+
- Test command: added suite display name derivation from file paths using PascalCase
8+
19
## [1.6.0] - 2026-03-28
210

311
### Added

lib/src/commands/test_command.dart

Lines changed: 53 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'dart:convert';
22
import 'dart:io';
33
import 'package:args/args.dart';
44
import 'package:path/path.dart' as path;
5+
import 'package:recase/recase.dart';
56
import '../console/console.dart';
67

78
/// Tracks a test that has started but not yet finished
@@ -92,9 +93,6 @@ class TestCommand {
9293
if (coverage) args.add('--coverage');
9394
args.add(testPath);
9495

95-
NyloConsole.writeStep('Running tests...');
96-
NyloConsole.write('');
97-
9896
final process = await Process.start(
9997
'flutter',
10098
args,
@@ -105,6 +103,7 @@ class TestCommand {
105103
final tests = <int, _TestInfo>{};
106104
final testErrors = <int, String>{};
107105
final allResults = <_IndividualTestResult>[];
106+
final printedSuites = <String>{};
108107
final rawOutput = StringBuffer();
109108
final stderrBuffer = StringBuffer();
110109

@@ -117,7 +116,7 @@ class TestCommand {
117116
final result = _processJsonLine(line, suites, tests, testErrors);
118117
if (result != null) {
119118
allResults.add(result);
120-
_printTestResult(result);
119+
_printTestResult(result, printedSuites);
121120
}
122121
}),
123122
process.stderr.transform(utf8.decoder).forEach((data) {
@@ -207,90 +206,84 @@ class TestCommand {
207206
return null;
208207
}
209208

210-
/// Print a formatted test result line
211-
void _printTestResult(_IndividualTestResult result) {
209+
/// Convert a suite file path to a display name
210+
/// e.g. `test/auth_test.dart``Test\Auth`
211+
String _suiteDisplayName(String suitePath) {
212+
var relative = path.relative(suitePath);
213+
if (relative.endsWith('.dart')) {
214+
relative = relative.substring(0, relative.length - 5);
215+
}
216+
if (relative.endsWith('_test')) {
217+
relative = relative.substring(0, relative.length - 5);
218+
}
219+
final segments = path.split(relative);
220+
return segments.map((s) => ReCase(s).pascalCase).join('\\');
221+
}
222+
223+
/// Print a single test result, printing suite header on first encounter
224+
void _printTestResult(
225+
_IndividualTestResult result, Set<String> printedSuites) {
226+
// Print suite header if this is the first test from this suite
227+
if (printedSuites.add(result.suitePath)) {
228+
final displayName = _suiteDisplayName(result.suitePath);
229+
stdout.writeln('\n \x1B[1m$displayName\x1B[0m');
230+
}
231+
212232
final termWidth = _getTerminalWidth();
233+
final icon =
234+
result.passed ? '\x1B[92m\u2713\x1B[0m' : '\x1B[91m\u2717\x1B[0m';
213235
final durationStr = _formatTestDuration(result.duration);
214-
final suitePath = path.relative(result.suitePath);
215236
final testName = result.testName;
216237

217-
// Build visible label (no ANSI) for width calculations
218-
final visibleLabel = '$suitePath > $testName';
219238
final prefixLen = 4; // " ✓ " or " ✗ "
220-
final availableWidth = termWidth - prefixLen - durationStr.length - 1;
239+
final availableWidth = termWidth - prefixLen - durationStr.length - 2;
221240

222-
// Truncate if too long
223-
String displayVisible;
224-
if (availableWidth > 3 && visibleLabel.length > availableWidth) {
225-
displayVisible = '${visibleLabel.substring(0, availableWidth - 1)}\u2026';
241+
String displayTest;
242+
if (availableWidth > 3 && testName.length > availableWidth) {
243+
displayTest = '${testName.substring(0, availableWidth - 1)}\u2026';
226244
} else {
227-
displayVisible = visibleLabel;
245+
displayTest = testName;
228246
}
229247

230-
// Pad for right-aligned duration
231248
final padLen =
232-
termWidth - prefixLen - displayVisible.length - durationStr.length;
249+
termWidth - prefixLen - displayTest.length - durationStr.length;
233250
final pad = padLen > 0 ? ' ' * padLen : ' ';
234-
235-
// Add ANSI color to the ">" separator
236-
String ansiLabel;
237-
if (displayVisible.length > suitePath.length + 3) {
238-
final testPart = displayVisible.substring(suitePath.length + 3);
239-
ansiLabel = '$suitePath \x1B[90m>\x1B[0m $testPart';
240-
} else {
241-
ansiLabel = displayVisible;
242-
}
243-
244-
final icon =
245-
result.passed ? '\x1B[92m\u2713\x1B[0m' : '\x1B[91m\u2717\x1B[0m';
246251
final coloredDuration = _colorDuration(result.duration, durationStr);
247252

248-
stdout.writeln(' $icon $ansiLabel$pad$coloredDuration');
253+
stdout.writeln(' $icon $displayTest$pad$coloredDuration');
254+
255+
// Print error details for failed tests
256+
if (!result.passed &&
257+
result.errorMessage != null &&
258+
result.errorMessage!.trim().isNotEmpty) {
259+
final cleanError = _stripAnsiCodes(result.errorMessage!);
260+
for (final errorLine in cleanError.split('\n')) {
261+
if (errorLine.trim().isNotEmpty) {
262+
stdout.writeln('\x1B[91m $errorLine\x1B[0m');
263+
}
264+
}
265+
}
249266
}
250267

251268
/// Print aggregated test summary
252269
void _printSummary(List<_IndividualTestResult> results) {
253270
final passed = results.where((r) => r.passed).length;
254271
final failed = results.where((r) => !r.passed).length;
255-
final total = results.length;
256-
257-
NyloConsole.write('');
258-
NyloConsole.write(' ${'─' * 50}');
259-
NyloConsole.write('');
260-
261-
// Print failure details
262-
if (failed > 0) {
263-
NyloConsole.writeError('Failed tests:');
264-
NyloConsole.write('');
265-
for (final r in results.where((r) => !r.passed)) {
266-
final suitePath = path.relative(r.suitePath);
267-
NyloConsole.writeError(' $suitePath > ${r.testName}');
268-
if (r.errorMessage != null && r.errorMessage!.trim().isNotEmpty) {
269-
final cleanError = _stripAnsiCodes(r.errorMessage!);
270-
for (final errorLine in cleanError.split('\n')) {
271-
if (errorLine.trim().isNotEmpty) {
272-
NyloConsole.write(' $errorLine');
273-
}
274-
}
275-
}
276-
NyloConsole.write('');
277-
}
278-
}
279272

280-
final summary = 'Tests: $passed passed, $failed failed, $total total';
281273
final totalTime = results.fold<Duration>(
282274
Duration.zero,
283275
(sum, r) => sum + r.duration,
284276
);
285-
final time = 'Time: ${_formatDuration(totalTime)}';
277+
final durationStr = _formatTestDuration(totalTime);
286278

279+
NyloConsole.write('');
287280
if (failed > 0) {
288-
stdout.writeln('\x1B[91m \u2717 $summary\x1B[0m');
289-
stdout.writeln('\x1B[91m $time\x1B[0m');
281+
stdout.writeln(
282+
' \x1B[1mTests:\x1B[0m \x1B[92m$passed passed\x1B[0m, \x1B[91m$failed failed\x1B[0m');
290283
} else {
291-
NyloConsole.writeStepComplete(summary);
292-
NyloConsole.write(' $time');
284+
stdout.writeln(' \x1B[1mTests:\x1B[0m \x1B[92m$passed passed\x1B[0m');
293285
}
286+
stdout.writeln(' \x1B[1mDuration:\x1B[0m $durationStr');
294287
}
295288

296289
/// Color a duration string based on speed
@@ -307,18 +300,6 @@ class TestCommand {
307300
return '${seconds.toStringAsFixed(2)}s';
308301
}
309302

310-
/// Format duration for summary display
311-
String _formatDuration(Duration duration) {
312-
if (duration.inMinutes > 0) {
313-
return '${duration.inMinutes}m ${duration.inSeconds.remainder(60)}s';
314-
}
315-
if (duration.inSeconds > 0) {
316-
final ms = duration.inMilliseconds.remainder(1000);
317-
return '${duration.inSeconds}.${(ms ~/ 100)}s';
318-
}
319-
return '${duration.inMilliseconds}ms';
320-
}
321-
322303
/// Strip ANSI escape codes and their literal representations from text
323304
String _stripAnsiCodes(String text) {
324305
// Strip actual ANSI escape sequences (byte 0x1B)

lib/src/constants.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class Constants {
88
static const String templateRepoUrl = 'https://github.com/nylo-core/nylo';
99

1010
/// Installer version
11-
static const String version = '1.6.0';
11+
static const String version = '1.6.1';
1212

1313
/// Documentation URL
1414
static const String docsUrl = 'https://nylo.dev/docs';

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: nylo_installer
22
description: CLI tool to create new Nylo Flutter projects. Quickly scaffold production-ready Flutter applications.
3-
version: 1.6.0
3+
version: 1.6.1
44
homepage: https://nylo.dev
55
repository: https://github.com/nylo-core/nylo-installer
66
issue_tracker: https://github.com/nylo-core/nylo-installer/issues

0 commit comments

Comments
 (0)