Skip to content

Commit

Permalink
@ F widget tests, formatted code, some minor changes
Browse files Browse the repository at this point in the history
  • Loading branch information
yelmuratoff committed Jun 29, 2024
1 parent 30c1e1a commit 1c8219d
Show file tree
Hide file tree
Showing 21 changed files with 350 additions and 117 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ generated_*

# Other files
.old.dart
.build
build/

.DS_Store
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 1.1.0

- Breaking release:
- Added new reporter: `GitReporter`. It allows you to use `git` to view the differences between the received and approved files.
- Added support to approve files using CLI. Now you can approve files using the command line: `dart run approved:review`
- Added support to use ApprovalTests during widget tests.
Thanks to [Richard Coutts](https://github.com/buttonsrtoys)

## 1.0.0

- Initial major release.
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ Reporters are the part of Approval Tests that launch diff tools when things do n

There are several reporters available in the package:
- `CommandLineReporter` - This is the default reporter, which will output the diff in the terminal.
- `GitReporter` - This reporter will open the diff in the Git GUI.
- `DiffReporter` - This reporter will open the Diff Tool in your IDE.
- For Diff Reporter I using the default paths to the IDE, if something didn't work then you in the console see the expected correct path to the IDE and specify customDiffInfo. You can also contact me for help.

Expand Down
1 change: 1 addition & 0 deletions lib/approval_tests.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ part 'src/scrubbers/date_scrubber.dart';
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';
48 changes: 48 additions & 0 deletions lib/src/reporters/git.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
part of '../../approval_tests.dart';

/// `GitReporter` is a class for reporting the comparison results using the git.
class GitReporter implements Reporter {
const GitReporter();

@override
Future<void> report(String approvedPath, String receivedPath) async {
const DiffInfo diffInfo =
DiffInfo(name: "Git", command: 'git', arg: 'diff --no-index');

try {
await Future.wait([
_checkFileExists(approvedPath),
_checkFileExists(receivedPath),
]);

await Process.run(
diffInfo.command,
[diffInfo.arg, approvedPath, receivedPath],
);
} catch (e, st) {
if (e is PathNotFoundException) {
ApprovalLogger.exception(e, stackTrace: st);
rethrow;
}
if (e is ProcessException) {
final ProcessResult result =
await Process.run(ApprovalUtils.commandWhere, [diffInfo.command]);
ApprovalLogger.exception(
'Error during comparison via Git. Please make sure that Git is installed and available in the system path. Error: ${e.message}. Git path: ${result.stdout}',
stackTrace: st,
);
}
rethrow;
}
}

Future<void> _checkFileExists(String path) async {
if (!ApprovalUtils.isFileExists(path)) {
throw PathNotFoundException(
path,
const OSError('File not found'),
'From GitReporter: File not found at path: [$path]. Please check the path and try again.',
);
}
}
}
3 changes: 2 additions & 1 deletion lib/src/widget/src/common.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ extension StringApprovedExtension on String {

extension DirectoryApprovedExtension on Directory {
Set<File> filesWithExtension(String extension) {
final fileSystemEntities = listSync().where((entity) => entity is File && entity.path.endsWith(extension));
final fileSystemEntities = listSync()
.where((entity) => entity is File && entity.path.endsWith(extension));
return fileSystemEntities.whereType<File>().toSet();
}
}
21 changes: 15 additions & 6 deletions lib/src/widget/src/get_widget_names.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ Future<Set<String>> getWidgetNames() async {
});
} else {
final libPath = '${Directory.current.absolute.path}/lib';
stdout.write("package:approved: searching for class names in $libPath...");
stdout
.write("package:approved: searching for class names in $libPath...");
extractWidgetNames(libPath).then((widgetsList) {
if (!_widgetNamesDir.existsSync()) {
_widgetNamesDir.createSync();
Expand Down Expand Up @@ -73,12 +74,15 @@ Future<Set<String>> extractWidgetNames(String libPath) async {
final libDirectory = Directory(libPath);
final dartFiles = libDirectory.listSync(recursive: true).where(
(file) =>
file.path.endsWith('.dart') && !file.path.contains('.g.dart') && !file.path.contains('.freezed.dart'),
file.path.endsWith('.dart') &&
!file.path.contains('.g.dart') &&
!file.path.contains('.freezed.dart'),
);

for (final file in dartFiles) {
final analysisSession = analysisContext.currentSession;
final parsedResult = analysisSession.getParsedUnit(file.path) as ParsedUnitResult;
final parsedResult =
analysisSession.getParsedUnit(file.path) as ParsedUnitResult;

for (final compilationUnitMember in parsedResult.unit.declarations) {
if (compilationUnitMember is ClassDeclaration) {
Expand All @@ -105,7 +109,8 @@ Future<String> getFlutterSdkPath() async {
throw Exception('Failed to run flutter command: ${result.stderr}');
}

final jsonData = jsonDecode(result.stdout.toString()) as Map<String, dynamic>;
final jsonData =
jsonDecode(result.stdout.toString()) as Map<String, dynamic>;

completer.complete(jsonData['flutterRoot'].toString());
});
Expand All @@ -121,7 +126,9 @@ Future<Set<String>> readWidgetsFile(String filePath) async {
// Split by lines
final linesList = text.split('\n');
// Remove empty lines and comments
final linesSet = linesList.where((line) => line.isNotEmpty && !line.startsWith('#')).toSet();
final linesSet = linesList
.where((line) => line.isNotEmpty && !line.startsWith('#'))
.toSet();
completer.complete(linesSet);
});

Expand All @@ -135,7 +142,9 @@ Future<bool> isWidgetNamesFileFresh() async {

await findNewestDartFileTimestamp(libDirectory).then((dateTime) {
final widgetNamesFile = File(_widgetNamesPath);
if (dateTime != null && widgetNamesFile.existsSync() && widgetNamesFile.lastModifiedSync().isAfter(dateTime)) {
if (dateTime != null &&
widgetNamesFile.existsSync() &&
widgetNamesFile.lastModifiedSync().isAfter(dateTime)) {
resultCompleter.complete(true);
} else {
resultCompleter.complete(false);
Expand Down
13 changes: 9 additions & 4 deletions lib/src/widget/src/git_diffs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,27 @@ void printGitDiffs(String testDescription, String differences) {

/// return the diff of two files
String gitDiffFiles(File path0, FileSystemEntity path1) {
final processResult = Process.runSync('git', ['diff', '--no-index', path0.path, path1.path]);
final processResult =
Process.runSync('git', ['diff', '--no-index', path0.path, path1.path]);

final stdoutString = processResult.stdout as String;
final stderrString = processResult.stderr as String;

final processString = stdoutString.isNotEmpty || stderrString.isNotEmpty ? stdoutString : '';
final processString =
stdoutString.isNotEmpty || stderrString.isNotEmpty ? stdoutString : '';

return _stripGitDiff(processString);
}

/// Format the git --diff if superfluous text
String _stripGitDiff(String multiLineString) {
bool startsWithAny(String line, List<String> prefixes) => prefixes.any((prefix) => line.startsWith(prefix));
bool startsWithAny(String line, List<String> prefixes) =>
prefixes.any((prefix) => line.startsWith(prefix));

final List<String> lines = multiLineString.split('\n');
final List<String> filteredLines = lines.where((line) => !startsWithAny(line, ['diff', 'index', '@@'])).toList();
final List<String> filteredLines = lines
.where((line) => !startsWithAny(line, ['diff', 'index', '@@']))
.toList();

final String result = filteredLines.join('\n');

Expand Down
104 changes: 34 additions & 70 deletions lib/src/widget/src/src.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,18 @@
import 'dart:async';
import 'dart:io';

import 'package:approval_tests/approval_tests.dart';
import 'package:approval_tests/src/widget/src/common.dart';
import 'package:approval_tests/src/widget/src/get_widget_names.dart';
import 'package:approval_tests/src/widget/src/git_diffs.dart';
import 'package:approval_tests/src/widget/src/widget_meta/collect_widgets_meta_data.dart' as wmd;
import 'package:approval_tests/src/widget/src/widget_meta/collect_widgets_meta_data.dart'
as wmd;
import 'package:flutter_test/flutter_test.dart';

Set<String>? _widgetNames;
const _approvedExtension = 'approved.txt';
const _unapprovedExtension = 'unapproved.txt';
final _executedApprovedFullPaths = <String>{};
bool _allTestsPassed = true;

class Approved {
class ApprovalWidgets {
/// Initializes the approval test by building a database of project classes.
///
/// Typically called from within flutter_tests function 'setUpAll'
Expand All @@ -37,15 +36,23 @@ class Approved {

final testPath = _testFilePath();
final testDirectory = Directory(testPath);
final approvedFullPaths = testDirectory.filesWithExtension('.$_approvedExtension').map((file) => file.path).toSet();
final unapprovedFullPaths =
testDirectory.filesWithExtension('.$_unapprovedExtension').map((file) => file.path).toSet();
final approvedFullPaths = testDirectory
.filesWithExtension('.${Namer.approvedExtension}')
.map((file) => file.path)
.toSet();
final unapprovedFullPaths = testDirectory
.filesWithExtension('.${Namer.receivedExtension}')
.map((file) => file.path)
.toSet();

for (final approvedFullPath in _executedApprovedFullPaths) {
if (approvedFullPaths.contains(approvedFullPath)) {
approvedFullPaths.remove(approvedFullPath);
}
final unapprovedFullPath = approvedFullPath.replaceAll(_approvedExtension, _unapprovedExtension);
final unapprovedFullPath = approvedFullPath.replaceAll(
Namer.approvedExtension,
Namer.receivedExtension,
);
if (unapprovedFullPaths.contains(unapprovedFullPath)) {
unapprovedFullPaths.remove(unapprovedFullPath);
}
Expand Down Expand Up @@ -75,62 +82,11 @@ topBar
static Set<String>? get widgetNames => _widgetNames;
}

Future<void> approvalTest(
String testDescription,
String textForReview,
) async {
try {
final String outputPath = _testFilePath();

final approvedFullPath = '$outputPath/$testDescription.$_approvedExtension';
final currentFullPath = '$outputPath/$testDescription.$_unapprovedExtension';
String approvedText = '<No approved text yet>';

if (_executedApprovedFullPaths.contains(approvedFullPath)) {
_allTestsPassed = false;
throw Exception('''
$topBar
An approvalTest with description '$testDescription' was already created in path '$outputPath'.
Try adding a unique description to approvedTest. E.g.,
await tester.approvalTest('my unique description');
$bottomBar''');
}

_executedApprovedFullPaths.add(approvedFullPath);

final approvedFile = File(approvedFullPath);
final currentFile = File(currentFullPath);

if (approvedFile.existsSync()) {
approvedText = approvedFile.readAsStringSync();
} else {
approvedFile.writeAsStringSync(approvedText.endWithNewline);
}

if (approvedText == textForReview.endWithNewline) {
if (currentFile.existsSync()) {
currentFile.deleteSync();
}
} else {
currentFile.writeAsStringSync(textForReview.endWithNewline);

final differences = gitDiffFiles(approvedFile, currentFile);

if (differences.isNotEmpty) {
_allTestsPassed = false;
printGitDiffs(testDescription, differences);
throw Exception("Approval test '$testDescription' failed. The file diff is listed above.");
}
}
} catch (e) {
print(e.toString());
rethrow;
}
}

/// [_globalApprovalTest] resolves the name conflict with [WidgetTester.approvalTest]
Future<void> Function(String, String) _globalApprovalTest = approvalTest;
Future<void> Function(String, String) _globalApprovalTest =
(description, value) async {
Approvals.verify(value);
};

extension WidgetTesterApprovedExtension on WidgetTester {
/// Returns the meta data for the widgets for comparison during the approval test
Expand All @@ -151,7 +107,7 @@ $bottomBar''');
this,
outputMeta: true,
verbose: false,
widgetNames: Approved.widgetNames,
widgetNames: ApprovalWidgets.widgetNames,
)
.then((stringList) {
completer.complete(stringList.join('\n'));
Expand All @@ -164,10 +120,14 @@ $bottomBar''');
///
/// [description] is the name of the test. It is appended to the description in [Tester].
/// [textForReview] is the meta data text used in the approval test.
Future<void> approvalTest([String? description, String? textForReview]) async {
Future<void> approvalTest([
String? description,
String? textForReview,
]) async {
final resultCompleter = Completer<void>();
final widgetsMetaCompleter = Completer<String>();
final String updatedTestDescription = description == null ? testDescription : '$testDescription $description';
final String updatedTestDescription =
description == null ? testDescription : '$testDescription $description';

// Get the test path before the stack gets too deep.
_testFilePath();
Expand All @@ -181,7 +141,8 @@ $bottomBar''');
widgetsMetaCompleter.complete(textForReview);
}
await widgetsMetaCompleter.future.then((value) {
resultCompleter.complete(_globalApprovalTest(updatedTestDescription, value));
resultCompleter
.complete(_globalApprovalTest(updatedTestDescription, value));
});
return resultCompleter.future;
}
Expand All @@ -204,13 +165,16 @@ String _testFilePath() {

final stackTrace = StackTrace.current;
final lines = stackTrace.toString().split('\n');
final pathLine = lines.firstWhere((line) => line.contains('_test.dart'), orElse: () => '');
final pathLine =
lines.firstWhere((line) => line.contains('_test.dart'), orElse: () => '');

if (pathLine.isNotEmpty) {
final match = RegExp(r'\(file:\/\/(.*\/)').firstMatch(pathLine);
if (match != null && match.groupCount > 0) {
result = Uri.parse(match.group(1)!).toFilePath();
result = result.endsWith('/') ? result.substring(0, result.length - 1) : result;
result = result.endsWith('/')
? result.substring(0, result.length - 1)
: result;
}
}

Expand Down
Loading

0 comments on commit 1c8219d

Please sign in to comment.