From 6ce5d9ab92c67be242604fa764d911b03c312d58 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Thu, 18 May 2023 12:11:34 +0100 Subject: [PATCH 01/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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 a375d992e1f08d860849fdcb20d1df222e0ce1d3 Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Tue, 23 May 2023 18:32:03 +0100 Subject: [PATCH 20/30] 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 21/30] 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 22/30] 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 23/30] 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 24/30] 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 25/30] 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 26/30] 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 27/30] 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 28/30] 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 29/30] 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 30/30] 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);