diff --git a/README.md b/README.md index 39b6129..0ab8277 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,22 @@ After running the command, the files will be analyzed and you will be asked to c - `n` - Reject the received file. - `v`iew - View the differences between the received and approved files. After selecting `v` you will be asked which IDE you want to use to view the differences. +The command `dart run approval_tests:review` has additional options, including listing files, selecting + files to review from this list by index, and more. For its current capabilities, run + ```bash + dart run approval_tests:review --help + ``` + Typing 'dart run approval_tests:review' is tedious! To reduce your typing, alias the command in your + `.zshrc` or `.bashrc` file + ``` + alias review='dart run approval_tests:review' + ``` + or PowerShell profile + ```shell + function review { + dart run approval_tests:review + } + ``` #### • Via approveResult property If you want the result to be automatically saved after running the test, you need to use the `approveResult` property in `Options`: diff --git a/examples/flutter_example/test/approved/class_names.txt b/examples/flutter_example/test/.approval_tests/class_names.txt similarity index 100% rename from examples/flutter_example/test/approved/class_names.txt rename to examples/flutter_example/test/.approval_tests/class_names.txt diff --git a/examples/flutter_example/test/.approval_tests/received_files.txt b/examples/flutter_example/test/.approval_tests/received_files.txt new file mode 100644 index 0000000..36485e6 --- /dev/null +++ b/examples/flutter_example/test/.approval_tests/received_files.txt @@ -0,0 +1,2 @@ +/Users/yelamanyelmuratov/Development/approvals/ApprovalTests.Dart/examples/flutter_example/test/widget_test.smoke_test.should_display_1.received.txt +/Users/yelamanyelmuratov/Development/approvals/ApprovalTests.Dart/examples/flutter_example/test/widget_test.smoke_test.should_display_0.received.txt \ No newline at end of file diff --git a/examples/flutter_example/test/widget_test.dart b/examples/flutter_example/test/widget_test.dart index ff2987b..4815f2b 100644 --- a/examples/flutter_example/test/widget_test.dart +++ b/examples/flutter_example/test/widget_test.dart @@ -13,14 +13,17 @@ void main() { await tester.approvalTest( description: 'should display 0', + options: const Options(deleteReceivedFile: false), ); + await tester.tap(find.byType(FloatingActionButton)); await tester.tap(find.byType(FloatingActionButton)); await tester.tap(find.byType(FloatingActionButton)); await tester.pumpAndSettle(); await tester.approvalTest( description: 'should display 1', + options: const Options(deleteReceivedFile: false), ); }); }); diff --git a/examples/flutter_example/test/widget_test.smoke_test.should_display_0.approved.txt b/examples/flutter_example/test/widget_test.smoke_test.should_display_0.approved.txt index deb8bb5..7fc68ac 100644 --- a/examples/flutter_example/test/widget_test.smoke_test.should_display_0.approved.txt +++ b/examples/flutter_example/test/widget_test.smoke_test.should_display_0.approved.txt @@ -2,6 +2,6 @@ MyApp: {count: 1} MyHomePage: {count: 1} -Text: {text: 'You have pushed the button this many times:', count: 1} -Text: {key: 'Counter', text: '0', count: 1} -Text: {text: 'Approved Example', count: 1} \ No newline at end of file +Text: {data: 'You have pushed the button this many times:', count: 1} +Text: {key: 'Counter', data: '0', count: 1} +Text: {data: 'Approved Example', count: 1} \ No newline at end of file diff --git a/examples/flutter_example/test/widget_test.smoke_test.should_display_1.approved.txt b/examples/flutter_example/test/widget_test.smoke_test.should_display_1.approved.txt index eccd4eb..7e3ec86 100644 --- a/examples/flutter_example/test/widget_test.smoke_test.should_display_1.approved.txt +++ b/examples/flutter_example/test/widget_test.smoke_test.should_display_1.approved.txt @@ -1,4 +1,4 @@ # This file was generated by approval_tests. Please do not edit. -Text: {key: 'Counter', text: '2', count: 1} -Text: {key: 'Counter', text: '0', count: 0} \ No newline at end of file +Text: {key: 'Counter', data: '3', count: 1} +Text: {key: 'Counter', data: '0', count: 0} \ No newline at end of file diff --git a/packages/approval_tests/bin/review.dart b/packages/approval_tests/bin/review.dart index 903e8bb..ed5dfa3 100644 --- a/packages/approval_tests/bin/review.dart +++ b/packages/approval_tests/bin/review.dart @@ -1,46 +1,171 @@ -// ignore_for_file: avoid_print - import 'dart:io'; import 'package:approval_tests/approval_tests.dart'; -void main() async { - final searchDirectory = Directory.current; - +void main(List args) async { final List> tasks = []; + bool isProcessingTasks = false; + + void processUnapprovedFile(File receivedFile) { + if (!receivedFile.existsSync()) { + ApprovalLogger.exception( + 'Error: the file below does not exist for review comparison:', + ); + ApprovalLogger.exception(receivedFile.path); + return; + } + + final approvedFileName = + receivedFile.path.replaceAll('.received.txt', '.approved.txt'); + final approvedFile = File(approvedFileName); + + if (approvedFile.existsSync()) { + tasks.add(processFile(approvedFile, receivedFile)); + } else { + tasks.add(processFile(null, receivedFile)); + } + } - /// Recursively search for current files - await for (final file in searchDirectory.list(recursive: true)) { - if (file.path.endsWith('.received.txt')) { - final reviewFile = file; - final approvedFileName = - file.path.replaceAll('.received.txt', '.approved.txt'); - final approvedFile = File(approvedFileName); + Future> getUnapprovedFiles() async { + final files = []; + final searchDirectory = Directory.current; - if (approvedFile.existsSync()) { - tasks.add(processFile(approvedFile, reviewFile)); + await for (final file in searchDirectory.list(recursive: true)) { + if (file.path.endsWith('.received.txt')) { + files.add(file as File); } } + + return files; } - await Future.wait(tasks); + /// If no args, then searching the whole project + if (args.isEmpty || args[0].isEmpty) { + for (final file in await getUnapprovedFiles()) { + if (file.path.endsWith('.received.txt')) { + isProcessingTasks = true; + processUnapprovedFile(file); + } + } + } else { + /// If here, have args. If arg is an option... + if (args[0][0] == '-') { + if (args[0] == '--help') { + ApprovalLogger.log(''' +Manage your package:approval_tests files. + +Common usage: + + dart run approval_tests:review + Reviews all project .received.txt files + + dart run approval_tests:review --list + List project's .received.txt files - ApprovalLogger.success( - 'Review process completed: ${tasks.length} files reviewed.', - ); +Usage: dart run approval_tests:review [arguments] + +Arguments: +--help Print this usage information. +--list Print a list of project .received.txt files. + Review an .received.txt file indexed by --list. + Review an .received.txt file.'''); + } else if (args[0] == '--list') { + final unapprovedFiles = await getUnapprovedFiles(); + final fileCount = unapprovedFiles.length; + for (int i = 0; i < fileCount; i++) { + ApprovalLogger.log( + '${i.toString().padLeft(3)} ${unapprovedFiles[i].path}', + ); + } + ApprovalLogger.log('Found $fileCount received files.'); + if (fileCount > 0) { + ApprovalLogger.log( + "\nTo review one, run: dart run approval_tests:review \nTo review all, run: dart run approval_tests:review", + ); + } + + writeUnapprovedFiles(unapprovedFiles); + } else { + ApprovalLogger.log( + "Unknown option '${args[0]}'. See '--help' for more details.", + ); + } + } else { + /// If here, arg is a path or an index in the list of paths + File? unapprovedFile; + final arg = args[0]; + final int? maybeIntValue = int.tryParse(arg); + if (maybeIntValue == null) { + unapprovedFile = File(arg); + } else { + final unapprovedFilePaths = readReceivedFiles(); + if (maybeIntValue >= 0 && maybeIntValue < unapprovedFilePaths.length) { + unapprovedFile = File(unapprovedFilePaths[maybeIntValue]); + } else { + ApprovalLogger.log( + 'No received file with an index of $maybeIntValue', + ); + } + } + if (unapprovedFile != null) { + isProcessingTasks = true; + processUnapprovedFile(unapprovedFile); + } + } + } + + if (isProcessingTasks) { + if (tasks.isEmpty) { + ApprovalLogger.exception('No received test results to review!'); + } else { + final tasksCount = tasks.length; + await Future.wait(tasks); + ApprovalLogger.success( + 'Review completed. $tasksCount test results reviewed.', + ); + } + } } -/// Check of the files are different using "git diff" -Future processFile(File approvedFile, FileSystemEntity reviewFile) async { - final resultString = GitReporter.gitDiffFiles(approvedFile, reviewFile); +void writeUnapprovedFiles(List? unapprovedFiles) { + final file = File(ApprovalTestsConstants.receivedFilesPath) + ..createSync(recursive: true); + if (unapprovedFiles == null) { + file.writeAsStringSync(''); + } else { + file.writeAsStringSync(unapprovedFiles.map((file) => file.path).join('\n')); + } +} + +List readReceivedFiles() { + List result = []; + final file = File(ApprovalTestsConstants.receivedFilesPath); + if (file.existsSync()) { + final String fileContents = file.readAsStringSync(); + result = fileContents.split('\n'); + } else { + result = []; + } + + return result; +} + +/// Check of the files are different using "git diff" +Future processFile(File? approvedFile, File receivedFile) async { + late String resultString; ComparatorIDE comparatorIDE = ComparatorIDE.vsCode; - if (resultString.isNotEmpty || resultString.isNotEmpty) { - // final String fileNameWithoutExtension = - // approvedFile.path.split('/').last.split('.').first; - // GitReporter.printGitDiffs(fileNameWithoutExtension, resultString); - const CommandLineReporter().report(approvedFile.path, reviewFile.path); + if (approvedFile == null) { + final unapprovedText = receivedFile.readAsStringSync(); + resultString = "Data in '${receivedFile.path}':\n$unapprovedText"; + } else { + final gitDiff = GitReporter.gitDiffFiles(approvedFile, receivedFile); + resultString = gitDiff; + } + + if (resultString.isNotEmpty) { + GitReporter.printGitDiffs(receivedFile.path, resultString, showTip: false); String? firstCharacter; @@ -48,15 +173,18 @@ Future processFile(File approvedFile, FileSystemEntity reviewFile) async { stdout.write('Accept changes? (y/N/[v]iew): '); final response = stdin.readLineSync()?.trim().toLowerCase(); - if (response == null || response.isEmpty) { - firstCharacter = null; - } else { + firstCharacter = null; + if (response != null && response.isNotEmpty) { firstCharacter = response[0]; } + final receivedFilename = receivedFile.path; + final approvedFilename = receivedFile.path + .replaceAll(Namer.receivedExtension, Namer.approvedExtension); + if (firstCharacter == 'y') { - await approvedFile.delete(); - await reviewFile.rename(approvedFile.path); + await approvedFile?.delete(); + await receivedFile.rename(approvedFilename); ApprovalLogger.success('Approval test approved'); } else if (firstCharacter == 'v') { stdout.write('Enter diff tool (code, studio): '); @@ -68,12 +196,19 @@ Future processFile(File approvedFile, FileSystemEntity reviewFile) async { } final diffReporter = DiffReporter(ide: comparatorIDE); if (diffReporter.isReporterAvailable) { - final approvedFilename = approvedFile.path; - final reviewFilename = reviewFile.path; + final approvedFilename = approvedFile?.path; + ApprovalLogger.log( - "Executing '${diffReporter.defaultDiffInfo.command} ${diffReporter.defaultDiffInfo.arg} $approvedFilename $reviewFilename'", + "Executing '${diffReporter.defaultDiffInfo.command} ${diffReporter.defaultDiffInfo.arg} $approvedFilename $receivedFilename'", ); - await diffReporter.report(approvedFilename, reviewFilename); + if (approvedFilename != null) { + await diffReporter.report(approvedFilename, receivedFilename); + } else { + final processResult = Process.runSync('code', [receivedFilename]); + ApprovalLogger.log( + '______processResult: ${processResult.toString()}', + ); + } } else { ApprovalLogger.warning( 'No diff tool available: please check:\nName: ${diffReporter.defaultDiffInfo.name}\nPath:${diffReporter.defaultDiffInfo.command}', @@ -83,5 +218,13 @@ Future processFile(File approvedFile, FileSystemEntity reviewFile) async { ApprovalLogger.exception('Approval test rejected'); } } while (firstCharacter == 'v'); + } else { + ApprovalLogger.success('No differences found. Approval test approved.'); } } + +bool isCodeCommandAvailable() { + final result = Process.runSync('which', ['code']); + + return result.exitCode == 0; +} diff --git a/packages/approval_tests/lib/approval_tests.dart b/packages/approval_tests/lib/approval_tests.dart index ae00949..5c8e195 100644 --- a/packages/approval_tests/lib/approval_tests.dart +++ b/packages/approval_tests/lib/approval_tests.dart @@ -20,7 +20,6 @@ import 'dart:convert'; import 'dart:io'; import 'dart:math'; -import 'package:approval_tests/src/core/constants/constants.dart'; import 'package:approval_tests/src/core/enums/file_type.dart'; import 'package:diff_match_patch2/diff_match_patch.dart'; import 'package:talker/talker.dart'; @@ -58,3 +57,4 @@ part 'src/scrubbers/nothing_scrubber.dart'; part 'src/scrubbers/reg_exp_scrubber.dart'; part 'src/writers/approval_text_writer.dart'; part 'src/reporters/git.dart'; +part 'src/core/constants/constants.dart'; diff --git a/packages/approval_tests/lib/src/core/constants/constants.dart b/packages/approval_tests/lib/src/core/constants/constants.dart index e001b45..075bddc 100644 --- a/packages/approval_tests/lib/src/core/constants/constants.dart +++ b/packages/approval_tests/lib/src/core/constants/constants.dart @@ -1,3 +1,5 @@ +part of '../../../approval_tests.dart'; + final class ApprovalTestsConstants { static const String widgetHeader = ''' # This file was autogenerated by package:approval_tests_flutter. Please do not edit. @@ -5,4 +7,7 @@ final class ApprovalTestsConstants { static const String baseHeader = ''' # This file was generated by approval_tests. Please do not edit.\n'''; + + static const resourceLocalPath = './test/.approval_tests'; + static const receivedFilesPath = '$resourceLocalPath/received_files.txt'; } diff --git a/packages/approval_tests/lib/src/reporters/git.dart b/packages/approval_tests/lib/src/reporters/git.dart index 99e5b94..d8491ed 100644 --- a/packages/approval_tests/lib/src/reporters/git.dart +++ b/packages/approval_tests/lib/src/reporters/git.dart @@ -83,4 +83,18 @@ class GitReporter implements Reporter { // ); // ApprovalLogger.log(differences.trim()); // } + + static void printGitDiffs( + String unapprovedFullPath, + String differences, { + bool showTip = true, + }) { + ApprovalLogger.log("Results of git diff:\n${differences.trim()}"); + if (showTip) { + ApprovalLogger.log( + "To review, run: dart run approved:review '$unapprovedFullPath'", + ); + ApprovalLogger.log("To review all, run: dart run approved:review"); + } + } } diff --git a/packages/approval_tests_flutter/lib/src/get_widget_names.dart b/packages/approval_tests_flutter/lib/src/get_widget_names.dart index 2ab76e6..d298a88 100644 --- a/packages/approval_tests_flutter/lib/src/get_widget_names.dart +++ b/packages/approval_tests_flutter/lib/src/get_widget_names.dart @@ -6,9 +6,10 @@ import 'package:analyzer/dart/analysis/context_builder.dart'; import 'package:analyzer/dart/analysis/context_locator.dart'; import 'package:analyzer/dart/analysis/results.dart'; import 'package:analyzer/dart/ast/ast.dart'; +import 'package:approval_tests/approval_tests.dart'; import 'package:approval_tests_flutter/src/common.dart'; -final _widgetNamesDir = Directory('./test/approved'); +final _widgetNamesDir = Directory(ApprovalTestsConstants.resourceLocalPath); final _widgetNamesPath = '${_widgetNamesDir.path}/class_names.txt'; Future> getWidgetNames() async { diff --git a/packages/approval_tests_flutter/lib/src/widget_meta/collect_widgets_meta_data.dart b/packages/approval_tests_flutter/lib/src/widget_meta/collect_widgets_meta_data.dart index 829353a..fabbc24 100644 --- a/packages/approval_tests_flutter/lib/src/widget_meta/collect_widgets_meta_data.dart +++ b/packages/approval_tests_flutter/lib/src/widget_meta/collect_widgets_meta_data.dart @@ -431,7 +431,7 @@ List _generateExpectWidgets( buffer.write("intl: (s) => s.$intlPlaceHolder"); } } else { - buffer.write("text: '${widgetMeta.widgetText}'"); + buffer.write("data: '${widgetMeta.widgetText}'"); } } @@ -514,7 +514,7 @@ String _generateWidgetMeta( void addTextAttributeToBuffer() { addCommaIfNecessary(); - buffer.write("text: '${widgetMeta.widgetText}'"); + buffer.write("data: '${widgetMeta.widgetText}'"); } void addKeyAttributeToBuffer() { diff --git a/packages/approval_tests_flutter/pubspec.yaml b/packages/approval_tests_flutter/pubspec.yaml index 41d9984..c4eb2d9 100644 --- a/packages/approval_tests_flutter/pubspec.yaml +++ b/packages/approval_tests_flutter/pubspec.yaml @@ -4,6 +4,7 @@ version: 1.1.1 repository: https://github.com/approvals/ApprovalTests.Dart homepage: https://github.com/approvals/ApprovalTests.Dart.git issue_tracker: https://github.com/approvals/ApprovalTests.Dart/issues +publish_to: 'none' environment: sdk: '>=3.0.0 <4.0.0' @@ -17,6 +18,7 @@ dependencies: flutter_test: sdk: flutter approval_tests: ^1.1.0 + # path: ../approval_tests test_api: ^0.7.0 test: ^1.25.2 analyzer: ^6.4.1