From 22f6174214fadaa7bba6d9ed2d9c753ccfca9d6c Mon Sep 17 00:00:00 2001 From: Thomas Shephard Date: Thu, 15 Feb 2024 11:59:08 +0000 Subject: [PATCH] Add option input methods (#16) * Add option input methods * Update README file to include option input methods * Add unit tests for get option method * Add unit tests for get yes/no option method --- .../InputTests/OptionTests/OptionTests.cs | 54 +++++++++++++++++++ .../OptionTests/YesNoOptionTests.cs | 45 ++++++++++++++++ IOUtils/Input/OptionInput.cs | 30 +++++++++++ README.md | 20 ++++++- 4 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 IOUtils.Tests/InputTests/OptionTests/OptionTests.cs create mode 100644 IOUtils.Tests/InputTests/OptionTests/YesNoOptionTests.cs create mode 100644 IOUtils/Input/OptionInput.cs diff --git a/IOUtils.Tests/InputTests/OptionTests/OptionTests.cs b/IOUtils.Tests/InputTests/OptionTests/OptionTests.cs new file mode 100644 index 0000000..79a406a --- /dev/null +++ b/IOUtils.Tests/InputTests/OptionTests/OptionTests.cs @@ -0,0 +1,54 @@ +using IOUtils.Input; +using NUnit.Framework; + +namespace IOUtils.Tests.InputTests.OptionTests; + +public class OptionTests { + private const string Question = "Example question text"; + + [TestCase("1", new[] {"Option 1", "Option 2"}, "Option 1")] + [TestCase("2", new[] {"Option 1", "Option 2"}, "Option 2")] + [TestCase("3", new[] {"Option 1", "Option 2", "Option 3"}, "Option 3")] + [TestCase("4", new[] {"Option 1", "Option 2", "Option 3", "Option 4"}, "Option 4")] + [TestCase("3", new[] {"Option 1", "Option 2", "Option 3", "Option 4", "Option 5"}, "Option 3")] + public void OptionInput_Options_ValidInput_ReturnsValue(string input, string[] options, string expected) { + MockProvider mockProvider = new(input); + + string actual = OptionInput.GetOption(Question, options, mockProvider); + + Assert.That(actual, Is.EqualTo(expected)); + } + + [TestCase("6", new[] {"Option 1", "Option 2"})] + [TestCase("0", new[] {"Option 1", "Option 2"})] + [TestCase("1.5", new[] {"Option 1", "Option 2"})] + [TestCase("1.0", new[] {"Option 1", "Option 2", "Option 3"})] + [TestCase("0", new[] {"Option 1", "Option 2", "Option 3", "Option 4"})] + [TestCase("-1", new[] {"Option 1", "Option 2", "Option 3"})] + [TestCase("abc", new[] {"Option 1", "Option 2"})] + [TestCase("Yes", new[] {"Option 1", "Option 2", "Option 3"})] + [TestCase("No", new[] {"Option 1", "Option 2"})] + [TestCase("", new[] {"Option 1", "Option 2", "Option 3", "Option 4"})] + public void OptionInput_Options_InvalidInput_ErrorOutput(string input, string[] options) { + MockProvider mockProvider = new(input); + + Assert.Throws(() => OptionInput.GetOption(Question, options, mockProvider)); + + string expectedQuestion = Question; + + for (int i = 0; i < options.Length; i++) { + expectedQuestion += $"{Environment.NewLine}{i + 1}. {options[i]}"; + } + + string expectedErrorMessage = $"That was not valid, enter a whole number between 1 and {options.Length}"; + + string[] expected = new[] { expectedQuestion, expectedErrorMessage }; + + Assert.That(mockProvider.OutputLines, Is.EquivalentTo(expected)); + } + + [Test] + public void OptionInput_Options_EmptyOptions_ThrowsException() { + Assert.Throws(() => OptionInput.GetOption(Question, Array.Empty())); + } +} \ No newline at end of file diff --git a/IOUtils.Tests/InputTests/OptionTests/YesNoOptionTests.cs b/IOUtils.Tests/InputTests/OptionTests/YesNoOptionTests.cs new file mode 100644 index 0000000..c08266a --- /dev/null +++ b/IOUtils.Tests/InputTests/OptionTests/YesNoOptionTests.cs @@ -0,0 +1,45 @@ +using IOUtils.Input; +using NUnit.Framework; + +namespace IOUtils.Tests.InputTests.OptionTests; + +public class YesNoOptionTests { + private const string Question = "Example question text"; + + [TestCase("1", true)] + [TestCase("2", false)] + public void OptionInput_YesNo_ValidInput_ReturnsValue(string input, bool expected) { + MockProvider mockProvider = new(input); + + bool actual = OptionInput.GetYesNoOption(Question, provider: mockProvider); + + Assert.That(actual, Is.EqualTo(expected)); + } + + [TestCase("3")] + [TestCase("1.5")] + [TestCase("1.0")] + [TestCase("0")] + [TestCase("-1")] + [TestCase("abc")] + [TestCase("Yes")] + [TestCase("No")] + [TestCase("")] + [TestCase(null)] + public void OptionInput_YesNo_InvalidInput_ErrorOutput(string input) { + MockProvider mockProvider = new(input); + + Assert.Throws(() => OptionInput.GetYesNoOption(Question, provider: mockProvider)); + + string[] expected = { + $""" + {Question} + 1. Yes + 2. No + """, + "That was not valid, enter a whole number between 1 and 2" + }; + + Assert.That(mockProvider.OutputLines, Is.EquivalentTo(expected)); + } +} \ No newline at end of file diff --git a/IOUtils/Input/OptionInput.cs b/IOUtils/Input/OptionInput.cs new file mode 100644 index 0000000..a2167aa --- /dev/null +++ b/IOUtils/Input/OptionInput.cs @@ -0,0 +1,30 @@ +using System.Text; +using IOUtils.Providers; + +namespace IOUtils.Input; + +public static class OptionInput { + public static string GetOption(string question, string[] options, IProvider? provider = null) { + int responseIndex = GetOptionIndex(question, options, provider); + + return options[responseIndex]; + } + + public static bool GetYesNoOption(string question, IProvider? provider = null) { + // The index of the "Yes" option is 0 + return GetOptionIndex(question, new[] { "Yes", "No" }, provider) == 0; + } + + public static int GetOptionIndex(string question, string[] options, IProvider? provider = null) { + if (options.Length < 1) + throw new ArgumentException("There must be at least one option to choose from", nameof(options)); + + StringBuilder questionStringBuilder = new(question); + + for (int i = 0; i < options.Length; i++) + questionStringBuilder.Append($"{Environment.NewLine}{i + 1}. {options[i]}"); + + // The index of the selected option is 1 less than the numerical input + return NumericalInput.Get(questionStringBuilder.ToString(), 1, options.Length, provider) - 1; + } +} \ No newline at end of file diff --git a/README.md b/README.md index e627136..78f3a3f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # IOUtils: Input and Output Utility Package for .NET ## Installation + Install the IOUtils NuGet package using the .NET CLI: ``` @@ -10,7 +11,10 @@ dotnet add package IOUtils ## Features ### Retrieve numerical input from the user -NumericalInput is a generic class that allows for the retrieval of a numerical input from the user. Optionally, a minimum and maximum value can be specified to restrict the input range. + +NumericalInput is a generic class that allows for the retrieval of a numerical input from the user. Optionally, a +minimum and maximum value can be specified to restrict the input range. + ```csharp using IOUtils.Input; @@ -20,8 +24,22 @@ float value = NumericalInput.Get("Enter a negative number", max: 0); decimal value = NumericalInput.Get("Enter a number between 0 and 100", min: 0, max: 100); ``` +### Retrieve option from the user + +OptionInput is a class that allows for the retrieval of an option from the user. + +```csharp +using IOUtils.Input; + +string selectedOption = OptionInput.GetOption("Select an option", new[] { "Option 1", "Option 2", "Option 3" }); +int optionIndex = OptionInput.GetOptionIndex("Select an option", new[] { "Option 1", "Option 2", "Option 3" }); +bool doSomething = OptionInput.GetYesNoOption("Do you want to do something?"); +``` + ## Contributions + Contributions are welcome! Read the [CONTRIBUTING](CONTRIBUTING.md) guide for information. ## License + This project is licensed under the MIT License. See the [LICENSE](LICENSE) for details. \ No newline at end of file