From 9c210f42b5c2092c4265339af2071fbe39056cd9 Mon Sep 17 00:00:00 2001 From: Yelaman Yelmuratov Date: Fri, 17 May 2024 22:49:13 +0500 Subject: [PATCH] . r F highlight error text in command line reporter --- example/main.dart | 2 +- .../verify_methods/verify/verify_test.dart | 3 +- .../verify_as_json/verify_as_json_test.dart | 6 +- lib/approval_tests.dart | 14 +++-- lib/src/approvals.dart | 15 +++-- lib/src/comparator/file_comparator.dart | 31 ++++++++++ lib/src/{reporters => core}/comparator.dart | 2 +- .../approval_string_extensions.dart | 4 +- lib/src/core/options.dart | 6 +- lib/src/core/reporter.dart | 6 ++ lib/src/core/utils/utils.dart | 18 ------ lib/src/reporters/command_line/colorize.dart | 19 ++++++ .../command_line/command_line_reporter.dart | 60 +++++++++++++++++++ .../reporters/command_line_comparator.dart | 50 ---------------- .../reporters/{ => diff_tool}/diff_info.dart | 2 +- .../diff_tool_reporter.dart} | 41 +++---------- .../reporters/{ => diff_tool}/diff_tools.dart | 2 +- test/approval_test.dart | 25 ++++---- test/utils/helper.dart | 8 +-- 19 files changed, 176 insertions(+), 138 deletions(-) create mode 100644 lib/src/comparator/file_comparator.dart rename lib/src/{reporters => core}/comparator.dart (92%) create mode 100644 lib/src/core/reporter.dart create mode 100644 lib/src/reporters/command_line/colorize.dart create mode 100644 lib/src/reporters/command_line/command_line_reporter.dart delete mode 100644 lib/src/reporters/command_line_comparator.dart rename lib/src/reporters/{ => diff_tool}/diff_info.dart (82%) rename lib/src/reporters/{ide_comparator.dart => diff_tool/diff_tool_reporter.dart} (53%) rename lib/src/reporters/{ => diff_tool}/diff_tools.dart (96%) diff --git a/example/main.dart b/example/main.dart index 1fc3a85..26b50ee 100644 --- a/example/main.dart +++ b/example/main.dart @@ -7,7 +7,7 @@ void main() { Approvals.verifyAll( [3, 5, 15], options: const Options( - comparator: IDEComparator(), + reporter: DiffReporter(), deleteReceivedFile: true, ), processor: (items) => fizzBuzz(items).toString(), diff --git a/example/verify_methods/verify/verify_test.dart b/example/verify_methods/verify/verify_test.dart index 64cbd46..a69ed12 100644 --- a/example/verify_methods/verify/verify_test.dart +++ b/example/verify_methods/verify/verify_test.dart @@ -3,7 +3,8 @@ import 'package:test/test.dart'; void main() { test('verify method', () { - const String response = '{"result": "success", "data": {"id": 1, "name": "Item"}}'; + const String response = + '{"result": "success", "data": {"id": 1, "name": "Item"}}'; Approvals.verify( response, diff --git a/example/verify_methods/verify_as_json/verify_as_json_test.dart b/example/verify_methods/verify_as_json/verify_as_json_test.dart index d59b82f..c49c398 100644 --- a/example/verify_methods/verify_as_json/verify_as_json_test.dart +++ b/example/verify_methods/verify_as_json/verify_as_json_test.dart @@ -23,8 +23,10 @@ void main() { Approvals.verifyAsJson( jsonItem, options: const Options( - deleteReceivedFile: true, // Automatically delete the received file after the test. - approveResult: true, // Approve the result automatically. You can remove this property after the approved file is created. + deleteReceivedFile: + true, // Automatically delete the received file after the test. + approveResult: + true, // Approve the result automatically. You can remove this property after the approved file is created. ), ); }); diff --git a/lib/approval_tests.dart b/lib/approval_tests.dart index e4e11d9..d63b19f 100644 --- a/lib/approval_tests.dart +++ b/lib/approval_tests.dart @@ -12,6 +12,7 @@ part 'src/writers/approval_text_writer.dart'; part 'src/core/logger/logger.dart'; part 'src/core/utils/utils.dart'; +part 'src/core/reporter.dart'; part 'src/core/options.dart'; part 'src/core/approval_writer.dart'; part 'src/core/enums/comporator_ide.dart'; @@ -27,12 +28,15 @@ part 'src/namer/namer.dart'; part 'src/core/approval_number.dart'; part 'src/namer/file_namer_options.dart'; -part 'src/reporters/comparator.dart'; -part 'src/reporters/command_line_comparator.dart'; -part 'src/reporters/ide_comparator.dart'; -part 'src/reporters/diff_info.dart'; -part 'src/reporters/diff_tools.dart'; +part 'src/core/comparator.dart'; +part 'src/comparator/file_comparator.dart'; +part 'src/reporters/diff_tool/diff_info.dart'; +part 'src/reporters/diff_tool/diff_tools.dart'; part 'src/exceptions/doesnt_match_exception.dart'; part 'src/exceptions/command_line_comparator_exception.dart'; part 'src/exceptions/ide_comparator_exception.dart'; + +part 'src/reporters/command_line/command_line_reporter.dart'; +part 'src/reporters/diff_tool/diff_tool_reporter.dart'; +part 'src/reporters/command_line/colorize.dart'; diff --git a/lib/src/approvals.dart b/lib/src/approvals.dart index b9e7d4f..3fbc7ed 100644 --- a/lib/src/approvals.dart +++ b/lib/src/approvals.dart @@ -34,20 +34,19 @@ class Approvals { } // Check if received file matches the approved file - final bool isFilesMatch = - ApprovalUtils.filesMatch(namer.approved, namer.received); + final bool isFilesMatch = options.comparator.compare( + approvedPath: namer.approved, + receivedPath: namer.received, + isLogError: options.logErrors, + ); // Log results and throw exception if files do not match if (!isFilesMatch) { - options.comparator.compare( - approvedPath: namer.approved, - receivedPath: namer.received, - isLogError: options.logErrors, - ); + options.reporter.report(namer.approved, namer.received); throw DoesntMatchException( 'Test failed: ${namer.approved} does not match ${namer.received}.\n - Approved file path: ${namer.approved}\n - Received file path: ${namer.received}', ); - } else if (isFilesMatch) { + } else { if (options.logResults) { ApprovalLogger.success( 'Test passed: [${namer.approvedFileName}] matches [${namer.receivedFileName}]\n- Approved file path: ${namer.approved}\n- Received file path: ${namer.received}', diff --git a/lib/src/comparator/file_comparator.dart b/lib/src/comparator/file_comparator.dart new file mode 100644 index 0000000..53f2b8c --- /dev/null +++ b/lib/src/comparator/file_comparator.dart @@ -0,0 +1,31 @@ +part of '../../approval_tests.dart'; + +/// A class `FileComparator` that implements the `Comparator` interface. +/// This class is used to compare the content of two files. +final class FileComparator implements Comparator { + const FileComparator(); + + @override + bool compare({ + required String approvedPath, + required String receivedPath, + bool isLogError = true, + }) { + try { + final approved = ApprovalUtils.readFile(path: approvedPath) + .replaceAll('\r\n', '\n') + .trim(); + final received = ApprovalUtils.readFile(path: receivedPath) + .replaceAll('\r\n', '\n') + .trim(); + + // Return true if contents of both files match exactly + return approved.compareTo(received) == 0; + } catch (e) { + if (isLogError) { + ApprovalLogger.exception(e.toString()); + } + rethrow; + } + } +} diff --git a/lib/src/reporters/comparator.dart b/lib/src/core/comparator.dart similarity index 92% rename from lib/src/reporters/comparator.dart rename to lib/src/core/comparator.dart index 9246999..887c4e5 100644 --- a/lib/src/reporters/comparator.dart +++ b/lib/src/core/comparator.dart @@ -5,7 +5,7 @@ abstract interface class Comparator { const Comparator(); /// A method named `compare` for comparing two files. - Future compare({ + bool compare({ required String approvedPath, required String receivedPath, bool isLogError = true, diff --git a/lib/src/core/extensions/approval_string_extensions.dart b/lib/src/core/extensions/approval_string_extensions.dart index b8687d1..6dcd13f 100644 --- a/lib/src/core/extensions/approval_string_extensions.dart +++ b/lib/src/core/extensions/approval_string_extensions.dart @@ -11,6 +11,8 @@ extension StringExtensions on String { }) { final regExp = RegExp(matchingPattern); return replaceAllMapped( - regExp, (match) => replacementProvider(match.group(0)!),); + regExp, + (match) => replacementProvider(match.group(0)!), + ); } } diff --git a/lib/src/core/options.dart b/lib/src/core/options.dart index 443c0e0..06f3b1b 100644 --- a/lib/src/core/options.dart +++ b/lib/src/core/options.dart @@ -8,6 +8,9 @@ class Options { /// A final variable `comparator` of type `Comparator` used to compare the approved and received files. final Comparator comparator; + /// A final variable `reporter` of type `Reporter` used to report the comparison results. + final Reporter reporter; + /// A final bool variable `approveResult` used to determine if the result should be approved after the test. final bool approveResult; @@ -27,7 +30,8 @@ class Options { const Options({ this.scrubber = const ScrubNothing(), this.approveResult = false, - this.comparator = const CommandLineComparator(), + this.comparator = const FileComparator(), + this.reporter = const CommandLineReporter(), this.deleteReceivedFile = false, this.namer, this.logErrors = true, diff --git a/lib/src/core/reporter.dart b/lib/src/core/reporter.dart new file mode 100644 index 0000000..8d2f2d8 --- /dev/null +++ b/lib/src/core/reporter.dart @@ -0,0 +1,6 @@ +part of '../../approval_tests.dart'; + +/// `Reporter` is an abstract class for reporting the comparison results. +abstract interface class Reporter { + void report(String approvedPath, String receivedPath); +} diff --git a/lib/src/core/utils/utils.dart b/lib/src/core/utils/utils.dart index ad61a28..ab1e520 100644 --- a/lib/src/core/utils/utils.dart +++ b/lib/src/core/utils/utils.dart @@ -54,24 +54,6 @@ final class ApprovalUtils { static String lines(int count) => List.filled(count, '=').join(); - // Helper private method to check if contents of two files match - static bool filesMatch(String approvedPath, String receivedPath) { - try { - // Read contents of the approved and received files - final approved = ApprovalUtils.readFile(path: approvedPath) - .replaceAll('\r\n', '\n') - .trim(); - final received = ApprovalUtils.readFile(path: receivedPath) - .replaceAll('\r\n', '\n') - .trim(); - - // Return true if contents of both files match exactly - return approved.compareTo(received) == 0; - } catch (_) { - rethrow; - } - } - static void deleteFile(String path) { try { final File file = File(path); diff --git a/lib/src/reporters/command_line/colorize.dart b/lib/src/reporters/command_line/colorize.dart new file mode 100644 index 0000000..6afcdef --- /dev/null +++ b/lib/src/reporters/command_line/colorize.dart @@ -0,0 +1,19 @@ +part of '../../../approval_tests.dart'; + +enum _LogColorStyles { red, bgRed } + +class _Colorize { + static const String esc = "\u{1B}["; + final String text; + + const _Colorize(this.text); + + _Colorize apply(_LogColorStyles style) { + final appliedText = + "$esc${style == _LogColorStyles.red ? '38;2;222;121;121' : '41'}m$text${esc}0m"; + return _Colorize(appliedText); + } + + @override + String toString() => text; +} diff --git a/lib/src/reporters/command_line/command_line_reporter.dart b/lib/src/reporters/command_line/command_line_reporter.dart new file mode 100644 index 0000000..fccba3f --- /dev/null +++ b/lib/src/reporters/command_line/command_line_reporter.dart @@ -0,0 +1,60 @@ +part of '../../../approval_tests.dart'; + +/// `CommandLineReporter` is a class for reporting the comparison results using the command line. +class CommandLineReporter implements Reporter { + const CommandLineReporter(); + + @override + void report(String approvedPath, String receivedPath, {String? message}) { + try { + final String approvedContent = ApprovalUtils.readFile(path: approvedPath); + final String receivedContent = ApprovalUtils.readFile(path: receivedPath); + + final StringBuffer buffer = StringBuffer(message ?? "Differences:\n"); + final List approvedLines = approvedContent.split('\n'); + final List receivedLines = receivedContent.split('\n'); + + final int maxLines = max(approvedLines.length, receivedLines.length); + for (int i = 0; i < maxLines; i++) { + final String approvedLine = + i < approvedLines.length ? approvedLines[i] : ""; + final String receivedLine = + i < receivedLines.length ? receivedLines[i] : ""; + + if (approvedLine != receivedLine) { + buffer.writeln( + '${ApprovalUtils.lines(20)} Difference at line ${i + 1} ${ApprovalUtils.lines(20)}', + ); + buffer.writeln( + 'Approved file: ${_highlightDifference(approvedLine, receivedLine)}', + ); + buffer.writeln( + 'Received file: ${_highlightDifference(receivedLine, approvedLine)}', + ); + } + } + + if (buffer.isNotEmpty) { + final String reportMessage = buffer.toString(); + ApprovalLogger.exception(reportMessage); + } + } catch (e) { + rethrow; + } + } + + String _highlightDifference(String line1, String line2) { + final int minLength = min(line1.length, line2.length); + final StringBuffer highlighted = StringBuffer(); + + for (int i = 0; i < minLength; i++) { + if (line1[i] != line2[i]) { + highlighted.write(_Colorize(line1[i]).apply(_LogColorStyles.bgRed)); + } else { + highlighted.write(_Colorize(line1[i]).apply(_LogColorStyles.red)); + } + } + + return highlighted.toString(); + } +} diff --git a/lib/src/reporters/command_line_comparator.dart b/lib/src/reporters/command_line_comparator.dart deleted file mode 100644 index e87f62f..0000000 --- a/lib/src/reporters/command_line_comparator.dart +++ /dev/null @@ -1,50 +0,0 @@ -part of '../../approval_tests.dart'; - -/// `CommandLineComparator` it is for comparing files via Command Line. -/// -/// This method compares the content of two files line by line and prints the differences in the console. -class CommandLineComparator extends Comparator { - const CommandLineComparator(); - - @override - Future compare({ - required String approvedPath, - required String receivedPath, - bool isLogError = true, - }) async { - try { - final String approvedContent = ApprovalUtils.readFile(path: approvedPath); - final String receivedContent = ApprovalUtils.readFile(path: receivedPath); - - final StringBuffer buffer = StringBuffer("Differences:\n"); - final List approvedLines = approvedContent.split('\n'); - final List receivedLines = receivedContent.split('\n'); - - final int maxLines = max(approvedLines.length, receivedLines.length); - for (int i = 0; i < maxLines; i++) { - if (i >= approvedLines.length || i >= receivedLines.length || approvedLines[i] != receivedLines[i]) { - buffer.writeln( - '${ApprovalUtils.lines(20)} Difference at line ${i + 1} ${ApprovalUtils.lines(20)}', - ); - buffer.writeln( - 'Approved file: ${i < approvedLines.length ? approvedLines[i] : "No content"}', - ); - buffer.writeln( - 'Received file: ${i < receivedLines.length ? receivedLines[i] : "No content"}', - ); - } - } - - if (buffer.isNotEmpty && isLogError) { - final String message = buffer.toString(); - ApprovalLogger.exception(message); - } - } catch (e, st) { - throw CommandLineComparatorException( - message: 'Error during comparison via Command Line.', - exception: e, - stackTrace: st, - ); - } - } -} diff --git a/lib/src/reporters/diff_info.dart b/lib/src/reporters/diff_tool/diff_info.dart similarity index 82% rename from lib/src/reporters/diff_info.dart rename to lib/src/reporters/diff_tool/diff_info.dart index aae3a14..c171fc8 100644 --- a/lib/src/reporters/diff_info.dart +++ b/lib/src/reporters/diff_tool/diff_info.dart @@ -1,4 +1,4 @@ -part of '../../../approval_tests.dart'; +part of '../../../../approval_tests.dart'; /// `DiffInfo` is a class for storing information about a diff tool. class DiffInfo { diff --git a/lib/src/reporters/ide_comparator.dart b/lib/src/reporters/diff_tool/diff_tool_reporter.dart similarity index 53% rename from lib/src/reporters/ide_comparator.dart rename to lib/src/reporters/diff_tool/diff_tool_reporter.dart index bd0dbfd..19d64fb 100644 --- a/lib/src/reporters/ide_comparator.dart +++ b/lib/src/reporters/diff_tool/diff_tool_reporter.dart @@ -1,36 +1,19 @@ -part of '../../approval_tests.dart'; +part of '../../../approval_tests.dart'; -/// `IDEComparator` is a class for comparing files using an `IDE`. -/// -/// Available IDEs: -/// - `Visual Studio Code` -/// - `Android Studio` -final class IDEComparator extends Comparator { +/// `DiffReporter` is a class for reporting the comparison results using a `Diff Tool`. +class DiffReporter implements Reporter { final ComparatorIDE ide; final DiffInfo? customDiffInfo; - const IDEComparator({ + const DiffReporter({ this.ide = ComparatorIDE.vsCode, this.customDiffInfo, }); @override - Future compare({ - required String approvedPath, - required String receivedPath, - bool isLogError = true, - }) async { + Future report(String approvedPath, String receivedPath) async { + final DiffInfo diffInfo = _diffInfo; try { - final File approvedFile = File(approvedPath); - final File receivedFile = File(receivedPath); - - if (!_fileExists(approvedFile) || !_fileExists(receivedFile)) { - _throwFileException( - 'Files not found for comparison. Please check the paths: \n\n Approved file path: $approvedPath, \n\n Received file path: $receivedPath.', - ); - } - final DiffInfo diffInfo = _diffInfo; - await Process.run( diffInfo.command, [diffInfo.arg, approvedPath, receivedPath], @@ -38,7 +21,7 @@ final class IDEComparator extends Comparator { } catch (e, st) { throw IDEComparatorException( message: - 'Error during comparison via ${ide.name}. Please try restart your IDE.', + 'Error during comparison via ${ide.name}. Please try check path to IDE. \n Current path: ${diffInfo.command}.', exception: e, stackTrace: st, ); @@ -67,14 +50,4 @@ final class IDEComparator extends Comparator { } } } - - bool _fileExists(File file) => file.existsSync(); - - void _throwFileException(String message) { - throw IDEComparatorException( - message: message, - exception: null, - stackTrace: StackTrace.current, - ); - } } diff --git a/lib/src/reporters/diff_tools.dart b/lib/src/reporters/diff_tool/diff_tools.dart similarity index 96% rename from lib/src/reporters/diff_tools.dart rename to lib/src/reporters/diff_tool/diff_tools.dart index 4ec223d..1d1386e 100644 --- a/lib/src/reporters/diff_tools.dart +++ b/lib/src/reporters/diff_tool/diff_tools.dart @@ -1,4 +1,4 @@ -part of '../../../approval_tests.dart'; +part of '../../../../approval_tests.dart'; /// `MacDiffTools` contains diff tools available on macOS. final class MacDiffTools { diff --git a/test/approval_test.dart b/test/approval_test.dart index 0eaaa9b..935d86e 100644 --- a/test/approval_test.dart +++ b/test/approval_test.dart @@ -92,7 +92,8 @@ void main() { ); }); - test("Method «verify» must throw DoesntMatchException with error handling", () { + test("Method «verify» must throw DoesntMatchException with error handling", + () { expect( () => helper.verify( 'Hello W0rld', @@ -113,8 +114,10 @@ void main() { ApprovalLogger.log("$lines25 Group: Minor cases are starting $lines25"); }); - test('Simulate file not found error during comparison. Must throw CommandLineComparatorException.', () async { - const comparator = CommandLineComparator(); + test( + 'Simulate file not found error during comparison. Must throw CommandLineComparatorException.', + () async { + const comparator = FileComparator(); // Setup: paths to non-existent files const nonExistentApprovedPath = 'path/to/nonexistent/approved.txt'; @@ -134,8 +137,10 @@ void main() { ); }); - test('Simulate file not found error during comparison. Must throw IDEComparatorException.', () async { - const comparator = IDEComparator(); + test( + 'Simulate file not found error during comparison. Must throw IDEComparatorException.', + () async { + const reporter = DiffReporter(); // Setup: paths to non-existent files const nonExistentApprovedPath = 'path/to/nonexistent/approved.txt'; @@ -143,9 +148,9 @@ void main() { // Expect an exception to be thrown expect( - () => comparator.compare( - approvedPath: nonExistentApprovedPath, - receivedPath: nonExistentReceivedPath, + () => reporter.report( + nonExistentApprovedPath, + nonExistentReceivedPath, ), throwsA(isA()), ); @@ -155,13 +160,13 @@ void main() { ); }); - test('verify string with IDEComparator', () { + test('verify string with DiffReporter', () { expect( () => helper.verify( 'Hello W0rld', 'verify', deleteReceivedFile: false, - comparator: const IDEComparator(), + reporter: const DiffReporter(), ), throwsA(isA()), ); diff --git a/test/utils/helper.dart b/test/utils/helper.dart index 832100d..ef25266 100644 --- a/test/utils/helper.dart +++ b/test/utils/helper.dart @@ -27,8 +27,8 @@ class ApprovalTestHelper { bool approveResult = false, bool deleteReceivedFile = true, bool useDefaultPath = true, - Comparator comparator = const CommandLineComparator(), ApprovalScrubber scrubber = const ScrubNothing(), + Reporter reporter = const CommandLineReporter(), }) { Approvals.verify( content, @@ -38,7 +38,7 @@ class ApprovalTestHelper { approveResult: approveResult, deleteReceivedFile: deleteReceivedFile, useDefaultPath: useDefaultPath, - comparator: comparator, + reporter: reporter, scrubber: scrubber, ), ); @@ -144,7 +144,7 @@ class ApprovalTestHelper { required bool deleteReceivedFile, bool useDefaultPath = true, ApprovalScrubber scrubber = const ScrubNothing(), - Comparator comparator = const CommandLineComparator(), + Reporter reporter = const CommandLineReporter(), }) => Options( namer: useDefaultPath @@ -159,7 +159,7 @@ class ApprovalTestHelper { deleteReceivedFile: deleteReceivedFile, approveResult: approveResult, logErrors: !expectException, - comparator: comparator, + reporter: reporter, scrubber: scrubber, ); }