From 6ce5d9ab92c67be242604fa764d911b03c312d58 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Thu, 18 May 2023 12:11:34 +0100 Subject: [PATCH 01/45] feat: added logic --- .../completion_command_runner.dart | 9 +++- .../installer/completion_configuration.dart | 49 +++++++++++++++++++ .../installer/completion_installation.dart | 33 ++++++++++++- 3 files changed, 89 insertions(+), 2 deletions(-) diff --git a/lib/src/command_runner/completion_command_runner.dart b/lib/src/command_runner/completion_command_runner.dart index cd7a860..bb313b9 100644 --- a/lib/src/command_runner/completion_command_runner.dart +++ b/lib/src/command_runner/completion_command_runner.dart @@ -73,8 +73,14 @@ abstract class CompletionCommandRunner extends CommandRunner { InstallCompletionFilesCommand.commandName, ]; + final completionConfiguration = CompletionConfiguration.fromFile( + completionInstallation.completionConfigurationFile, + ); if (enableAutoInstall && - !reservedCommands.contains(topLevelResults.command?.name)) { + !reservedCommands.contains(topLevelResults.command?.name) && + systemShell != null && + completionConfiguration.uninstalls + .contains(command: executableName, systemShell: systemShell!)) { // When auto installing, use error level to display messages. tryInstallCompletionFiles(Level.error); } @@ -87,6 +93,7 @@ abstract class CompletionCommandRunner extends CommandRunner { void tryInstallCompletionFiles(Level level) { try { completionInstallationLogger.level = level; + completionInstallation.install(executableName); } on CompletionInstallationException catch (e) { completionInstallationLogger.warn(e.toString()); diff --git a/lib/src/installer/completion_configuration.dart b/lib/src/installer/completion_configuration.dart index 7a10957..d803a1b 100644 --- a/lib/src/installer/completion_configuration.dart +++ b/lib/src/installer/completion_configuration.dart @@ -137,3 +137,52 @@ String _jsonEncodeUninstalls(Uninstalls uninstalls) { entry.key.toString(): entry.value.toList(), }); } + +/// Provides convinience methods for [Uninstalls]. +extension UninstallsExtension on Uninstalls { + /// Returns a new [Uninstalls] with the given [command] added to + /// [systemShell]. + Uninstalls include( + {required String command, required SystemShell systemShell}) { + final modifiable = _modifiable(); + + if (modifiable.containsKey(systemShell)) { + modifiable[systemShell]!.add(command); + } else { + modifiable[systemShell] = {command}; + } + + return UnmodifiableMapView( + modifiable.map((key, value) => MapEntry(key, UnmodifiableSetView(value))), + ); + } + + /// Returns a new [Uninstalls] with the given [command] removed from + /// [systemShell]. + Uninstalls exclude({ + required String command, + required SystemShell systemShell, + }) { + final modifiable = _modifiable(); + + if (modifiable.containsKey(systemShell)) { + modifiable[systemShell]!.remove(command); + } + + return UnmodifiableMapView( + modifiable.map((key, value) => MapEntry(key, UnmodifiableSetView(value))), + ); + } + + /// Whether the [command] is contained in [systemShell]. + bool contains({required String command, required SystemShell systemShell}) { + if (containsKey(systemShell)) { + return this[systemShell]!.contains(command); + } + return false; + } + + Map> _modifiable() { + return map((key, value) => MapEntry(key, value.toSet())); + } +} diff --git a/lib/src/installer/completion_installation.dart b/lib/src/installer/completion_installation.dart index 9904280..ccab872 100644 --- a/lib/src/installer/completion_installation.dart +++ b/lib/src/installer/completion_installation.dart @@ -90,6 +90,11 @@ class CompletionInstallation { } } + /// Define the [File] in which the completion configuration is stored. + File get completionConfigurationFile { + return File(path.join(completionConfigDir.path, 'config.json')); + } + /// Install completion configuration files for a [rootCommand] in the /// current shell. /// @@ -124,6 +129,17 @@ class CompletionInstallation { if (completionFileCreated) { _logSourceInstructions(rootCommand); } + + final completionConfiguration = + CompletionConfiguration.fromFile(completionConfigurationFile); + completionConfiguration + .copyWith( + uninstalls: completionConfiguration.uninstalls.exclude( + command: rootCommand, + systemShell: configuration.shell, + ), + ) + .writeTo(completionConfigurationFile); } /// Create a directory in which the completion config files shall be saved. @@ -379,8 +395,23 @@ ${configuration!.sourceLineTemplate(scriptPath)}'''; completionEntry.removeFrom(shellRCFile); } - if (completionConfigDir.listSync().isEmpty) { + final completionConfigDirContent = completionConfigDir.listSync(); + final onlyHasConfigurationFile = completionConfigDirContent.length == 1 && + completionConfigDirContent.first.path == + shellCompletionConfigurationFile.path; + if (completionConfigDirContent.isEmpty || onlyHasConfigurationFile) { completionConfigDir.deleteSync(); + } else { + final completionConfiguration = + CompletionConfiguration.fromFile(completionConfigurationFile); + completionConfiguration + .copyWith( + uninstalls: completionConfiguration.uninstalls.include( + command: rootCommand, + systemShell: configuration.shell, + ), + ) + .writeTo(completionConfigurationFile); } } } From e4ae10c979e277b1daf1de2662c6b374428ef831 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Thu, 18 May 2023 12:13:19 +0100 Subject: [PATCH 02/45] refactor: removed empty line --- lib/src/command_runner/completion_command_runner.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/command_runner/completion_command_runner.dart b/lib/src/command_runner/completion_command_runner.dart index bb313b9..4f6c514 100644 --- a/lib/src/command_runner/completion_command_runner.dart +++ b/lib/src/command_runner/completion_command_runner.dart @@ -93,7 +93,6 @@ abstract class CompletionCommandRunner extends CommandRunner { void tryInstallCompletionFiles(Level level) { try { completionInstallationLogger.level = level; - completionInstallation.install(executableName); } on CompletionInstallationException catch (e) { completionInstallationLogger.warn(e.toString()); From d9e110050af5dd18bb34a6854acb5de4c1e77cdd Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Thu, 18 May 2023 12:14:15 +0100 Subject: [PATCH 03/45] refactor: formatting --- lib/src/installer/completion_configuration.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/src/installer/completion_configuration.dart b/lib/src/installer/completion_configuration.dart index d803a1b..33e116a 100644 --- a/lib/src/installer/completion_configuration.dart +++ b/lib/src/installer/completion_configuration.dart @@ -142,8 +142,10 @@ String _jsonEncodeUninstalls(Uninstalls uninstalls) { extension UninstallsExtension on Uninstalls { /// Returns a new [Uninstalls] with the given [command] added to /// [systemShell]. - Uninstalls include( - {required String command, required SystemShell systemShell}) { + Uninstalls include({ + required String command, + required SystemShell systemShell, + }) { final modifiable = _modifiable(); if (modifiable.containsKey(systemShell)) { From 677f8971518d6ba17d332e575652fedeb97a9d96 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Thu, 18 May 2023 12:18:08 +0100 Subject: [PATCH 04/45] refactor: refined autoinstallation logic --- lib/src/command_runner/completion_command_runner.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/src/command_runner/completion_command_runner.dart b/lib/src/command_runner/completion_command_runner.dart index 4f6c514..134692b 100644 --- a/lib/src/command_runner/completion_command_runner.dart +++ b/lib/src/command_runner/completion_command_runner.dart @@ -76,11 +76,12 @@ abstract class CompletionCommandRunner extends CommandRunner { final completionConfiguration = CompletionConfiguration.fromFile( completionInstallation.completionConfigurationFile, ); + final isUninstalled = systemShell != null && + completionConfiguration.uninstalls + .contains(command: executableName, systemShell: systemShell!); if (enableAutoInstall && !reservedCommands.contains(topLevelResults.command?.name) && - systemShell != null && - completionConfiguration.uninstalls - .contains(command: executableName, systemShell: systemShell!)) { + !isUninstalled) { // When auto installing, use error level to display messages. tryInstallCompletionFiles(Level.error); } From 0d41fdab70fa6c75d6c4be32b0b720d9e10221bf Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Thu, 18 May 2023 12:29:54 +0100 Subject: [PATCH 05/45] fix: resolved uninstallation directory removal logic --- lib/src/installer/completion_installation.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/src/installer/completion_installation.dart b/lib/src/installer/completion_installation.dart index ccab872..425f290 100644 --- a/lib/src/installer/completion_installation.dart +++ b/lib/src/installer/completion_installation.dart @@ -394,13 +394,12 @@ ${configuration!.sourceLineTemplate(scriptPath)}'''; if (!shellCompletionConfigurationFile.existsSync()) { completionEntry.removeFrom(shellRCFile); } - final completionConfigDirContent = completionConfigDir.listSync(); final onlyHasConfigurationFile = completionConfigDirContent.length == 1 && - completionConfigDirContent.first.path == - shellCompletionConfigurationFile.path; + path.absolute(completionConfigDirContent.first.path) == + path.absolute(completionConfigurationFile.path); if (completionConfigDirContent.isEmpty || onlyHasConfigurationFile) { - completionConfigDir.deleteSync(); + completionConfigDir.deleteSync(recursive: true); } else { final completionConfiguration = CompletionConfiguration.fromFile(completionConfigurationFile); From 1f3cd354ea329e57254a331103ed04e3466ef08c Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Thu, 18 May 2023 12:33:15 +0100 Subject: [PATCH 06/45] test: added 'config.json' to files --- test/src/installer/completion_installation_test.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/src/installer/completion_installation_test.dart b/test/src/installer/completion_installation_test.dart index 60a3def..0180358 100644 --- a/test/src/installer/completion_installation_test.dart +++ b/test/src/installer/completion_installation_test.dart @@ -426,6 +426,7 @@ void main() { 'not_good.zsh', 'very_good.zsh', 'zsh-config.zsh', + 'config.json', ]), ); @@ -462,7 +463,8 @@ void main() { 'very_good.bash', 'very_good.zsh', 'zsh-config.zsh', - 'bash-config.bash' + 'bash-config.bash', + 'config.json', ]), ); }, From 08e2b72a95d622dfc87222885c858f0f09792fd2 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Thu, 18 May 2023 12:44:51 +0100 Subject: [PATCH 07/45] test: fixed unmocked tests --- ...install_completion_files_command_test.dart | 15 +++++--- .../completion_command_runner_test.dart | 35 +++++++++++++++---- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/test/src/command_runner/commands/install_completion_files_command_test.dart b/test/src/command_runner/commands/install_completion_files_command_test.dart index 399a0b5..66231c3 100644 --- a/test/src/command_runner/commands/install_completion_files_command_test.dart +++ b/test/src/command_runner/commands/install_completion_files_command_test.dart @@ -1,12 +1,14 @@ +import 'dart:io'; + import 'package:cli_completion/cli_completion.dart'; import 'package:cli_completion/installer.dart'; import 'package:mason_logger/mason_logger.dart'; import 'package:mocktail/mocktail.dart'; import 'package:test/test.dart'; -class MockLogger extends Mock implements Logger {} +class _MockLogger extends Mock implements Logger {} -class MockCompletionInstallation extends Mock +class _MockCompletionInstallation extends Mock implements CompletionInstallation {} class _TestCompletionCommandRunner extends CompletionCommandRunner { @@ -14,11 +16,11 @@ class _TestCompletionCommandRunner extends CompletionCommandRunner { @override // ignore: overridden_fields - final Logger completionInstallationLogger = MockLogger(); + final Logger completionInstallationLogger = _MockLogger(); @override final CompletionInstallation completionInstallation = - MockCompletionInstallation(); + _MockCompletionInstallation(); } void main() { @@ -27,6 +29,11 @@ void main() { setUp(() { commandRunner = _TestCompletionCommandRunner(); + + final completionInstallation = commandRunner.completionInstallation; + final completionInstallationFile = File('test-config.json'); + when(() => completionInstallation.completionConfigurationFile) + .thenReturn(completionInstallationFile); }); test('can be instantiated', () { diff --git a/test/src/command_runner/completion_command_runner_test.dart b/test/src/command_runner/completion_command_runner_test.dart index eb65636..884a73b 100644 --- a/test/src/command_runner/completion_command_runner_test.dart +++ b/test/src/command_runner/completion_command_runner_test.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:args/command_runner.dart'; import 'package:cli_completion/cli_completion.dart'; import 'package:cli_completion/installer.dart'; @@ -8,7 +10,7 @@ import 'package:test/test.dart'; class MockLogger extends Mock implements Logger {} -class MockCompletionInstallation extends Mock +class _MockCompletionInstallation extends Mock implements CompletionInstallation {} class _TestCompletionCommandRunner extends CompletionCommandRunner { @@ -114,9 +116,14 @@ void main() { group('auto install', () { test('Tries to install completion files on test subcommand', () async { + final completionInstallation = _MockCompletionInstallation(); + final completionInstallationFile = File('test-config.json'); + when(() => completionInstallation.completionConfigurationFile) + .thenReturn(completionInstallationFile); + final commandRunner = _TestCompletionCommandRunner() ..addCommand(_TestUserCommand()) - ..mockCompletionInstallation = MockCompletionInstallation(); + ..mockCompletionInstallation = completionInstallation; await commandRunner.run(['ahoy']); @@ -129,10 +136,15 @@ void main() { }); test('does not auto install when it is disabled', () async { + final completionInstallation = _MockCompletionInstallation(); + final completionInstallationFile = File('test-config.json'); + when(() => completionInstallation.completionConfigurationFile) + .thenReturn(completionInstallationFile); + final commandRunner = _TestCompletionCommandRunner() ..enableAutoInstall = false ..addCommand(_TestUserCommand()) - ..mockCompletionInstallation = MockCompletionInstallation(); + ..mockCompletionInstallation = completionInstallation; await commandRunner.run(['ahoy']); @@ -147,9 +159,13 @@ void main() { test( 'When it throws CompletionInstallationException, it logs as a warning', () async { + final completionInstallation = _MockCompletionInstallation(); + final completionInstallationFile = File('test-config.json'); + when(() => completionInstallation.completionConfigurationFile) + .thenReturn(completionInstallationFile); final commandRunner = _TestCompletionCommandRunner() ..addCommand(_TestUserCommand()) - ..mockCompletionInstallation = MockCompletionInstallation(); + ..mockCompletionInstallation = completionInstallation; when( () => commandRunner.completionInstallation.install('test'), @@ -166,9 +182,14 @@ void main() { test('When an unknown exception happens during a install, it logs as error', () async { + final completionInstallation = _MockCompletionInstallation(); + final completionInstallationFile = File('test-config.json'); + when(() => completionInstallation.completionConfigurationFile) + .thenReturn(completionInstallationFile); + final commandRunner = _TestCompletionCommandRunner() ..addCommand(_TestUserCommand()) - ..mockCompletionInstallation = MockCompletionInstallation(); + ..mockCompletionInstallation = completionInstallation; when( () => commandRunner.completionInstallation.install('test'), @@ -185,7 +206,7 @@ void main() { 'logs a warning wen it throws $CompletionUninstallationException', () async { final commandRunner = _TestCompletionCommandRunner() - ..mockCompletionInstallation = MockCompletionInstallation(); + ..mockCompletionInstallation = _MockCompletionInstallation(); when( () => commandRunner.completionInstallation.uninstall('test'), @@ -207,7 +228,7 @@ void main() { 'logs an error when an unknown exception happens during a install', () async { final commandRunner = _TestCompletionCommandRunner() - ..mockCompletionInstallation = MockCompletionInstallation(); + ..mockCompletionInstallation = _MockCompletionInstallation(); when( () => commandRunner.completionInstallation.uninstall('test'), From 63487507fdc80e88082024a61236bea08b368f23 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Thu, 18 May 2023 12:46:55 +0100 Subject: [PATCH 08/45] test: added unmocked instances --- .../commands/uninstall_completion_files_command_test.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/src/command_runner/commands/uninstall_completion_files_command_test.dart b/test/src/command_runner/commands/uninstall_completion_files_command_test.dart index f1309f9..bb77349 100644 --- a/test/src/command_runner/commands/uninstall_completion_files_command_test.dart +++ b/test/src/command_runner/commands/uninstall_completion_files_command_test.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:cli_completion/cli_completion.dart'; import 'package:cli_completion/installer.dart'; import 'package:mason_logger/mason_logger.dart'; @@ -27,6 +29,11 @@ void main() { setUp(() { commandRunner = _TestCompletionCommandRunner(); + + final completionInstallation = commandRunner.completionInstallation; + final completionInstallationFile = File('test-config.json'); + when(() => completionInstallation.completionConfigurationFile) + .thenReturn(completionInstallationFile); }); test('can be instantiated', () { From 43180f835c629a921a4ce8b93f03c97cfe338667 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Thu, 18 May 2023 12:59:59 +0100 Subject: [PATCH 09/45] test: added TODOs --- .../command_runner/completion_command_runner_test.dart | 2 ++ test/src/installer/completion_configuration_test.dart | 9 +++++++++ test/src/installer/completion_installation_test.dart | 4 ++++ 3 files changed, 15 insertions(+) diff --git a/test/src/command_runner/completion_command_runner_test.dart b/test/src/command_runner/completion_command_runner_test.dart index 884a73b..3a36040 100644 --- a/test/src/command_runner/completion_command_runner_test.dart +++ b/test/src/command_runner/completion_command_runner_test.dart @@ -115,6 +115,8 @@ void main() { }); group('auto install', () { + // TODO(alestiago): Add tests to check that no autoinstall occurs when the + // command has been manually uninstalled. test('Tries to install completion files on test subcommand', () async { final completionInstallation = _MockCompletionInstallation(); final completionInstallationFile = File('test-config.json'); diff --git a/test/src/installer/completion_configuration_test.dart b/test/src/installer/completion_configuration_test.dart index ae158e8..1889200 100644 --- a/test/src/installer/completion_configuration_test.dart +++ b/test/src/installer/completion_configuration_test.dart @@ -193,4 +193,13 @@ void main() { }); }); }); + + group('UninstallsExtension', () { + // TODO(alestiago): Write tests. + group('include', () {}); + + group('exclude', () {}); + + group('contains', () {}); + }); } diff --git a/test/src/installer/completion_installation_test.dart b/test/src/installer/completion_installation_test.dart index 0180358..d04c00e 100644 --- a/test/src/installer/completion_installation_test.dart +++ b/test/src/installer/completion_installation_test.dart @@ -110,6 +110,8 @@ void main() { }); group('install', () { + // TODO(alestiago): Add checks that manual install writes into the config.json + // file when previously uninstalled. test('createCompletionConfigDir', () { final installation = CompletionInstallation( configuration: zshConfiguration, @@ -497,6 +499,8 @@ void main() { }); group('uninstall', () { + // TODO(alestiago): Add checks that uninstall writes into the config.json + // file when uninstalled. test( '''deletes entire completion configuration when there is a single command''', () { From 30e7cc0d0a12e5a59fc7e88e477e3ea63f64ede4 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Thu, 18 May 2023 18:01:53 +0100 Subject: [PATCH 10/45] refactor: formatter --- test/src/installer/completion_installation_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/src/installer/completion_installation_test.dart b/test/src/installer/completion_installation_test.dart index d04c00e..7e670ed 100644 --- a/test/src/installer/completion_installation_test.dart +++ b/test/src/installer/completion_installation_test.dart @@ -110,8 +110,8 @@ void main() { }); group('install', () { - // TODO(alestiago): Add checks that manual install writes into the config.json - // file when previously uninstalled. + // TODO(alestiago): Add checks that manual install writes into the + // config.json file when previously uninstalled. test('createCompletionConfigDir', () { final installation = CompletionInstallation( configuration: zshConfiguration, From adc16844d71240760689da89ad8362b58aa19ce9 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Mon, 22 May 2023 15:53:51 +0100 Subject: [PATCH 11/45] test: added UninstallsExtension tests --- .../completion_configuration_test.dart | 142 +++++++++++++++++- 1 file changed, 138 insertions(+), 4 deletions(-) diff --git a/test/src/installer/completion_configuration_test.dart b/test/src/installer/completion_configuration_test.dart index 1889200..89c4ad3 100644 --- a/test/src/installer/completion_configuration_test.dart +++ b/test/src/installer/completion_configuration_test.dart @@ -2,6 +2,7 @@ import 'dart:collection'; import 'dart:io'; +import 'dart:js_util'; import 'package:cli_completion/installer.dart'; import 'package:path/path.dart' as path; @@ -195,11 +196,144 @@ void main() { }); group('UninstallsExtension', () { - // TODO(alestiago): Write tests. - group('include', () {}); + group('include', () { + test('adds command to $Uninstalls when not already in', () { + const testCommand = 'test_command'; + const testShell = SystemShell.bash; + final uninstalls = Uninstalls({}); - group('exclude', () {}); + final newUninstalls = + uninstalls.include(command: testCommand, systemShell: testShell); - group('contains', () {}); + expect( + newUninstalls.contains(command: testCommand, systemShell: testShell), + isTrue, + ); + }); + + test('does nothing when $Uninstalls already has command', () { + const testCommand = 'test_command'; + const testShell = SystemShell.bash; + final uninstalls = Uninstalls({ + testShell: UnmodifiableSetView({testCommand}), + }); + + final newUninstalls = + uninstalls.include(command: testCommand, systemShell: testShell); + + expect( + newUninstalls.contains(command: testCommand, systemShell: testShell), + isTrue, + ); + }); + + test('adds command $Uninstalls when on a different shell', () { + const testCommand = 'test_command'; + const testShell = SystemShell.bash; + final uninstalls = Uninstalls({ + testShell: UnmodifiableSetView({testCommand}), + }); + + const anotherShell = SystemShell.zsh; + final newUninstalls = uninstalls.include( + command: testCommand, + systemShell: anotherShell, + ); + expect(testShell, isNot(equals(anotherShell))); + + expect( + newUninstalls.contains(command: testCommand, systemShell: testShell), + isTrue, + ); + expect( + newUninstalls.contains( + command: testCommand, systemShell: anotherShell), + isTrue, + ); + }); + }); + + group('exclude', () { + test('removes command when in $Uninstalls', () { + const testCommand = 'test_command'; + const testShell = SystemShell.bash; + final uninstalls = Uninstalls({ + testShell: UnmodifiableSetView({testCommand}), + }); + + final newUninstalls = + uninstalls.exclude(command: testCommand, systemShell: testShell); + + expect( + newUninstalls.contains(command: testCommand, systemShell: testShell), + isFalse, + ); + }); + + test('does nothing when command not in $Uninstalls', () { + const testCommand = 'test_command'; + const testShell = SystemShell.bash; + final uninstalls = Uninstalls({}); + + final newUninstalls = + uninstalls.exclude(command: testCommand, systemShell: testShell); + + expect( + newUninstalls.contains(command: testCommand, systemShell: testShell), + isFalse, + ); + }); + + test('does nothing when command in $Uninstalls is on a different shell', + () { + const testCommand = 'test_command'; + const testShell = SystemShell.bash; + final uninstalls = Uninstalls({ + testShell: UnmodifiableSetView({testCommand}), + }); + + const anotherShell = SystemShell.zsh; + final newUninstalls = + uninstalls.exclude(command: testCommand, systemShell: anotherShell); + + expect( + newUninstalls.contains(command: testCommand, systemShell: testShell), + isTrue, + ); + }); + }); + + group('contains', () { + test('returns true when command is in $Uninstalls for the given shell', + () { + const testCommand = 'test_command'; + const testShell = SystemShell.bash; + final uninstalls = Uninstalls({ + testShell: UnmodifiableSetView({testCommand}), + }); + + expect( + uninstalls.contains(command: testCommand, systemShell: testShell), + isTrue, + ); + }); + + test('returns false when command is in $Uninstalls for another shell', + () { + const testCommand = 'test_command'; + const testShell = SystemShell.bash; + final uninstalls = Uninstalls({ + testShell: UnmodifiableSetView({testCommand}), + }); + + const anotherShell = SystemShell.zsh; + expect(testShell, isNot(equals(anotherShell))); + + expect( + uninstalls.contains(command: testCommand, systemShell: anotherShell), + isFalse, + ); + }); + }); }); } From b99b5bd7ca9abcb61477212ed6efd98e2ff69379 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Mon, 22 May 2023 15:54:12 +0100 Subject: [PATCH 12/45] refactor: fixed formatter --- test/src/installer/completion_configuration_test.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/src/installer/completion_configuration_test.dart b/test/src/installer/completion_configuration_test.dart index 89c4ad3..3e48821 100644 --- a/test/src/installer/completion_configuration_test.dart +++ b/test/src/installer/completion_configuration_test.dart @@ -2,7 +2,6 @@ import 'dart:collection'; import 'dart:io'; -import 'dart:js_util'; import 'package:cli_completion/installer.dart'; import 'package:path/path.dart' as path; @@ -247,7 +246,9 @@ void main() { ); expect( newUninstalls.contains( - command: testCommand, systemShell: anotherShell), + command: testCommand, + systemShell: anotherShell, + ), isTrue, ); }); From 552515130312ec3c1426bb350f1b41b14f48b042 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Mon, 22 May 2023 16:12:53 +0100 Subject: [PATCH 13/45] feat: added new command as reserved --- lib/src/command_runner/completion_command_runner.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/command_runner/completion_command_runner.dart b/lib/src/command_runner/completion_command_runner.dart index 134692b..edb3617 100644 --- a/lib/src/command_runner/completion_command_runner.dart +++ b/lib/src/command_runner/completion_command_runner.dart @@ -71,6 +71,7 @@ abstract class CompletionCommandRunner extends CommandRunner { final reservedCommands = [ HandleCompletionRequestCommand.commandName, InstallCompletionFilesCommand.commandName, + UnistallCompletionFilesCommand.commandName, ]; final completionConfiguration = CompletionConfiguration.fromFile( From 86aee3bf20d9c2b52d4fedaae998befb3c2b56f3 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Mon, 22 May 2023 16:13:14 +0100 Subject: [PATCH 14/45] test: don't auto install when uninstalled --- .../completion_command_runner_test.dart | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/test/src/command_runner/completion_command_runner_test.dart b/test/src/command_runner/completion_command_runner_test.dart index 3a36040..1e0d042 100644 --- a/test/src/command_runner/completion_command_runner_test.dart +++ b/test/src/command_runner/completion_command_runner_test.dart @@ -1,3 +1,4 @@ +import 'dart:collection'; import 'dart:io'; import 'package:args/command_runner.dart'; @@ -6,6 +7,7 @@ import 'package:cli_completion/installer.dart'; import 'package:cli_completion/parser.dart'; import 'package:mason_logger/mason_logger.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:path/path.dart' as path; import 'package:test/test.dart'; class MockLogger extends Mock implements Logger {} @@ -115,8 +117,6 @@ void main() { }); group('auto install', () { - // TODO(alestiago): Add tests to check that no autoinstall occurs when the - // command has been manually uninstalled. test('Tries to install completion files on test subcommand', () async { final completionInstallation = _MockCompletionInstallation(); final completionInstallationFile = File('test-config.json'); @@ -139,6 +139,7 @@ void main() { test('does not auto install when it is disabled', () async { final completionInstallation = _MockCompletionInstallation(); + final completionInstallationFile = File('test-config.json'); when(() => completionInstallation.completionConfigurationFile) .thenReturn(completionInstallationFile); @@ -156,6 +157,38 @@ void main() { () => commandRunner.completionInstallationLogger.level = any(), ); }); + + test('does not auto install when it is unistalled', () async { + final completionInstallation = _MockCompletionInstallation(); + + final tempDirectory = Directory.systemTemp.createTempSync(); + addTearDown(() => tempDirectory.deleteSync(recursive: true)); + + final completioninstallationFilePath = + path.join(tempDirectory.path, 'test-config.json'); + final completionInstallationFile = File(completioninstallationFilePath); + final uninstalls = Uninstalls({ + SystemShell.zsh: UnmodifiableSetView({'test'}), + }); + CompletionConfiguration.empty() + .copyWith(uninstalls: uninstalls) + .writeTo(completionInstallationFile); + when(() => completionInstallation.completionConfigurationFile) + .thenReturn(completionInstallationFile); + + final commandRunner = _TestCompletionCommandRunner() + ..enableAutoInstall = true + ..addCommand(_TestUserCommand()) + ..mockCompletionInstallation = completionInstallation; + + await commandRunner.run(['ahoy']); + + verifyNever(() => commandRunner.completionInstallation.install('test')); + + verifyNever( + () => commandRunner.completionInstallationLogger.level = any(), + ); + }); }); test( From 82a6cc347698a4364dfcdc040fdddcd643176e3e Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Mon, 22 May 2023 16:52:55 +0100 Subject: [PATCH 15/45] test: added installation tests --- .../completion_installation_test.dart | 82 ++++++++++++++++++- 1 file changed, 78 insertions(+), 4 deletions(-) diff --git a/test/src/installer/completion_installation_test.dart b/test/src/installer/completion_installation_test.dart index 7e670ed..d4571b4 100644 --- a/test/src/installer/completion_installation_test.dart +++ b/test/src/installer/completion_installation_test.dart @@ -1,3 +1,4 @@ +import 'dart:collection'; import 'dart:io'; import 'package:cli_completion/installer.dart'; @@ -110,8 +111,6 @@ void main() { }); group('install', () { - // TODO(alestiago): Add checks that manual install writes into the - // config.json file when previously uninstalled. test('createCompletionConfigDir', () { final installation = CompletionInstallation( configuration: zshConfiguration, @@ -496,11 +495,56 @@ void main() { ); }, ); + + test('removes command from $CompletionConfiguration when uninstalled', + () { + const systemShell = SystemShell.zsh; + final installation = CompletionInstallation.fromSystemShell( + logger: logger, + isWindowsOverride: false, + environmentOverride: { + 'HOME': tempDir.path, + }, + systemShell: systemShell, + ); + + File(path.join(tempDir.path, '.zshrc')).createSync(); + + const command = 'very_good'; + final completionConfigurationFile = + installation.completionConfigurationFile; + + final uninstalls = Uninstalls({ + systemShell: UnmodifiableSetView({command}), + }); + CompletionConfiguration.empty() + .copyWith(uninstalls: uninstalls) + .writeTo(completionConfigurationFile); + final completionConfiguration = + CompletionConfiguration.fromFile(completionConfigurationFile); + expect( + completionConfiguration.uninstalls + .contains(command: command, systemShell: systemShell), + isTrue, + reason: + '''The completion configuration should contain the uninstall for the command before install''', + ); + + installation.install('very_good'); + + final newCompletionConfiguration = + CompletionConfiguration.fromFile(completionConfigurationFile); + expect( + newCompletionConfiguration.uninstalls + .contains(command: command, systemShell: systemShell), + isFalse, + reason: + '''The completion configuration should not contain the uninstall for the command after install''', + ); + }); }); group('uninstall', () { - // TODO(alestiago): Add checks that uninstall writes into the config.json - // file when uninstalled. test( '''deletes entire completion configuration when there is a single command''', () { @@ -741,6 +785,36 @@ void main() { ); }); + test('adds command to uninstalls when not the last command', () { + const systemShell = SystemShell.zsh; + final installation = CompletionInstallation.fromSystemShell( + systemShell: systemShell, + logger: logger, + environmentOverride: { + 'HOME': tempDir.path, + }, + ); + + File(path.join(tempDir.path, '.zshrc')).createSync(); + + const command = 'very_good'; + installation + ..install(command) + ..install('another_command') + ..uninstall(command); + + final completionConfigurationFile = + installation.completionConfigurationFile; + final completionConfiguration = + CompletionConfiguration.fromFile(completionConfigurationFile); + expect( + completionConfiguration.uninstalls + .contains(command: command, systemShell: systemShell), + isTrue, + reason: 'Command should be added to uninstalls after uninstalling.', + ); + }); + group('throws a CompletionUnistallationException', () { test('when RC file does not exist', () { final installation = CompletionInstallation( From 434fe9198bad0d52cf6f48534a9f2e346b5991d7 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Mon, 22 May 2023 17:02:39 +0100 Subject: [PATCH 16/45] test: does not auto install when it is unistalled --- test/src/command_runner/completion_command_runner_test.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/src/command_runner/completion_command_runner_test.dart b/test/src/command_runner/completion_command_runner_test.dart index 1e0d042..ae15f1b 100644 --- a/test/src/command_runner/completion_command_runner_test.dart +++ b/test/src/command_runner/completion_command_runner_test.dart @@ -184,10 +184,6 @@ void main() { await commandRunner.run(['ahoy']); verifyNever(() => commandRunner.completionInstallation.install('test')); - - verifyNever( - () => commandRunner.completionInstallationLogger.level = any(), - ); }); }); From e657265c6c4fd6a1f88c80ab9d3bb4dae0e72da0 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Mon, 22 May 2023 17:22:54 +0100 Subject: [PATCH 17/45] test: used executableName --- .../completion_command_runner_test.dart | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/test/src/command_runner/completion_command_runner_test.dart b/test/src/command_runner/completion_command_runner_test.dart index ae15f1b..11e05ad 100644 --- a/test/src/command_runner/completion_command_runner_test.dart +++ b/test/src/command_runner/completion_command_runner_test.dart @@ -164,11 +164,17 @@ void main() { final tempDirectory = Directory.systemTemp.createTempSync(); addTearDown(() => tempDirectory.deleteSync(recursive: true)); + final commandRunner = _TestCompletionCommandRunner() + ..enableAutoInstall = true + ..addCommand(_TestUserCommand()) + ..mockCompletionInstallation = completionInstallation; + final completioninstallationFilePath = path.join(tempDirectory.path, 'test-config.json'); final completionInstallationFile = File(completioninstallationFilePath); final uninstalls = Uninstalls({ - SystemShell.zsh: UnmodifiableSetView({'test'}), + SystemShell.zsh: + UnmodifiableSetView({commandRunner.executableName}), }); CompletionConfiguration.empty() .copyWith(uninstalls: uninstalls) @@ -176,14 +182,15 @@ void main() { when(() => completionInstallation.completionConfigurationFile) .thenReturn(completionInstallationFile); - final commandRunner = _TestCompletionCommandRunner() - ..enableAutoInstall = true - ..addCommand(_TestUserCommand()) - ..mockCompletionInstallation = completionInstallation; - await commandRunner.run(['ahoy']); - verifyNever(() => commandRunner.completionInstallation.install('test')); + verifyNever( + () => commandRunner.completionInstallation + .install(commandRunner.executableName), + ); + verifyNever( + () => commandRunner.completionInstallationLogger.level = any(), + ); }); }); From b2422ec9f819bb9d207005c3976435011ef7f770 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Mon, 22 May 2023 17:56:29 +0100 Subject: [PATCH 18/45] test: used command runner shell --- test/src/command_runner/completion_command_runner_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/src/command_runner/completion_command_runner_test.dart b/test/src/command_runner/completion_command_runner_test.dart index 11e05ad..18ac494 100644 --- a/test/src/command_runner/completion_command_runner_test.dart +++ b/test/src/command_runner/completion_command_runner_test.dart @@ -173,7 +173,7 @@ void main() { path.join(tempDirectory.path, 'test-config.json'); final completionInstallationFile = File(completioninstallationFilePath); final uninstalls = Uninstalls({ - SystemShell.zsh: + commandRunner.systemShell!: UnmodifiableSetView({commandRunner.executableName}), }); CompletionConfiguration.empty() From 261aa3f118d1e795f753e8c8accf99e2a09e969d Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Mon, 22 May 2023 18:00:10 +0100 Subject: [PATCH 19/45] test: defined environment override for shell --- test/src/command_runner/completion_command_runner_test.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/src/command_runner/completion_command_runner_test.dart b/test/src/command_runner/completion_command_runner_test.dart index 18ac494..f734c97 100644 --- a/test/src/command_runner/completion_command_runner_test.dart +++ b/test/src/command_runner/completion_command_runner_test.dart @@ -167,7 +167,10 @@ void main() { final commandRunner = _TestCompletionCommandRunner() ..enableAutoInstall = true ..addCommand(_TestUserCommand()) - ..mockCompletionInstallation = completionInstallation; + ..mockCompletionInstallation = completionInstallation + ..environmentOverride = { + 'SHELL': '/foo/bar/zsh', + }; final completioninstallationFilePath = path.join(tempDirectory.path, 'test-config.json'); From 1d9da79f2f4d49757f2364ec6c722e387ebcc00b Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Tue, 23 May 2023 14:10:04 +0100 Subject: [PATCH 20/45] feat: support for caching installs --- .../completion_command_runner.dart | 6 +- .../installer/completion_configuration.dart | 55 ++++++++++++++----- .../installer/completion_installation.dart | 8 +++ 3 files changed, 53 insertions(+), 16 deletions(-) diff --git a/lib/src/command_runner/completion_command_runner.dart b/lib/src/command_runner/completion_command_runner.dart index edb3617..aff3908 100644 --- a/lib/src/command_runner/completion_command_runner.dart +++ b/lib/src/command_runner/completion_command_runner.dart @@ -77,12 +77,16 @@ abstract class CompletionCommandRunner extends CommandRunner { final completionConfiguration = CompletionConfiguration.fromFile( completionInstallation.completionConfigurationFile, ); + final isInstalled = systemShell != null && + completionConfiguration.installs + .contains(command: executableName, systemShell: systemShell!); final isUninstalled = systemShell != null && completionConfiguration.uninstalls .contains(command: executableName, systemShell: systemShell!); if (enableAutoInstall && !reservedCommands.contains(topLevelResults.command?.name) && - !isUninstalled) { + !isUninstalled && + !isInstalled) { // When auto installing, use error level to display messages. tryInstallCompletionFiles(Level.error); } diff --git a/lib/src/installer/completion_configuration.dart b/lib/src/installer/completion_configuration.dart index 33e116a..8261525 100644 --- a/lib/src/installer/completion_configuration.dart +++ b/lib/src/installer/completion_configuration.dart @@ -10,6 +10,7 @@ import 'package:meta/meta.dart'; /// /// The map and its content are unmodifiable. This is to ensure that /// [CompletionConfiguration]s is fully immutable. +// TODO(alestiago): Rename Uninstalls to ShellCommandsMap typedef Uninstalls = UnmodifiableMapView>; @@ -22,11 +23,14 @@ class CompletionConfiguration { /// {@macro completion_configuration} const CompletionConfiguration._({ required this.uninstalls, + required this.installs, }); /// Creates an empty [CompletionConfiguration]. @visibleForTesting - CompletionConfiguration.empty() : uninstalls = UnmodifiableMapView({}); + CompletionConfiguration.empty() + : uninstalls = Uninstalls({}), + installs = Uninstalls({}); /// Creates a [CompletionConfiguration] from the given [file] content. /// @@ -56,18 +60,33 @@ class CompletionConfiguration { } return CompletionConfiguration._( - uninstalls: _jsonDecodeUninstalls(decodedJson), + uninstalls: _jsonDecodeUninstalls( + decodedJson, + jsonKey: CompletionConfiguration._uninstallsJsonKey, + ), + installs: _jsonDecodeUninstalls( + decodedJson, + jsonKey: CompletionConfiguration._installsJsonKey, + ), ); } /// The JSON key for the [uninstalls] field. static const String _uninstallsJsonKey = 'uninstalls'; + /// The JSON key for the [installs] field. + static const String _installsJsonKey = 'installs'; + /// Stores those commands that have been manually uninstalled by the user. /// /// Uninstalls are specific to a given [SystemShell]. final Uninstalls uninstalls; + /// Stores those commands that have completion installed. + /// + /// Installed commands are specific to a given [SystemShell]. + final Uninstalls installs; + /// Stores the [CompletionConfiguration] in the given [file]. void writeTo(File file) { if (!file.existsSync()) { @@ -76,41 +95,47 @@ class CompletionConfiguration { file.writeAsStringSync(_toJson()); } + /// Returns a JSON representation of this [CompletionConfiguration]. + String _toJson() { + return jsonEncode({ + _uninstallsJsonKey: _jsonEncodeUninstalls(uninstalls), + _installsJsonKey: _jsonEncodeUninstalls(installs), + }); + } + /// Returns a copy of this [CompletionConfiguration] with the given fields /// replaced. CompletionConfiguration copyWith({ Uninstalls? uninstalls, + Uninstalls? installs, }) { return CompletionConfiguration._( uninstalls: uninstalls ?? this.uninstalls, + installs: installs ?? this.installs, ); } - - /// Returns a JSON representation of this [CompletionConfiguration]. - String _toJson() { - return jsonEncode({ - _uninstallsJsonKey: _jsonEncodeUninstalls(uninstalls), - }); - } } /// Decodes [Uninstalls] from the given [json]. /// /// If the [json] is not partially or fully valid, it handles issues gracefully /// without throwing an [Exception]. -Uninstalls _jsonDecodeUninstalls(Map json) { - if (!json.containsKey(CompletionConfiguration._uninstallsJsonKey)) { - return UnmodifiableMapView({}); +Uninstalls _jsonDecodeUninstalls( + Map json, { + required String jsonKey, +}) { + if (!json.containsKey(jsonKey)) { + return Uninstalls({}); } - final jsonUninstalls = json[CompletionConfiguration._uninstallsJsonKey]; + final jsonUninstalls = json[jsonKey]; if (jsonUninstalls is! String) { - return UnmodifiableMapView({}); + return Uninstalls({}); } late final Map decodedUninstalls; try { decodedUninstalls = jsonDecode(jsonUninstalls) as Map; } on FormatException { - return UnmodifiableMapView({}); + return Uninstalls({}); } final newUninstalls = >{}; diff --git a/lib/src/installer/completion_installation.dart b/lib/src/installer/completion_installation.dart index 425f290..f3a82b3 100644 --- a/lib/src/installer/completion_installation.dart +++ b/lib/src/installer/completion_installation.dart @@ -138,6 +138,10 @@ class CompletionInstallation { command: rootCommand, systemShell: configuration.shell, ), + installs: completionConfiguration.installs.include( + command: rootCommand, + systemShell: configuration.shell, + ), ) .writeTo(completionConfigurationFile); } @@ -409,6 +413,10 @@ ${configuration!.sourceLineTemplate(scriptPath)}'''; command: rootCommand, systemShell: configuration.shell, ), + installs: completionConfiguration.installs.exclude( + command: rootCommand, + systemShell: configuration.shell, + ), ) .writeTo(completionConfigurationFile); } From b9adf05dfb9c694a3a7983dd3054a32c1b606be8 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Tue, 23 May 2023 14:17:58 +0100 Subject: [PATCH 21/45] test: added completion_installation tests --- .../completion_installation_test.dart | 108 +++++++++++++++++- 1 file changed, 107 insertions(+), 1 deletion(-) diff --git a/test/src/installer/completion_installation_test.dart b/test/src/installer/completion_installation_test.dart index d4571b4..046ece6 100644 --- a/test/src/installer/completion_installation_test.dart +++ b/test/src/installer/completion_installation_test.dart @@ -496,7 +496,8 @@ void main() { }, ); - test('removes command from $CompletionConfiguration when uninstalled', + test( + '''removes command from $CompletionConfiguration uninstalls when uninstalled''', () { const systemShell = SystemShell.zsh; final installation = CompletionInstallation.fromSystemShell( @@ -542,6 +543,81 @@ void main() { '''The completion configuration should not contain the uninstall for the command after install''', ); }); + + test( + '''adds command to $CompletionConfiguration installs when installed''', + () { + const systemShell = SystemShell.zsh; + final installation = CompletionInstallation.fromSystemShell( + logger: logger, + isWindowsOverride: false, + environmentOverride: { + 'HOME': tempDir.path, + }, + systemShell: systemShell, + ); + + File(path.join(tempDir.path, '.zshrc')).createSync(); + + const command = 'very_good'; + + installation.install(command); + + final newCompletionConfiguration = CompletionConfiguration.fromFile( + installation.completionConfigurationFile, + ); + expect( + newCompletionConfiguration.installs + .contains(command: command, systemShell: systemShell), + isTrue, + reason: + '''The completion configuration installs should contain the command after install''', + ); + }); + + test( + '''command still in $CompletionConfiguration installs when already installed''', + () { + const systemShell = SystemShell.zsh; + final installation = CompletionInstallation.fromSystemShell( + logger: logger, + isWindowsOverride: false, + environmentOverride: { + 'HOME': tempDir.path, + }, + systemShell: systemShell, + ); + + File(path.join(tempDir.path, '.zshrc')).createSync(); + + const command = 'very_good'; + installation.install(command); + + var completionConfiguration = CompletionConfiguration.fromFile( + installation.completionConfigurationFile, + ); + expect( + completionConfiguration.installs + .contains(command: command, systemShell: systemShell), + isTrue, + reason: + '''The completion configuration installs should contain the command after install''', + ); + + // Install again. + installation.install(command); + + completionConfiguration = CompletionConfiguration.fromFile( + installation.completionConfigurationFile, + ); + expect( + completionConfiguration.installs + .contains(command: command, systemShell: systemShell), + isTrue, + reason: + '''The completion configuration installs should still contain the command after install''', + ); + }); }); group('uninstall', () { @@ -815,6 +891,36 @@ void main() { ); }); + test('removes command from installs when not the last command', () { + const systemShell = SystemShell.zsh; + final installation = CompletionInstallation.fromSystemShell( + systemShell: systemShell, + logger: logger, + environmentOverride: { + 'HOME': tempDir.path, + }, + ); + + File(path.join(tempDir.path, '.zshrc')).createSync(); + + const command = 'very_good'; + installation + ..install(command) + ..install('another_command') + ..uninstall(command); + + final completionConfigurationFile = + installation.completionConfigurationFile; + final completionConfiguration = + CompletionConfiguration.fromFile(completionConfigurationFile); + expect( + completionConfiguration.installs + .contains(command: command, systemShell: systemShell), + isFalse, + reason: 'Command should be removed from installs after uninstalling.', + ); + }); + group('throws a CompletionUnistallationException', () { test('when RC file does not exist', () { final installation = CompletionInstallation( From 0ea3d2fa1b99cecad4adcad762543549adfbbe14 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Tue, 23 May 2023 14:20:09 +0100 Subject: [PATCH 22/45] test: added completion_command_runner test --- .../completion_command_runner_test.dart | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/test/src/command_runner/completion_command_runner_test.dart b/test/src/command_runner/completion_command_runner_test.dart index f734c97..11c563f 100644 --- a/test/src/command_runner/completion_command_runner_test.dart +++ b/test/src/command_runner/completion_command_runner_test.dart @@ -195,6 +195,44 @@ void main() { () => commandRunner.completionInstallationLogger.level = any(), ); }); + + test('does not auto install when it is already installed', () async { + final completionInstallation = _MockCompletionInstallation(); + + final tempDirectory = Directory.systemTemp.createTempSync(); + addTearDown(() => tempDirectory.deleteSync(recursive: true)); + + final commandRunner = _TestCompletionCommandRunner() + ..enableAutoInstall = true + ..addCommand(_TestUserCommand()) + ..mockCompletionInstallation = completionInstallation + ..environmentOverride = { + 'SHELL': '/foo/bar/zsh', + }; + + final completioninstallationFilePath = + path.join(tempDirectory.path, 'test-config.json'); + final completionInstallationFile = File(completioninstallationFilePath); + final installs = Uninstalls({ + commandRunner.systemShell!: + UnmodifiableSetView({commandRunner.executableName}), + }); + CompletionConfiguration.empty() + .copyWith(installs: installs) + .writeTo(completionInstallationFile); + when(() => completionInstallation.completionConfigurationFile) + .thenReturn(completionInstallationFile); + + await commandRunner.run(['ahoy']); + + verifyNever( + () => commandRunner.completionInstallation + .install(commandRunner.executableName), + ); + verifyNever( + () => commandRunner.completionInstallationLogger.level = any(), + ); + }); }); test( From edc4af939d1f2837fe2fac7cee8f932ff510cb60 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Tue, 23 May 2023 14:22:38 +0100 Subject: [PATCH 23/45] feat: efficiently check for autoinstall before reading cache --- .../completion_command_runner.dart | 31 ++++++++++--------- .../completion_command_runner_test.dart | 4 --- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/lib/src/command_runner/completion_command_runner.dart b/lib/src/command_runner/completion_command_runner.dart index aff3908..0ffc3f7 100644 --- a/lib/src/command_runner/completion_command_runner.dart +++ b/lib/src/command_runner/completion_command_runner.dart @@ -74,21 +74,22 @@ abstract class CompletionCommandRunner extends CommandRunner { UnistallCompletionFilesCommand.commandName, ]; - final completionConfiguration = CompletionConfiguration.fromFile( - completionInstallation.completionConfigurationFile, - ); - final isInstalled = systemShell != null && - completionConfiguration.installs - .contains(command: executableName, systemShell: systemShell!); - final isUninstalled = systemShell != null && - completionConfiguration.uninstalls - .contains(command: executableName, systemShell: systemShell!); - if (enableAutoInstall && - !reservedCommands.contains(topLevelResults.command?.name) && - !isUninstalled && - !isInstalled) { - // When auto installing, use error level to display messages. - tryInstallCompletionFiles(Level.error); + if (enableAutoInstall) { + final completionConfiguration = CompletionConfiguration.fromFile( + completionInstallation.completionConfigurationFile, + ); + final isInstalled = systemShell != null && + completionConfiguration.installs + .contains(command: executableName, systemShell: systemShell!); + final isUninstalled = systemShell != null && + completionConfiguration.uninstalls + .contains(command: executableName, systemShell: systemShell!); + if (!reservedCommands.contains(topLevelResults.command?.name) && + !isUninstalled && + !isInstalled) { + // When auto installing, use error level to display messages. + tryInstallCompletionFiles(Level.error); + } } return super.runCommand(topLevelResults); diff --git a/test/src/command_runner/completion_command_runner_test.dart b/test/src/command_runner/completion_command_runner_test.dart index 11c563f..d958c70 100644 --- a/test/src/command_runner/completion_command_runner_test.dart +++ b/test/src/command_runner/completion_command_runner_test.dart @@ -140,10 +140,6 @@ void main() { test('does not auto install when it is disabled', () async { final completionInstallation = _MockCompletionInstallation(); - final completionInstallationFile = File('test-config.json'); - when(() => completionInstallation.completionConfigurationFile) - .thenReturn(completionInstallationFile); - final commandRunner = _TestCompletionCommandRunner() ..enableAutoInstall = false ..addCommand(_TestUserCommand()) From 71a97db320eeb524222cae3185b9305d8f456a53 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Tue, 23 May 2023 14:25:52 +0100 Subject: [PATCH 24/45] refactor: simplfied auto-installation logic --- .../command_runner/completion_command_runner.dart | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/src/command_runner/completion_command_runner.dart b/lib/src/command_runner/completion_command_runner.dart index 0ffc3f7..28c9002 100644 --- a/lib/src/command_runner/completion_command_runner.dart +++ b/lib/src/command_runner/completion_command_runner.dart @@ -74,16 +74,14 @@ abstract class CompletionCommandRunner extends CommandRunner { UnistallCompletionFilesCommand.commandName, ]; - if (enableAutoInstall) { + if (enableAutoInstall && systemShell != null) { final completionConfiguration = CompletionConfiguration.fromFile( completionInstallation.completionConfigurationFile, ); - final isInstalled = systemShell != null && - completionConfiguration.installs - .contains(command: executableName, systemShell: systemShell!); - final isUninstalled = systemShell != null && - completionConfiguration.uninstalls - .contains(command: executableName, systemShell: systemShell!); + final isInstalled = completionConfiguration.installs + .contains(command: executableName, systemShell: systemShell!); + final isUninstalled = completionConfiguration.uninstalls + .contains(command: executableName, systemShell: systemShell!); if (!reservedCommands.contains(topLevelResults.command?.name) && !isUninstalled && !isInstalled) { From bc6201be25fcb9cf737a0bdb46995cf840e73894 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Tue, 23 May 2023 14:27:19 +0100 Subject: [PATCH 25/45] refactor: made reserved commands static --- .../completion_command_runner.dart | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/src/command_runner/completion_command_runner.dart b/lib/src/command_runner/completion_command_runner.dart index 28c9002..3654871 100644 --- a/lib/src/command_runner/completion_command_runner.dart +++ b/lib/src/command_runner/completion_command_runner.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:collection'; import 'package:args/args.dart'; import 'package:args/command_runner.dart'; @@ -65,15 +66,16 @@ abstract class CompletionCommandRunner extends CommandRunner { return _completionInstallation = completionInstallation; } + /// The list of commands that should not trigger the auto installation. + static final _reservedCommands = UnmodifiableListView([ + HandleCompletionRequestCommand.commandName, + InstallCompletionFilesCommand.commandName, + UnistallCompletionFilesCommand.commandName, + ]); + @override @mustCallSuper Future runCommand(ArgResults topLevelResults) async { - final reservedCommands = [ - HandleCompletionRequestCommand.commandName, - InstallCompletionFilesCommand.commandName, - UnistallCompletionFilesCommand.commandName, - ]; - if (enableAutoInstall && systemShell != null) { final completionConfiguration = CompletionConfiguration.fromFile( completionInstallation.completionConfigurationFile, @@ -82,7 +84,7 @@ abstract class CompletionCommandRunner extends CommandRunner { .contains(command: executableName, systemShell: systemShell!); final isUninstalled = completionConfiguration.uninstalls .contains(command: executableName, systemShell: systemShell!); - if (!reservedCommands.contains(topLevelResults.command?.name) && + if (!_reservedCommands.contains(topLevelResults.command?.name) && !isUninstalled && !isInstalled) { // When auto installing, use error level to display messages. From 0577b7365564c475e44b3df524a56c75c236238f Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Tue, 23 May 2023 14:27:47 +0100 Subject: [PATCH 26/45] refactor: made reserved commands const --- lib/src/command_runner/completion_command_runner.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/command_runner/completion_command_runner.dart b/lib/src/command_runner/completion_command_runner.dart index 3654871..b074c0b 100644 --- a/lib/src/command_runner/completion_command_runner.dart +++ b/lib/src/command_runner/completion_command_runner.dart @@ -67,11 +67,11 @@ abstract class CompletionCommandRunner extends CommandRunner { } /// The list of commands that should not trigger the auto installation. - static final _reservedCommands = UnmodifiableListView([ + static const _reservedCommands = [ HandleCompletionRequestCommand.commandName, InstallCompletionFilesCommand.commandName, UnistallCompletionFilesCommand.commandName, - ]); + ]; @override @mustCallSuper From cddebdae7564991b5a6b95ac91a553fc4f695f43 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Tue, 23 May 2023 14:28:20 +0100 Subject: [PATCH 27/45] refactor: analysis --- lib/src/command_runner/completion_command_runner.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/command_runner/completion_command_runner.dart b/lib/src/command_runner/completion_command_runner.dart index b074c0b..b159039 100644 --- a/lib/src/command_runner/completion_command_runner.dart +++ b/lib/src/command_runner/completion_command_runner.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:collection'; import 'package:args/args.dart'; import 'package:args/command_runner.dart'; From 6397ee06318d00bf1c658357b878f1c328078f4b Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Tue, 23 May 2023 14:51:13 +0100 Subject: [PATCH 28/45] refactor: rename `Uninstalls` to `ShellCommandsMap` (#75) --- .../installer/completion_configuration.dart | 82 +++--- .../completion_command_runner_test.dart | 4 +- .../completion_configuration_test.dart | 252 +++++++++++++----- .../completion_installation_test.dart | 2 +- 4 files changed, 229 insertions(+), 111 deletions(-) diff --git a/lib/src/installer/completion_configuration.dart b/lib/src/installer/completion_configuration.dart index 8261525..d4c6747 100644 --- a/lib/src/installer/completion_configuration.dart +++ b/lib/src/installer/completion_configuration.dart @@ -10,8 +10,7 @@ import 'package:meta/meta.dart'; /// /// The map and its content are unmodifiable. This is to ensure that /// [CompletionConfiguration]s is fully immutable. -// TODO(alestiago): Rename Uninstalls to ShellCommandsMap -typedef Uninstalls +typedef ShellCommandsMap = UnmodifiableMapView>; /// {@template completion_configuration} @@ -29,8 +28,8 @@ class CompletionConfiguration { /// Creates an empty [CompletionConfiguration]. @visibleForTesting CompletionConfiguration.empty() - : uninstalls = Uninstalls({}), - installs = Uninstalls({}); + : uninstalls = ShellCommandsMap({}), + installs = ShellCommandsMap({}); /// Creates a [CompletionConfiguration] from the given [file] content. /// @@ -60,32 +59,34 @@ class CompletionConfiguration { } return CompletionConfiguration._( - uninstalls: _jsonDecodeUninstalls( + uninstalls: _jsonDecodeShellCommandsMap( decodedJson, - jsonKey: CompletionConfiguration._uninstallsJsonKey, + jsonKey: CompletionConfiguration.uninstallsJsonKey, ), - installs: _jsonDecodeUninstalls( + installs: _jsonDecodeShellCommandsMap( decodedJson, - jsonKey: CompletionConfiguration._installsJsonKey, + jsonKey: CompletionConfiguration.installsJsonKey, ), ); } /// The JSON key for the [uninstalls] field. - static const String _uninstallsJsonKey = 'uninstalls'; + @visibleForTesting + static const String uninstallsJsonKey = 'uninstalls'; /// The JSON key for the [installs] field. - static const String _installsJsonKey = 'installs'; + @visibleForTesting + static const String installsJsonKey = 'installs'; /// Stores those commands that have been manually uninstalled by the user. /// /// Uninstalls are specific to a given [SystemShell]. - final Uninstalls uninstalls; + final ShellCommandsMap uninstalls; /// Stores those commands that have completion installed. /// /// Installed commands are specific to a given [SystemShell]. - final Uninstalls installs; + final ShellCommandsMap installs; /// Stores the [CompletionConfiguration] in the given [file]. void writeTo(File file) { @@ -98,16 +99,16 @@ class CompletionConfiguration { /// Returns a JSON representation of this [CompletionConfiguration]. String _toJson() { return jsonEncode({ - _uninstallsJsonKey: _jsonEncodeUninstalls(uninstalls), - _installsJsonKey: _jsonEncodeUninstalls(installs), + uninstallsJsonKey: _jsonEncodeShellCommandsMap(uninstalls), + installsJsonKey: _jsonEncodeShellCommandsMap(installs), }); } /// Returns a copy of this [CompletionConfiguration] with the given fields /// replaced. CompletionConfiguration copyWith({ - Uninstalls? uninstalls, - Uninstalls? installs, + ShellCommandsMap? uninstalls, + ShellCommandsMap? installs, }) { return CompletionConfiguration._( uninstalls: uninstalls ?? this.uninstalls, @@ -116,58 +117,59 @@ class CompletionConfiguration { } } -/// Decodes [Uninstalls] from the given [json]. +/// Decodes [ShellCommandsMap] from the given [json]. /// /// If the [json] is not partially or fully valid, it handles issues gracefully /// without throwing an [Exception]. -Uninstalls _jsonDecodeUninstalls( +ShellCommandsMap _jsonDecodeShellCommandsMap( Map json, { required String jsonKey, }) { if (!json.containsKey(jsonKey)) { - return Uninstalls({}); + return ShellCommandsMap({}); } - final jsonUninstalls = json[jsonKey]; - if (jsonUninstalls is! String) { - return Uninstalls({}); + final jsonShellCommandsMap = json[jsonKey]; + if (jsonShellCommandsMap is! String) { + return ShellCommandsMap({}); } - late final Map decodedUninstalls; + late final Map decodedShellCommandsMap; try { - decodedUninstalls = jsonDecode(jsonUninstalls) as Map; + decodedShellCommandsMap = + jsonDecode(jsonShellCommandsMap) as Map; } on FormatException { - return Uninstalls({}); + return ShellCommandsMap({}); } - final newUninstalls = >{}; - for (final entry in decodedUninstalls.entries) { + final newShellCommandsMap = >{}; + for (final entry in decodedShellCommandsMap.entries) { final systemShell = SystemShell.tryParse(entry.key); if (systemShell == null) continue; - final uninstallSet = {}; + final commandsSet = {}; if (entry.value is List) { for (final uninstall in entry.value as List) { if (uninstall is String) { - uninstallSet.add(uninstall); + commandsSet.add(uninstall); } } } - newUninstalls[systemShell] = UnmodifiableSetView(uninstallSet); + newShellCommandsMap[systemShell] = UnmodifiableSetView(commandsSet); } - return UnmodifiableMapView(newUninstalls); + return UnmodifiableMapView(newShellCommandsMap); } -/// Returns a JSON representation of the given [Uninstalls]. -String _jsonEncodeUninstalls(Uninstalls uninstalls) { +/// Returns a JSON representation of the given [ShellCommandsMap]. +String _jsonEncodeShellCommandsMap(ShellCommandsMap shellCommandsMap) { return jsonEncode({ - for (final entry in uninstalls.entries) + for (final entry in shellCommandsMap.entries) entry.key.toString(): entry.value.toList(), }); } -/// Provides convinience methods for [Uninstalls]. -extension UninstallsExtension on Uninstalls { - /// Returns a new [Uninstalls] with the given [command] added to +/// Provides convinience methods for [ShellCommandsMap]. +extension ShellCommandsMapExtension on ShellCommandsMap { + /// Returns a new [ShellCommandsMap] with the given [command] added to /// [systemShell]. - Uninstalls include({ + ShellCommandsMap include({ required String command, required SystemShell systemShell, }) { @@ -184,9 +186,9 @@ extension UninstallsExtension on Uninstalls { ); } - /// Returns a new [Uninstalls] with the given [command] removed from + /// Returns a new [ShellCommandsMap] with the given [command] removed from /// [systemShell]. - Uninstalls exclude({ + ShellCommandsMap exclude({ required String command, required SystemShell systemShell, }) { diff --git a/test/src/command_runner/completion_command_runner_test.dart b/test/src/command_runner/completion_command_runner_test.dart index d958c70..ac76a55 100644 --- a/test/src/command_runner/completion_command_runner_test.dart +++ b/test/src/command_runner/completion_command_runner_test.dart @@ -171,7 +171,7 @@ void main() { final completioninstallationFilePath = path.join(tempDirectory.path, 'test-config.json'); final completionInstallationFile = File(completioninstallationFilePath); - final uninstalls = Uninstalls({ + final uninstalls = ShellCommandsMap({ commandRunner.systemShell!: UnmodifiableSetView({commandRunner.executableName}), }); @@ -209,7 +209,7 @@ void main() { final completioninstallationFilePath = path.join(tempDirectory.path, 'test-config.json'); final completionInstallationFile = File(completioninstallationFilePath); - final installs = Uninstalls({ + final installs = ShellCommandsMap({ commandRunner.systemShell!: UnmodifiableSetView({commandRunner.executableName}), }); diff --git a/test/src/installer/completion_configuration_test.dart b/test/src/installer/completion_configuration_test.dart index 3e48821..1673520 100644 --- a/test/src/installer/completion_configuration_test.dart +++ b/test/src/installer/completion_configuration_test.dart @@ -9,13 +9,16 @@ import 'package:test/test.dart'; void main() { group('$CompletionConfiguration', () { - final testUninstalls = UnmodifiableMapView({ + final testInstalls = ShellCommandsMap({ + SystemShell.bash: UnmodifiableSetView({'very_good'}), + }); + final testUninstalls = ShellCommandsMap({ SystemShell.bash: UnmodifiableSetView({'very_bad'}), }); group('fromFile', () { test( - 'returns empty cache when file does not exist', + 'returns empty $CompletionConfiguration when file does not exist', () { final tempDirectory = Directory.systemTemp.createTempSync(); addTearDown(() => tempDirectory.deleteSync(recursive: true)); @@ -27,25 +30,26 @@ void main() { reason: 'File should not exist', ); - final cache = CompletionConfiguration.fromFile(file); + final completionConfiguration = + CompletionConfiguration.fromFile(file); expect( - cache.uninstalls, + completionConfiguration.uninstalls, isEmpty, reason: 'Uninstalls should be initially empty', ); }, ); - test('returns empty cache when file is empty', () { + test('returns empty $CompletionConfiguration when file is empty', () { final tempDirectory = Directory.systemTemp.createTempSync(); addTearDown(() => tempDirectory.deleteSync(recursive: true)); final file = File(path.join(tempDirectory.path, 'config.json')) ..writeAsStringSync(''); - final cache = CompletionConfiguration.fromFile(file); + final completionConfiguration = CompletionConfiguration.fromFile(file); expect( - cache.uninstalls, + completionConfiguration.uninstalls, isEmpty, reason: 'Uninstalls should be initially empty', ); @@ -57,31 +61,40 @@ void main() { addTearDown(() => tempDirectory.deleteSync(recursive: true)); final file = File(path.join(tempDirectory.path, 'config.json')); - final cache = CompletionConfiguration.empty().copyWith( + final completionConfiguration = + CompletionConfiguration.empty().copyWith( + installs: testInstalls, uninstalls: testUninstalls, )..writeTo(file); - final newCache = CompletionConfiguration.fromFile(file); + final newConfiguration = CompletionConfiguration.fromFile(file); + expect( + newConfiguration.installs, + equals(completionConfiguration.installs), + reason: 'Installs should match those defined in the file', + ); expect( - newCache.uninstalls, - cache.uninstalls, + newConfiguration.uninstalls, + equals(completionConfiguration.uninstalls), reason: 'Uninstalls should match those defined in the file', ); }); test( - '''returns a $CompletionConfiguration with empty uninstalls if the file's JSON "uninstalls" key has a string value''', + '''returns a $CompletionConfiguration with empty uninstalls if the file's JSON uninstalls key has a string value''', () { final tempDirectory = Directory.systemTemp.createTempSync(); addTearDown(() => tempDirectory.deleteSync(recursive: true)); - const json = '{"uninstalls": "very_bad"}'; + const json = + '{"${CompletionConfiguration.uninstallsJsonKey}": "very_bad"}'; final file = File(path.join(tempDirectory.path, 'config.json')) ..writeAsStringSync(json); - final cache = CompletionConfiguration.fromFile(file); + final completionConfiguration = + CompletionConfiguration.fromFile(file); expect( - cache.uninstalls, + completionConfiguration.uninstalls, isEmpty, reason: '''Uninstalls should be empty when the value is of an invalid type''', @@ -90,24 +103,68 @@ void main() { ); test( - '''returns a $CompletionConfiguration with empty uninstalls if file's JSON "uninstalls" key has a numeric value''', + '''returns a $CompletionConfiguration with empty installs if the file's JSON installs key has a string value''', + () { + final tempDirectory = Directory.systemTemp.createTempSync(); + addTearDown(() => tempDirectory.deleteSync(recursive: true)); + + const json = + '{"${CompletionConfiguration.installsJsonKey}": "very_bad"}'; + final file = File(path.join(tempDirectory.path, 'config.json')) + ..writeAsStringSync(json); + + final completionConfiguration = + CompletionConfiguration.fromFile(file); + expect( + completionConfiguration.installs, + isEmpty, + reason: + '''Installs should be empty when the value is of an invalid type''', + ); + }, + ); + + test( + '''returns a $CompletionConfiguration with empty uninstalls if file's JSON uninstalls key has a numeric value''', () { final tempDirectory = Directory.systemTemp.createTempSync(); addTearDown(() => tempDirectory.deleteSync(recursive: true)); - const json = '{"uninstalls": 1}'; + const json = '{"${CompletionConfiguration.uninstallsJsonKey}": 1}'; final file = File(path.join(tempDirectory.path, 'config.json')) ..writeAsStringSync(json); - final cache = CompletionConfiguration.fromFile(file); + final completionConfiguration = + CompletionConfiguration.fromFile(file); expect( - cache.uninstalls, + completionConfiguration.uninstalls, isEmpty, reason: '''Uninstalls should be empty when the value is of an invalid type''', ); }, ); + + test( + '''returns a $CompletionConfiguration with empty installs if file's JSON installs key has a numeric value''', + () { + final tempDirectory = Directory.systemTemp.createTempSync(); + addTearDown(() => tempDirectory.deleteSync(recursive: true)); + + const json = '{"${CompletionConfiguration.installsJsonKey}": 1}'; + final file = File(path.join(tempDirectory.path, 'config.json')) + ..writeAsStringSync(json); + + final completionConfiguration = + CompletionConfiguration.fromFile(file); + expect( + completionConfiguration.installs, + isEmpty, + reason: + '''Installs should be empty when the value is of an invalid type''', + ); + }, + ); }); group('writeTo', () { @@ -127,7 +184,7 @@ void main() { expect( file.existsSync(), isTrue, - reason: 'File should exist after cache creation', + reason: 'File should exist after completionConfiguration creation', ); }); @@ -155,14 +212,22 @@ void main() { addTearDown(() => tempDirectory.deleteSync(recursive: true)); final file = File(path.join(tempDirectory.path, 'config.json')); - final cache = CompletionConfiguration.empty().copyWith( + final completionConfiguration = + CompletionConfiguration.empty().copyWith( + installs: testInstalls, uninstalls: testUninstalls, )..writeTo(file); - final newCache = CompletionConfiguration.fromFile(file); + final newcompletionConfiguration = + CompletionConfiguration.fromFile(file); + expect( + newcompletionConfiguration.installs, + completionConfiguration.installs, + reason: 'Installs should match those defined in the file', + ); expect( - newCache.uninstalls, - cache.uninstalls, + newcompletionConfiguration.uninstalls, + completionConfiguration.uninstalls, reason: 'Uninstalls should match those defined in the file', ); }); @@ -170,82 +235,109 @@ void main() { group('copyWith', () { test('members remain unchanged when nothing is specified', () { - final cache = CompletionConfiguration.empty(); - final newCache = cache.copyWith(); + final completionConfiguration = CompletionConfiguration.empty(); + final newcompletionConfiguration = completionConfiguration.copyWith(); expect( - newCache.uninstalls, - cache.uninstalls, + newcompletionConfiguration.uninstalls, + completionConfiguration.uninstalls, reason: 'Uninstalls should remain unchanged', ); }); test('modifies uninstalls when specified', () { - final cache = CompletionConfiguration.empty(); + final completionConfiguration = CompletionConfiguration.empty(); final uninstalls = testUninstalls; - final newCache = cache.copyWith(uninstalls: uninstalls); + final newcompletionConfiguration = + completionConfiguration.copyWith(uninstalls: uninstalls); expect( - newCache.uninstalls, + newcompletionConfiguration.uninstalls, equals(uninstalls), reason: 'Uninstalls should be modified', ); }); + + test('modifies installs when specified', () { + final completionConfiguration = CompletionConfiguration.empty(); + final installs = testUninstalls; + final newcompletionConfiguration = + completionConfiguration.copyWith(installs: installs); + + expect( + newcompletionConfiguration.uninstalls, + equals(installs), + reason: 'Installs should be modified', + ); + }); }); }); - group('UninstallsExtension', () { + group('ShellCommandsMapExtension', () { group('include', () { - test('adds command to $Uninstalls when not already in', () { + test('adds command to $ShellCommandsMap when not already in', () { const testCommand = 'test_command'; const testShell = SystemShell.bash; - final uninstalls = Uninstalls({}); + final shellCommandsMap = ShellCommandsMap({}); - final newUninstalls = - uninstalls.include(command: testCommand, systemShell: testShell); + final newShellCommadsMap = shellCommandsMap.include( + command: testCommand, + systemShell: testShell, + ); expect( - newUninstalls.contains(command: testCommand, systemShell: testShell), + newShellCommadsMap.contains( + command: testCommand, + systemShell: testShell, + ), isTrue, ); }); - test('does nothing when $Uninstalls already has command', () { + test('does nothing when $ShellCommandsMap already has command', () { const testCommand = 'test_command'; const testShell = SystemShell.bash; - final uninstalls = Uninstalls({ + final shellCommandsMap = ShellCommandsMap({ testShell: UnmodifiableSetView({testCommand}), }); - final newUninstalls = - uninstalls.include(command: testCommand, systemShell: testShell); + final newShellCommadsMap = shellCommandsMap.include( + command: testCommand, + systemShell: testShell, + ); expect( - newUninstalls.contains(command: testCommand, systemShell: testShell), + newShellCommadsMap.contains( + command: testCommand, + systemShell: testShell, + ), isTrue, ); }); - test('adds command $Uninstalls when on a different shell', () { + test('adds command $ShellCommandsMap when on a different shell', () { const testCommand = 'test_command'; const testShell = SystemShell.bash; - final uninstalls = Uninstalls({ + final shellCommandsMap = ShellCommandsMap({ testShell: UnmodifiableSetView({testCommand}), }); const anotherShell = SystemShell.zsh; - final newUninstalls = uninstalls.include( + final newShellCommadsMap = shellCommandsMap.include( command: testCommand, systemShell: anotherShell, ); expect(testShell, isNot(equals(anotherShell))); expect( - newUninstalls.contains(command: testCommand, systemShell: testShell), + newShellCommadsMap.contains( + command: testCommand, + systemShell: testShell, + ), isTrue, ); expect( - newUninstalls.contains( + newShellCommadsMap.contains( command: testCommand, systemShell: anotherShell, ), @@ -255,75 +347,96 @@ void main() { }); group('exclude', () { - test('removes command when in $Uninstalls', () { + test('removes command when in $ShellCommandsMap', () { const testCommand = 'test_command'; const testShell = SystemShell.bash; - final uninstalls = Uninstalls({ + final shellCommandsMap = ShellCommandsMap({ testShell: UnmodifiableSetView({testCommand}), }); - final newUninstalls = - uninstalls.exclude(command: testCommand, systemShell: testShell); + final newShellCommandsMap = shellCommandsMap.exclude( + command: testCommand, + systemShell: testShell, + ); expect( - newUninstalls.contains(command: testCommand, systemShell: testShell), + newShellCommandsMap.contains( + command: testCommand, + systemShell: testShell, + ), isFalse, ); }); - test('does nothing when command not in $Uninstalls', () { + test('does nothing when command not in $ShellCommandsMap', () { const testCommand = 'test_command'; const testShell = SystemShell.bash; - final uninstalls = Uninstalls({}); + final shellCommandsMap = ShellCommandsMap({}); - final newUninstalls = - uninstalls.exclude(command: testCommand, systemShell: testShell); + final newShellCommandsMap = shellCommandsMap.exclude( + command: testCommand, + systemShell: testShell, + ); expect( - newUninstalls.contains(command: testCommand, systemShell: testShell), + newShellCommandsMap.contains( + command: testCommand, + systemShell: testShell, + ), isFalse, ); }); - test('does nothing when command in $Uninstalls is on a different shell', + test( + '''does nothing when command in $ShellCommandsMap is on a different shell''', () { const testCommand = 'test_command'; const testShell = SystemShell.bash; - final uninstalls = Uninstalls({ + final shellCommandsMap = ShellCommandsMap({ testShell: UnmodifiableSetView({testCommand}), }); const anotherShell = SystemShell.zsh; - final newUninstalls = - uninstalls.exclude(command: testCommand, systemShell: anotherShell); + final newShellCommadsMap = shellCommandsMap.exclude( + command: testCommand, + systemShell: anotherShell, + ); expect( - newUninstalls.contains(command: testCommand, systemShell: testShell), + newShellCommadsMap.contains( + command: testCommand, + systemShell: testShell, + ), isTrue, ); }); }); group('contains', () { - test('returns true when command is in $Uninstalls for the given shell', + test( + '''returns true when command is in $ShellCommandsMap for the given shell''', () { const testCommand = 'test_command'; const testShell = SystemShell.bash; - final uninstalls = Uninstalls({ + final shellCommandsMap = ShellCommandsMap({ testShell: UnmodifiableSetView({testCommand}), }); expect( - uninstalls.contains(command: testCommand, systemShell: testShell), + shellCommandsMap.contains( + command: testCommand, + systemShell: testShell, + ), isTrue, ); }); - test('returns false when command is in $Uninstalls for another shell', + test( + '''returns false when command is in $ShellCommandsMap for another shell''', () { const testCommand = 'test_command'; const testShell = SystemShell.bash; - final uninstalls = Uninstalls({ + final shellCommandsMap = ShellCommandsMap({ testShell: UnmodifiableSetView({testCommand}), }); @@ -331,7 +444,10 @@ void main() { expect(testShell, isNot(equals(anotherShell))); expect( - uninstalls.contains(command: testCommand, systemShell: anotherShell), + shellCommandsMap.contains( + command: testCommand, + systemShell: anotherShell, + ), isFalse, ); }); diff --git a/test/src/installer/completion_installation_test.dart b/test/src/installer/completion_installation_test.dart index 046ece6..9a35510 100644 --- a/test/src/installer/completion_installation_test.dart +++ b/test/src/installer/completion_installation_test.dart @@ -515,7 +515,7 @@ void main() { final completionConfigurationFile = installation.completionConfigurationFile; - final uninstalls = Uninstalls({ + final uninstalls = ShellCommandsMap({ systemShell: UnmodifiableSetView({command}), }); CompletionConfiguration.empty() From a375d992e1f08d860849fdcb20d1df222e0ce1d3 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Tue, 23 May 2023 18:32:03 +0100 Subject: [PATCH 29/45] refactor: moved logic to install --- .../completion_command_runner.dart | 24 +++++--------- .../installer/completion_installation.dart | 33 ++++++++++++++++++- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/lib/src/command_runner/completion_command_runner.dart b/lib/src/command_runner/completion_command_runner.dart index edb3617..d0eafc6 100644 --- a/lib/src/command_runner/completion_command_runner.dart +++ b/lib/src/command_runner/completion_command_runner.dart @@ -65,24 +65,18 @@ abstract class CompletionCommandRunner extends CommandRunner { return _completionInstallation = completionInstallation; } + /// The list of commands that should not trigger the auto installation. + static const _reservedCommands = { + HandleCompletionRequestCommand.commandName, + InstallCompletionFilesCommand.commandName, + UnistallCompletionFilesCommand.commandName, + }; + @override @mustCallSuper Future runCommand(ArgResults topLevelResults) async { - final reservedCommands = [ - HandleCompletionRequestCommand.commandName, - InstallCompletionFilesCommand.commandName, - UnistallCompletionFilesCommand.commandName, - ]; - - final completionConfiguration = CompletionConfiguration.fromFile( - completionInstallation.completionConfigurationFile, - ); - final isUninstalled = systemShell != null && - completionConfiguration.uninstalls - .contains(command: executableName, systemShell: systemShell!); if (enableAutoInstall && - !reservedCommands.contains(topLevelResults.command?.name) && - !isUninstalled) { + !_reservedCommands.contains(topLevelResults.command?.name)) { // When auto installing, use error level to display messages. tryInstallCompletionFiles(Level.error); } @@ -95,7 +89,7 @@ abstract class CompletionCommandRunner extends CommandRunner { void tryInstallCompletionFiles(Level level) { try { completionInstallationLogger.level = level; - completionInstallation.install(executableName); + completionInstallation.install(executableName, hard: false); } on CompletionInstallationException catch (e) { completionInstallationLogger.warn(e.toString()); } on Exception catch (e) { diff --git a/lib/src/installer/completion_installation.dart b/lib/src/installer/completion_installation.dart index 425f290..d396645 100644 --- a/lib/src/installer/completion_installation.dart +++ b/lib/src/installer/completion_installation.dart @@ -91,6 +91,7 @@ class CompletionInstallation { } /// Define the [File] in which the completion configuration is stored. + @visibleForTesting File get completionConfigurationFile { return File(path.join(completionConfigDir.path, 'config.json')); } @@ -106,7 +107,12 @@ class CompletionInstallation { /// completion script file. /// - A line in the shell config file (e.g. `.bash_profile`) that sources /// the aforementioned config file. - void install(String rootCommand) { + /// + /// If [hard] is true, it will overwrite the completion configuration files + /// even if they already exist. If false, it will check if the completion + /// configuration is already installed, or if it has been explicitly + /// uninstalled before installing it. + void install(String rootCommand, {bool hard = true}) { final configuration = this.configuration; if (configuration == null) { @@ -116,6 +122,10 @@ class CompletionInstallation { ); } + if (!hard && !_shouldInstall(rootCommand)) { + return; + } + logger.detail( 'Installing completion for the command $rootCommand ' 'on ${configuration.shell.name}', @@ -142,6 +152,27 @@ class CompletionInstallation { .writeTo(completionConfigurationFile); } + /// Wether the completion configuration files for a [rootCommand] should be + /// installed or not. + /// + /// It will return false if the root command is not already installed or it + /// has been explicitly uninstalled. + bool _shouldInstall(String rootCommand) { + final completionConfiguration = CompletionConfiguration.fromFile( + completionConfigurationFile, + ); + final systemShell = configuration!.shell; + final isInstalled = completionConfiguration.installs.contains( + command: rootCommand, + systemShell: systemShell, + ); + final isUninstalled = completionConfiguration.uninstalls.contains( + command: rootCommand, + systemShell: systemShell, + ); + return !isInstalled && !isUninstalled; + } + /// Create a directory in which the completion config files shall be saved. /// If the directory already exists, it will do nothing. /// From b2dfa4bbc44046a3d1426a20e6a202de3c2f6199 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Tue, 23 May 2023 18:32:28 +0100 Subject: [PATCH 30/45] refactor: removed errors --- lib/src/installer/completion_installation.dart | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/src/installer/completion_installation.dart b/lib/src/installer/completion_installation.dart index d396645..2564491 100644 --- a/lib/src/installer/completion_installation.dart +++ b/lib/src/installer/completion_installation.dart @@ -162,15 +162,11 @@ class CompletionInstallation { completionConfigurationFile, ); final systemShell = configuration!.shell; - final isInstalled = completionConfiguration.installs.contains( - command: rootCommand, - systemShell: systemShell, - ); final isUninstalled = completionConfiguration.uninstalls.contains( command: rootCommand, systemShell: systemShell, ); - return !isInstalled && !isUninstalled; + return !isUninstalled; } /// Create a directory in which the completion config files shall be saved. From 366a48059efe6e9d52193949c86fe062a5d2790c Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Tue, 23 May 2023 18:34:04 +0100 Subject: [PATCH 31/45] docs: fixed docs --- lib/src/installer/completion_installation.dart | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/src/installer/completion_installation.dart b/lib/src/installer/completion_installation.dart index 2564491..ca130b8 100644 --- a/lib/src/installer/completion_installation.dart +++ b/lib/src/installer/completion_installation.dart @@ -109,9 +109,8 @@ class CompletionInstallation { /// the aforementioned config file. /// /// If [hard] is true, it will overwrite the completion configuration files - /// even if they already exist. If false, it will check if the completion - /// configuration is already installed, or if it has been explicitly - /// uninstalled before installing it. + /// even if they already exist. If false, it will check if it has been + /// explicitly uninstalled before installing it. void install(String rootCommand, {bool hard = true}) { final configuration = this.configuration; @@ -155,8 +154,7 @@ class CompletionInstallation { /// Wether the completion configuration files for a [rootCommand] should be /// installed or not. /// - /// It will return false if the root command is not already installed or it - /// has been explicitly uninstalled. + /// It will return false if the root command has been explicitly uninstalled. bool _shouldInstall(String rootCommand) { final completionConfiguration = CompletionConfiguration.fromFile( completionConfigurationFile, From 2253015d35fdf1afd481e10795109fbedd73c6e7 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Tue, 23 May 2023 18:59:32 +0100 Subject: [PATCH 32/45] refactor: modify logic --- .../install_completion_files_command.dart | 2 +- .../completion_command_runner.dart | 4 +- .../installer/completion_installation.dart | 6 +-- .../completion_command_runner_test.dart | 37 +++++-------------- .../completion_installation_test.dart | 5 ++- 5 files changed, 18 insertions(+), 36 deletions(-) diff --git a/lib/src/command_runner/commands/install_completion_files_command.dart b/lib/src/command_runner/commands/install_completion_files_command.dart index 79e8d76..76c0e53 100644 --- a/lib/src/command_runner/commands/install_completion_files_command.dart +++ b/lib/src/command_runner/commands/install_completion_files_command.dart @@ -49,7 +49,7 @@ class InstallCompletionFilesCommand extends Command { FutureOr? run() { final verbose = argResults!['verbose'] as bool; final level = verbose ? Level.verbose : Level.info; - runner.tryInstallCompletionFiles(level); + runner.tryInstallCompletionFiles(level, force: true); return null; } } diff --git a/lib/src/command_runner/completion_command_runner.dart b/lib/src/command_runner/completion_command_runner.dart index d0eafc6..f333bbf 100644 --- a/lib/src/command_runner/completion_command_runner.dart +++ b/lib/src/command_runner/completion_command_runner.dart @@ -86,10 +86,10 @@ abstract class CompletionCommandRunner extends CommandRunner { /// Tries to install completion files for the current shell. @internal - void tryInstallCompletionFiles(Level level) { + void tryInstallCompletionFiles(Level level, {bool force = false}) { try { completionInstallationLogger.level = level; - completionInstallation.install(executableName, hard: false); + completionInstallation.install(executableName, force: force); } on CompletionInstallationException catch (e) { completionInstallationLogger.warn(e.toString()); } on Exception catch (e) { diff --git a/lib/src/installer/completion_installation.dart b/lib/src/installer/completion_installation.dart index ca130b8..340360d 100644 --- a/lib/src/installer/completion_installation.dart +++ b/lib/src/installer/completion_installation.dart @@ -108,10 +108,10 @@ class CompletionInstallation { /// - A line in the shell config file (e.g. `.bash_profile`) that sources /// the aforementioned config file. /// - /// If [hard] is true, it will overwrite the completion configuration files + /// If [force] is true, it will overwrite the completion configuration files /// even if they already exist. If false, it will check if it has been /// explicitly uninstalled before installing it. - void install(String rootCommand, {bool hard = true}) { + void install(String rootCommand, {bool force = false}) { final configuration = this.configuration; if (configuration == null) { @@ -121,7 +121,7 @@ class CompletionInstallation { ); } - if (!hard && !_shouldInstall(rootCommand)) { + if (!force && !_shouldInstall(rootCommand)) { return; } diff --git a/test/src/command_runner/completion_command_runner_test.dart b/test/src/command_runner/completion_command_runner_test.dart index f734c97..da34a8b 100644 --- a/test/src/command_runner/completion_command_runner_test.dart +++ b/test/src/command_runner/completion_command_runner_test.dart @@ -1,4 +1,3 @@ -import 'dart:collection'; import 'dart:io'; import 'package:args/command_runner.dart'; @@ -7,7 +6,6 @@ import 'package:cli_completion/installer.dart'; import 'package:cli_completion/parser.dart'; import 'package:mason_logger/mason_logger.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:path/path.dart' as path; import 'package:test/test.dart'; class MockLogger extends Mock implements Logger {} @@ -129,8 +127,9 @@ void main() { await commandRunner.run(['ahoy']); - verify(() => commandRunner.completionInstallation.install('test')) - .called(1); + verify( + () => commandRunner.completionInstallation.install('test'), + ).called(1); verify( () => commandRunner.completionInstallationLogger.level = Level.error, @@ -158,12 +157,9 @@ void main() { ); }); - test('does not auto install when it is unistalled', () async { + test('softly tries to install when enabled', () async { final completionInstallation = _MockCompletionInstallation(); - final tempDirectory = Directory.systemTemp.createTempSync(); - addTearDown(() => tempDirectory.deleteSync(recursive: true)); - final commandRunner = _TestCompletionCommandRunner() ..enableAutoInstall = true ..addCommand(_TestUserCommand()) @@ -172,28 +168,13 @@ void main() { 'SHELL': '/foo/bar/zsh', }; - final completioninstallationFilePath = - path.join(tempDirectory.path, 'test-config.json'); - final completionInstallationFile = File(completioninstallationFilePath); - final uninstalls = Uninstalls({ - commandRunner.systemShell!: - UnmodifiableSetView({commandRunner.executableName}), - }); - CompletionConfiguration.empty() - .copyWith(uninstalls: uninstalls) - .writeTo(completionInstallationFile); - when(() => completionInstallation.completionConfigurationFile) - .thenReturn(completionInstallationFile); - await commandRunner.run(['ahoy']); - verifyNever( - () => commandRunner.completionInstallation - .install(commandRunner.executableName), - ); - verifyNever( - () => commandRunner.completionInstallationLogger.level = any(), - ); + verify( + () => commandRunner.completionInstallation.install( + commandRunner.executableName, + ), + ).called(1); }); }); diff --git a/test/src/installer/completion_installation_test.dart b/test/src/installer/completion_installation_test.dart index d4571b4..fc6a92c 100644 --- a/test/src/installer/completion_installation_test.dart +++ b/test/src/installer/completion_installation_test.dart @@ -496,7 +496,8 @@ void main() { }, ); - test('removes command from $CompletionConfiguration when uninstalled', + test( + '''removes command from $CompletionConfiguration uninstalls when forced to install''', () { const systemShell = SystemShell.zsh; final installation = CompletionInstallation.fromSystemShell( @@ -530,7 +531,7 @@ void main() { '''The completion configuration should contain the uninstall for the command before install''', ); - installation.install('very_good'); + installation.install('very_good', force: true); final newCompletionConfiguration = CompletionConfiguration.fromFile(completionConfigurationFile); From 0a6b21fa251bd084fd5d1e3528ae476369d607b4 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Tue, 23 May 2023 19:02:37 +0100 Subject: [PATCH 33/45] test: soft install --- .../completion_installation_test.dart | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/test/src/installer/completion_installation_test.dart b/test/src/installer/completion_installation_test.dart index fc6a92c..8262016 100644 --- a/test/src/installer/completion_installation_test.dart +++ b/test/src/installer/completion_installation_test.dart @@ -496,6 +496,54 @@ void main() { }, ); + test( + '''doesn't remove command from $CompletionConfiguration uninstalls when not forced to install''', + () { + const systemShell = SystemShell.zsh; + final installation = CompletionInstallation.fromSystemShell( + logger: logger, + isWindowsOverride: false, + environmentOverride: { + 'HOME': tempDir.path, + }, + systemShell: systemShell, + ); + + File(path.join(tempDir.path, '.zshrc')).createSync(); + + const command = 'very_good'; + final completionConfigurationFile = + installation.completionConfigurationFile; + + final uninstalls = Uninstalls({ + systemShell: UnmodifiableSetView({command}), + }); + CompletionConfiguration.empty() + .copyWith(uninstalls: uninstalls) + .writeTo(completionConfigurationFile); + final completionConfiguration = + CompletionConfiguration.fromFile(completionConfigurationFile); + expect( + completionConfiguration.uninstalls + .contains(command: command, systemShell: systemShell), + isTrue, + reason: + '''The completion configuration should contain the uninstall for the command before install''', + ); + + installation.install('very_good', force: true); + + final newCompletionConfiguration = + CompletionConfiguration.fromFile(completionConfigurationFile); + expect( + newCompletionConfiguration.uninstalls + .contains(command: command, systemShell: systemShell), + isTrue, + reason: + '''The completion configuration should still contain the uninstall for the command after soft install''', + ); + }); + test( '''removes command from $CompletionConfiguration uninstalls when forced to install''', () { From ab652bf6169f6b75574d77c4c4bcce6b0fe5b525 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Tue, 23 May 2023 19:03:50 +0100 Subject: [PATCH 34/45] docs: fixed docs --- lib/src/installer/completion_installation.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/installer/completion_installation.dart b/lib/src/installer/completion_installation.dart index 340360d..d01c075 100644 --- a/lib/src/installer/completion_installation.dart +++ b/lib/src/installer/completion_installation.dart @@ -108,9 +108,9 @@ class CompletionInstallation { /// - A line in the shell config file (e.g. `.bash_profile`) that sources /// the aforementioned config file. /// - /// If [force] is true, it will overwrite the completion configuration files - /// even if they already exist. If false, it will check if it has been - /// explicitly uninstalled before installing it. + /// If [force] is true, it will overwrite the command's completion files even + /// if they already exist. If false, it will check if it has been explicitly + /// uninstalled before installing it. void install(String rootCommand, {bool force = false}) { final configuration = this.configuration; From c741dd323690b714326c8c95dd314ceef23a14e7 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Tue, 23 May 2023 19:07:10 +0100 Subject: [PATCH 35/45] refactor: removed mocks --- .../install_completion_files_command_test.dart | 16 +++++++++------- .../uninstall_completion_files_command_test.dart | 7 ------- .../completion_command_runner_test.dart | 16 +--------------- 3 files changed, 10 insertions(+), 29 deletions(-) diff --git a/test/src/command_runner/commands/install_completion_files_command_test.dart b/test/src/command_runner/commands/install_completion_files_command_test.dart index 66231c3..8b152d4 100644 --- a/test/src/command_runner/commands/install_completion_files_command_test.dart +++ b/test/src/command_runner/commands/install_completion_files_command_test.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:cli_completion/cli_completion.dart'; import 'package:cli_completion/installer.dart'; import 'package:mason_logger/mason_logger.dart'; @@ -29,11 +27,6 @@ void main() { setUp(() { commandRunner = _TestCompletionCommandRunner(); - - final completionInstallation = commandRunner.completionInstallation; - final completionInstallationFile = File('test-config.json'); - when(() => completionInstallation.completionConfigurationFile) - .thenReturn(completionInstallationFile); }); test('can be instantiated', () { @@ -52,6 +45,15 @@ void main() { }); group('install completion files', () { + test('forces to install', () async { + await commandRunner.run(['install-completion-files']); + + verify( + () => commandRunner.completionInstallation + .install(commandRunner.executableName, force: true), + ).called(1); + }); + test('when normal', () async { await commandRunner.run(['install-completion-files']); diff --git a/test/src/command_runner/commands/uninstall_completion_files_command_test.dart b/test/src/command_runner/commands/uninstall_completion_files_command_test.dart index bb77349..f1309f9 100644 --- a/test/src/command_runner/commands/uninstall_completion_files_command_test.dart +++ b/test/src/command_runner/commands/uninstall_completion_files_command_test.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:cli_completion/cli_completion.dart'; import 'package:cli_completion/installer.dart'; import 'package:mason_logger/mason_logger.dart'; @@ -29,11 +27,6 @@ void main() { setUp(() { commandRunner = _TestCompletionCommandRunner(); - - final completionInstallation = commandRunner.completionInstallation; - final completionInstallationFile = File('test-config.json'); - when(() => completionInstallation.completionConfigurationFile) - .thenReturn(completionInstallationFile); }); test('can be instantiated', () { diff --git a/test/src/command_runner/completion_command_runner_test.dart b/test/src/command_runner/completion_command_runner_test.dart index da34a8b..64b3e55 100644 --- a/test/src/command_runner/completion_command_runner_test.dart +++ b/test/src/command_runner/completion_command_runner_test.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:args/command_runner.dart'; import 'package:cli_completion/cli_completion.dart'; import 'package:cli_completion/installer.dart'; @@ -117,9 +115,6 @@ void main() { group('auto install', () { test('Tries to install completion files on test subcommand', () async { final completionInstallation = _MockCompletionInstallation(); - final completionInstallationFile = File('test-config.json'); - when(() => completionInstallation.completionConfigurationFile) - .thenReturn(completionInstallationFile); final commandRunner = _TestCompletionCommandRunner() ..addCommand(_TestUserCommand()) @@ -139,10 +134,6 @@ void main() { test('does not auto install when it is disabled', () async { final completionInstallation = _MockCompletionInstallation(); - final completionInstallationFile = File('test-config.json'); - when(() => completionInstallation.completionConfigurationFile) - .thenReturn(completionInstallationFile); - final commandRunner = _TestCompletionCommandRunner() ..enableAutoInstall = false ..addCommand(_TestUserCommand()) @@ -182,9 +173,7 @@ void main() { 'When it throws CompletionInstallationException, it logs as a warning', () async { final completionInstallation = _MockCompletionInstallation(); - final completionInstallationFile = File('test-config.json'); - when(() => completionInstallation.completionConfigurationFile) - .thenReturn(completionInstallationFile); + final commandRunner = _TestCompletionCommandRunner() ..addCommand(_TestUserCommand()) ..mockCompletionInstallation = completionInstallation; @@ -205,9 +194,6 @@ void main() { test('When an unknown exception happens during a install, it logs as error', () async { final completionInstallation = _MockCompletionInstallation(); - final completionInstallationFile = File('test-config.json'); - when(() => completionInstallation.completionConfigurationFile) - .thenReturn(completionInstallationFile); final commandRunner = _TestCompletionCommandRunner() ..addCommand(_TestUserCommand()) From 135e0d1391aeff88b6e753488c80e0efcf2f11e5 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Tue, 23 May 2023 19:08:40 +0100 Subject: [PATCH 36/45] refactor: test name --- .../commands/install_completion_files_command_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/src/command_runner/commands/install_completion_files_command_test.dart b/test/src/command_runner/commands/install_completion_files_command_test.dart index 8b152d4..d2f1346 100644 --- a/test/src/command_runner/commands/install_completion_files_command_test.dart +++ b/test/src/command_runner/commands/install_completion_files_command_test.dart @@ -45,7 +45,7 @@ void main() { }); group('install completion files', () { - test('forces to install', () async { + test('forces install', () async { await commandRunner.run(['install-completion-files']); verify( From 39128a1567e64d3f81dd74e9f6d40f8d11a17f6a Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Tue, 23 May 2023 19:09:48 +0100 Subject: [PATCH 37/45] test: removed redundant variable --- test/src/command_runner/completion_command_runner_test.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/src/command_runner/completion_command_runner_test.dart b/test/src/command_runner/completion_command_runner_test.dart index 64b3e55..275dd1f 100644 --- a/test/src/command_runner/completion_command_runner_test.dart +++ b/test/src/command_runner/completion_command_runner_test.dart @@ -114,18 +114,15 @@ void main() { group('auto install', () { test('Tries to install completion files on test subcommand', () async { - final completionInstallation = _MockCompletionInstallation(); - final commandRunner = _TestCompletionCommandRunner() ..addCommand(_TestUserCommand()) - ..mockCompletionInstallation = completionInstallation; + ..mockCompletionInstallation = _MockCompletionInstallation(); await commandRunner.run(['ahoy']); verify( () => commandRunner.completionInstallation.install('test'), ).called(1); - verify( () => commandRunner.completionInstallationLogger.level = Level.error, ).called(1); From 15f305fc51c2278ba62bf75b5a402ff130c7b1c5 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Tue, 23 May 2023 19:11:07 +0100 Subject: [PATCH 38/45] test: simplified --- .../completion_command_runner_test.dart | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/test/src/command_runner/completion_command_runner_test.dart b/test/src/command_runner/completion_command_runner_test.dart index 275dd1f..7e6e758 100644 --- a/test/src/command_runner/completion_command_runner_test.dart +++ b/test/src/command_runner/completion_command_runner_test.dart @@ -129,12 +129,10 @@ void main() { }); test('does not auto install when it is disabled', () async { - final completionInstallation = _MockCompletionInstallation(); - final commandRunner = _TestCompletionCommandRunner() ..enableAutoInstall = false ..addCommand(_TestUserCommand()) - ..mockCompletionInstallation = completionInstallation; + ..mockCompletionInstallation = _MockCompletionInstallation(); await commandRunner.run(['ahoy']); @@ -146,12 +144,10 @@ void main() { }); test('softly tries to install when enabled', () async { - final completionInstallation = _MockCompletionInstallation(); - final commandRunner = _TestCompletionCommandRunner() ..enableAutoInstall = true ..addCommand(_TestUserCommand()) - ..mockCompletionInstallation = completionInstallation + ..mockCompletionInstallation = _MockCompletionInstallation() ..environmentOverride = { 'SHELL': '/foo/bar/zsh', }; @@ -169,11 +165,9 @@ void main() { test( 'When it throws CompletionInstallationException, it logs as a warning', () async { - final completionInstallation = _MockCompletionInstallation(); - final commandRunner = _TestCompletionCommandRunner() ..addCommand(_TestUserCommand()) - ..mockCompletionInstallation = completionInstallation; + ..mockCompletionInstallation = _MockCompletionInstallation(); when( () => commandRunner.completionInstallation.install('test'), @@ -190,11 +184,9 @@ void main() { test('When an unknown exception happens during a install, it logs as error', () async { - final completionInstallation = _MockCompletionInstallation(); - final commandRunner = _TestCompletionCommandRunner() ..addCommand(_TestUserCommand()) - ..mockCompletionInstallation = completionInstallation; + ..mockCompletionInstallation = _MockCompletionInstallation(); when( () => commandRunner.completionInstallation.install('test'), From 190338fb218f84129a24792857d62a58f2df1c54 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Tue, 23 May 2023 19:14:50 +0100 Subject: [PATCH 39/45] test: small typo --- test/src/installer/completion_installation_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/src/installer/completion_installation_test.dart b/test/src/installer/completion_installation_test.dart index 8262016..2e6addb 100644 --- a/test/src/installer/completion_installation_test.dart +++ b/test/src/installer/completion_installation_test.dart @@ -531,7 +531,7 @@ void main() { '''The completion configuration should contain the uninstall for the command before install''', ); - installation.install('very_good', force: true); + installation.install(command); final newCompletionConfiguration = CompletionConfiguration.fromFile(completionConfigurationFile); @@ -579,7 +579,7 @@ void main() { '''The completion configuration should contain the uninstall for the command before install''', ); - installation.install('very_good', force: true); + installation.install(command, force: true); final newCompletionConfiguration = CompletionConfiguration.fromFile(completionConfigurationFile); From f0fbaac079602e9a86b8fbd8dfe2ebff62316f3d Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Tue, 23 May 2023 19:22:47 +0100 Subject: [PATCH 40/45] feat: added installation logic --- lib/src/installer/completion_installation.dart | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/src/installer/completion_installation.dart b/lib/src/installer/completion_installation.dart index ce4610d..a28435d 100644 --- a/lib/src/installer/completion_installation.dart +++ b/lib/src/installer/completion_installation.dart @@ -109,7 +109,8 @@ class CompletionInstallation { /// the aforementioned config file. /// /// If [force] is true, it will overwrite the command's completion files even - /// if they already exist. If false, it will check if it has been explicitly + /// if they already exist. If false, it will check if the completion + /// configuration is already installed, or if it has been explicitly /// uninstalled before installing it. void install(String rootCommand, {bool force = false}) { final configuration = this.configuration; @@ -158,17 +159,22 @@ class CompletionInstallation { /// Wether the completion configuration files for a [rootCommand] should be /// installed or not. /// - /// It will return false if the root command has been explicitly uninstalled. + /// It will return false if the root command is already installed or it + /// has been explicitly uninstalled. bool _shouldInstall(String rootCommand) { final completionConfiguration = CompletionConfiguration.fromFile( completionConfigurationFile, ); final systemShell = configuration!.shell; + final isInstalled = completionConfiguration.installs.contains( + command: rootCommand, + systemShell: systemShell, + ); final isUninstalled = completionConfiguration.uninstalls.contains( command: rootCommand, systemShell: systemShell, ); - return !isUninstalled; + return !isInstalled && !isUninstalled; } /// Create a directory in which the completion config files shall be saved. From 2944d43d82ee0ff0c274c11df94ae0f277e1ed6c Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Tue, 23 May 2023 19:23:46 +0100 Subject: [PATCH 41/45] docs: typo --- lib/src/installer/completion_installation.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/src/installer/completion_installation.dart b/lib/src/installer/completion_installation.dart index a28435d..9cb0d4d 100644 --- a/lib/src/installer/completion_installation.dart +++ b/lib/src/installer/completion_installation.dart @@ -109,9 +109,8 @@ class CompletionInstallation { /// the aforementioned config file. /// /// If [force] is true, it will overwrite the command's completion files even - /// if they already exist. If false, it will check if the completion - /// configuration is already installed, or if it has been explicitly - /// uninstalled before installing it. + /// if they already exist. If false, it will check if is already installed, or + /// if it has been explicitly uninstalled before installing it. void install(String rootCommand, {bool force = false}) { final configuration = this.configuration; From d8eae5f3c4f40b4bdb0a214babc845b6fbe77ae0 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Wed, 24 May 2023 14:59:40 +0100 Subject: [PATCH 42/45] fix: renamed merges --- .../completion_configuration_test.dart | 111 ++++++++++++------ .../completion_installation_test.dart | 4 +- 2 files changed, 76 insertions(+), 39 deletions(-) diff --git a/test/src/installer/completion_configuration_test.dart b/test/src/installer/completion_configuration_test.dart index c3cc54b..8013eab 100644 --- a/test/src/installer/completion_configuration_test.dart +++ b/test/src/installer/completion_configuration_test.dart @@ -454,58 +454,71 @@ void main() { }); }); - group('UninstallsExtension', () { + group('ShellCommandsMapExtension', () { group('include', () { - test('adds command to $Uninstalls when not already in', () { + test('adds command to $ShellCommandsMap when not already in', () { const testCommand = 'test_command'; const testShell = SystemShell.bash; - final uninstalls = Uninstalls({}); + final shellCommandsMap = ShellCommandsMap({}); - final newUninstalls = - uninstalls.include(command: testCommand, systemShell: testShell); + final newShellCommadsMap = shellCommandsMap.include( + command: testCommand, + systemShell: testShell, + ); expect( - newUninstalls.contains(command: testCommand, systemShell: testShell), + newShellCommadsMap.contains( + command: testCommand, + systemShell: testShell, + ), isTrue, ); }); - test('does nothing when $Uninstalls already has command', () { + test('does nothing when $ShellCommandsMap already has command', () { const testCommand = 'test_command'; const testShell = SystemShell.bash; - final uninstalls = Uninstalls({ + final shellCommandsMap = ShellCommandsMap({ testShell: UnmodifiableSetView({testCommand}), }); - final newUninstalls = - uninstalls.include(command: testCommand, systemShell: testShell); + final newShellCommadsMap = shellCommandsMap.include( + command: testCommand, + systemShell: testShell, + ); expect( - newUninstalls.contains(command: testCommand, systemShell: testShell), + newShellCommadsMap.contains( + command: testCommand, + systemShell: testShell, + ), isTrue, ); }); - test('adds command $Uninstalls when on a different shell', () { + test('adds command $ShellCommandsMap when on a different shell', () { const testCommand = 'test_command'; const testShell = SystemShell.bash; - final uninstalls = Uninstalls({ + final shellCommandsMap = ShellCommandsMap({ testShell: UnmodifiableSetView({testCommand}), }); const anotherShell = SystemShell.zsh; - final newUninstalls = uninstalls.include( + final newShellCommadsMap = shellCommandsMap.include( command: testCommand, systemShell: anotherShell, ); expect(testShell, isNot(equals(anotherShell))); expect( - newUninstalls.contains(command: testCommand, systemShell: testShell), + newShellCommadsMap.contains( + command: testCommand, + systemShell: testShell, + ), isTrue, ); expect( - newUninstalls.contains( + newShellCommadsMap.contains( command: testCommand, systemShell: anotherShell, ), @@ -515,75 +528,96 @@ void main() { }); group('exclude', () { - test('removes command when in $Uninstalls', () { + test('removes command when in $ShellCommandsMap', () { const testCommand = 'test_command'; const testShell = SystemShell.bash; - final uninstalls = Uninstalls({ + final shellCommandsMap = ShellCommandsMap({ testShell: UnmodifiableSetView({testCommand}), }); - final newUninstalls = - uninstalls.exclude(command: testCommand, systemShell: testShell); + final newShellCommandsMap = shellCommandsMap.exclude( + command: testCommand, + systemShell: testShell, + ); expect( - newUninstalls.contains(command: testCommand, systemShell: testShell), + newShellCommandsMap.contains( + command: testCommand, + systemShell: testShell, + ), isFalse, ); }); - test('does nothing when command not in $Uninstalls', () { + test('does nothing when command not in $ShellCommandsMap', () { const testCommand = 'test_command'; const testShell = SystemShell.bash; - final uninstalls = Uninstalls({}); + final shellCommandsMap = ShellCommandsMap({}); - final newUninstalls = - uninstalls.exclude(command: testCommand, systemShell: testShell); + final newShellCommandsMap = shellCommandsMap.exclude( + command: testCommand, + systemShell: testShell, + ); expect( - newUninstalls.contains(command: testCommand, systemShell: testShell), + newShellCommandsMap.contains( + command: testCommand, + systemShell: testShell, + ), isFalse, ); }); - test('does nothing when command in $Uninstalls is on a different shell', + test( + '''does nothing when command in $ShellCommandsMap is on a different shell''', () { const testCommand = 'test_command'; const testShell = SystemShell.bash; - final uninstalls = Uninstalls({ + final shellCommandsMap = ShellCommandsMap({ testShell: UnmodifiableSetView({testCommand}), }); const anotherShell = SystemShell.zsh; - final newUninstalls = - uninstalls.exclude(command: testCommand, systemShell: anotherShell); + final newShellCommadsMap = shellCommandsMap.exclude( + command: testCommand, + systemShell: anotherShell, + ); expect( - newUninstalls.contains(command: testCommand, systemShell: testShell), + newShellCommadsMap.contains( + command: testCommand, + systemShell: testShell, + ), isTrue, ); }); }); group('contains', () { - test('returns true when command is in $Uninstalls for the given shell', + test( + '''returns true when command is in $ShellCommandsMap for the given shell''', () { const testCommand = 'test_command'; const testShell = SystemShell.bash; - final uninstalls = Uninstalls({ + final shellCommandsMap = ShellCommandsMap({ testShell: UnmodifiableSetView({testCommand}), }); expect( - uninstalls.contains(command: testCommand, systemShell: testShell), + shellCommandsMap.contains( + command: testCommand, + systemShell: testShell, + ), isTrue, ); }); - test('returns false when command is in $Uninstalls for another shell', + test( + '''returns false when command is in $ShellCommandsMap for another shell''', () { const testCommand = 'test_command'; const testShell = SystemShell.bash; - final uninstalls = Uninstalls({ + final shellCommandsMap = ShellCommandsMap({ testShell: UnmodifiableSetView({testCommand}), }); @@ -591,7 +625,10 @@ void main() { expect(testShell, isNot(equals(anotherShell))); expect( - uninstalls.contains(command: testCommand, systemShell: anotherShell), + shellCommandsMap.contains( + command: testCommand, + systemShell: anotherShell, + ), isFalse, ); }); diff --git a/test/src/installer/completion_installation_test.dart b/test/src/installer/completion_installation_test.dart index 7502d4b..23578c8 100644 --- a/test/src/installer/completion_installation_test.dart +++ b/test/src/installer/completion_installation_test.dart @@ -515,7 +515,7 @@ void main() { final completionConfigurationFile = installation.completionConfigurationFile; - final uninstalls = Uninstalls({ + final uninstalls = ShellCommandsMap({ systemShell: UnmodifiableSetView({command}), }); CompletionConfiguration.empty() @@ -562,7 +562,7 @@ void main() { const command = 'very_good'; final completionConfigurationFile = installation.completionConfigurationFile; - + final uninstalls = ShellCommandsMap({ systemShell: UnmodifiableSetView({command}), }); From 743e6d6ab54d8442b5d806b39ed79762938f02fe Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Wed, 24 May 2023 15:03:56 +0100 Subject: [PATCH 43/45] refactor: removed repeated tests --- .../completion_configuration_test.dart | 181 ------------------ 1 file changed, 181 deletions(-) diff --git a/test/src/installer/completion_configuration_test.dart b/test/src/installer/completion_configuration_test.dart index 8013eab..1673520 100644 --- a/test/src/installer/completion_configuration_test.dart +++ b/test/src/installer/completion_configuration_test.dart @@ -453,185 +453,4 @@ void main() { }); }); }); - - group('ShellCommandsMapExtension', () { - group('include', () { - test('adds command to $ShellCommandsMap when not already in', () { - const testCommand = 'test_command'; - const testShell = SystemShell.bash; - final shellCommandsMap = ShellCommandsMap({}); - - final newShellCommadsMap = shellCommandsMap.include( - command: testCommand, - systemShell: testShell, - ); - - expect( - newShellCommadsMap.contains( - command: testCommand, - systemShell: testShell, - ), - isTrue, - ); - }); - - test('does nothing when $ShellCommandsMap already has command', () { - const testCommand = 'test_command'; - const testShell = SystemShell.bash; - final shellCommandsMap = ShellCommandsMap({ - testShell: UnmodifiableSetView({testCommand}), - }); - - final newShellCommadsMap = shellCommandsMap.include( - command: testCommand, - systemShell: testShell, - ); - - expect( - newShellCommadsMap.contains( - command: testCommand, - systemShell: testShell, - ), - isTrue, - ); - }); - - test('adds command $ShellCommandsMap when on a different shell', () { - const testCommand = 'test_command'; - const testShell = SystemShell.bash; - final shellCommandsMap = ShellCommandsMap({ - testShell: UnmodifiableSetView({testCommand}), - }); - - const anotherShell = SystemShell.zsh; - final newShellCommadsMap = shellCommandsMap.include( - command: testCommand, - systemShell: anotherShell, - ); - expect(testShell, isNot(equals(anotherShell))); - - expect( - newShellCommadsMap.contains( - command: testCommand, - systemShell: testShell, - ), - isTrue, - ); - expect( - newShellCommadsMap.contains( - command: testCommand, - systemShell: anotherShell, - ), - isTrue, - ); - }); - }); - - group('exclude', () { - test('removes command when in $ShellCommandsMap', () { - const testCommand = 'test_command'; - const testShell = SystemShell.bash; - final shellCommandsMap = ShellCommandsMap({ - testShell: UnmodifiableSetView({testCommand}), - }); - - final newShellCommandsMap = shellCommandsMap.exclude( - command: testCommand, - systemShell: testShell, - ); - - expect( - newShellCommandsMap.contains( - command: testCommand, - systemShell: testShell, - ), - isFalse, - ); - }); - - test('does nothing when command not in $ShellCommandsMap', () { - const testCommand = 'test_command'; - const testShell = SystemShell.bash; - final shellCommandsMap = ShellCommandsMap({}); - - final newShellCommandsMap = shellCommandsMap.exclude( - command: testCommand, - systemShell: testShell, - ); - - expect( - newShellCommandsMap.contains( - command: testCommand, - systemShell: testShell, - ), - isFalse, - ); - }); - - test( - '''does nothing when command in $ShellCommandsMap is on a different shell''', - () { - const testCommand = 'test_command'; - const testShell = SystemShell.bash; - final shellCommandsMap = ShellCommandsMap({ - testShell: UnmodifiableSetView({testCommand}), - }); - - const anotherShell = SystemShell.zsh; - final newShellCommadsMap = shellCommandsMap.exclude( - command: testCommand, - systemShell: anotherShell, - ); - - expect( - newShellCommadsMap.contains( - command: testCommand, - systemShell: testShell, - ), - isTrue, - ); - }); - }); - - group('contains', () { - test( - '''returns true when command is in $ShellCommandsMap for the given shell''', - () { - const testCommand = 'test_command'; - const testShell = SystemShell.bash; - final shellCommandsMap = ShellCommandsMap({ - testShell: UnmodifiableSetView({testCommand}), - }); - - expect( - shellCommandsMap.contains( - command: testCommand, - systemShell: testShell, - ), - isTrue, - ); - }); - - test( - '''returns false when command is in $ShellCommandsMap for another shell''', - () { - const testCommand = 'test_command'; - const testShell = SystemShell.bash; - final shellCommandsMap = ShellCommandsMap({ - testShell: UnmodifiableSetView({testCommand}), - }); - - const anotherShell = SystemShell.zsh; - expect(testShell, isNot(equals(anotherShell))); - - expect( - shellCommandsMap.contains( - command: testCommand, - systemShell: anotherShell, - ), - isFalse, - ); - }); - }); - }); } From 1fd1d43584e3c06236a413b801b87891722f11d5 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Wed, 24 May 2023 15:14:12 +0100 Subject: [PATCH 44/45] test: ammended tests --- .../completion_configuration_test.dart | 2 +- .../completion_installation_test.dart | 43 ++++++++++++++++++- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/test/src/installer/completion_configuration_test.dart b/test/src/installer/completion_configuration_test.dart index 1673520..6b962fe 100644 --- a/test/src/installer/completion_configuration_test.dart +++ b/test/src/installer/completion_configuration_test.dart @@ -265,7 +265,7 @@ void main() { completionConfiguration.copyWith(installs: installs); expect( - newcompletionConfiguration.uninstalls, + newcompletionConfiguration.installs, equals(installs), reason: 'Installs should be modified', ); diff --git a/test/src/installer/completion_installation_test.dart b/test/src/installer/completion_installation_test.dart index 23578c8..0cfff6d 100644 --- a/test/src/installer/completion_installation_test.dart +++ b/test/src/installer/completion_installation_test.dart @@ -296,7 +296,7 @@ void main() { }); test( - 'installing completion for a command when it is already installed', + 'installs completion for a an already installed command when forced', () { final installation = CompletionInstallation( logger: logger, @@ -325,7 +325,7 @@ void main() { reset(logger); // install again - installation.install('very_good'); + installation.install('very_good', force: true); verify( () => logger.warn( @@ -371,6 +371,45 @@ void main() { }, ); + test( + '''avoid installing completion for an already installed command''', + () { + final installation = CompletionInstallation( + logger: logger, + isWindows: false, + environment: { + 'HOME': tempDir.path, + }, + configuration: zshConfiguration, + ); + + File(path.join(tempDir.path, '.zshrc')).createSync(); + + installation.install('very_good'); + + verify(() => logger.level = Level.info).called(1); + + verify( + () => logger.info( + '\n' + 'Completion files installed. To enable completion, run the ' + 'following command in your shell:\n' + 'source ${path.join(tempDir.path, '.zshrc')}\n', + ), + ).called(1); + + reset(logger); + + // Install again + installation.install('very_good'); + + verifyNever(() => logger.detail(any())); + verifyNever(() => logger.warn(any())); + verifyNever(() => logger.level = Level.debug); + verifyNever(() => logger.info(any())); + }, + ); + test( 'installing completion for two different commands', () { From b8ae8ab6836fac9a31b0756f2dc7f28eaa839bb8 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Wed, 24 May 2023 15:21:48 +0100 Subject: [PATCH 45/45] refactor: renamed tests to "remains the same" --- .../completion_configuration_test.dart | 30 ++++--------------- 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/test/src/installer/completion_configuration_test.dart b/test/src/installer/completion_configuration_test.dart index 6b962fe..d9e9371 100644 --- a/test/src/installer/completion_configuration_test.dart +++ b/test/src/installer/completion_configuration_test.dart @@ -294,7 +294,7 @@ void main() { ); }); - test('does nothing when $ShellCommandsMap already has command', () { + test('remains the same when $ShellCommandsMap already has command', () { const testCommand = 'test_command'; const testShell = SystemShell.bash; final shellCommandsMap = ShellCommandsMap({ @@ -306,13 +306,7 @@ void main() { systemShell: testShell, ); - expect( - newShellCommadsMap.contains( - command: testCommand, - systemShell: testShell, - ), - isTrue, - ); + expect(newShellCommadsMap, equals(shellCommandsMap)); }); test('adds command $ShellCommandsMap when on a different shell', () { @@ -368,7 +362,7 @@ void main() { ); }); - test('does nothing when command not in $ShellCommandsMap', () { + test('remains the same when command not in $ShellCommandsMap', () { const testCommand = 'test_command'; const testShell = SystemShell.bash; final shellCommandsMap = ShellCommandsMap({}); @@ -378,17 +372,11 @@ void main() { systemShell: testShell, ); - expect( - newShellCommandsMap.contains( - command: testCommand, - systemShell: testShell, - ), - isFalse, - ); + expect(newShellCommandsMap, equals(shellCommandsMap)); }); test( - '''does nothing when command in $ShellCommandsMap is on a different shell''', + '''remains the same when command in $ShellCommandsMap is on a different shell''', () { const testCommand = 'test_command'; const testShell = SystemShell.bash; @@ -402,13 +390,7 @@ void main() { systemShell: anotherShell, ); - expect( - newShellCommadsMap.contains( - command: testCommand, - systemShell: testShell, - ), - isTrue, - ); + expect(newShellCommadsMap, equals(shellCommandsMap)); }); });