From 8494777aa1966558f19926ca56f045e2ec5083a2 Mon Sep 17 00:00:00 2001 From: David Pilar Date: Sun, 22 Feb 2026 01:00:25 +0100 Subject: [PATCH] Added tests Signed-off-by: David Pilar --- .../shell/core/FileInputProviderTests.java | 99 +++++++++ .../core/NonInteractiveShellRunnerTests.java | 105 ++++++++++ .../shell/core/command/ClearTests.java | 58 ++++++ .../core/command/CommandBuilderTests.java | 143 +++++++++++++ .../core/command/CommandExecutorTests.java | 158 +++++++++++++++ .../core/command/CommandOptionTests.java | 88 ++++++++ .../shell/core/command/ExitStatusTests.java | 58 ++++++ .../shell/core/command/ParsedInputTests.java | 108 ++++++++++ .../adapter/ConsumerCommandAdapterTests.java | 70 +++++++ .../adapter/FunctionCommandAdapterTests.java | 72 +++++++ .../availability/AvailabilityTests.java | 54 +++++ .../completion/CompletionContextTests.java | 141 +++++++++++++ .../CompositeCompletionProviderTests.java | 74 +++++++ .../EnumCompletionProviderTests.java | 75 +++++++ .../context/BaseComponentContextTests.java | 148 ++++++++++++++ .../view/control/ViewEventTests.java | 59 ++++++ .../tui/component/view/screen/ColorTests.java | 61 ++++++ .../view/screen/DefaultScreenTests.java | 191 ++++++++++++++++++ .../shell/jline/tui/geom/DimensionTests.java | 66 ++++++ .../jline/tui/geom/HorizontalAlignTests.java | 36 ++++ .../shell/jline/tui/geom/PositionTests.java | 76 +++++++ .../jline/tui/geom/VerticalAlignTests.java | 36 ++++ .../shell/test/ShellAssertionsTests.java | 68 +++++++ .../shell/test/ShellScreenTests.java | 98 +++++++++ 24 files changed, 2142 insertions(+) create mode 100644 spring-shell-core/src/test/java/org/springframework/shell/core/FileInputProviderTests.java create mode 100644 spring-shell-core/src/test/java/org/springframework/shell/core/NonInteractiveShellRunnerTests.java create mode 100644 spring-shell-core/src/test/java/org/springframework/shell/core/command/ClearTests.java create mode 100644 spring-shell-core/src/test/java/org/springframework/shell/core/command/CommandBuilderTests.java create mode 100644 spring-shell-core/src/test/java/org/springframework/shell/core/command/CommandExecutorTests.java create mode 100644 spring-shell-core/src/test/java/org/springframework/shell/core/command/CommandOptionTests.java create mode 100644 spring-shell-core/src/test/java/org/springframework/shell/core/command/ExitStatusTests.java create mode 100644 spring-shell-core/src/test/java/org/springframework/shell/core/command/ParsedInputTests.java create mode 100644 spring-shell-core/src/test/java/org/springframework/shell/core/command/adapter/ConsumerCommandAdapterTests.java create mode 100644 spring-shell-core/src/test/java/org/springframework/shell/core/command/adapter/FunctionCommandAdapterTests.java create mode 100644 spring-shell-core/src/test/java/org/springframework/shell/core/command/availability/AvailabilityTests.java create mode 100644 spring-shell-core/src/test/java/org/springframework/shell/core/command/completion/CompletionContextTests.java create mode 100644 spring-shell-core/src/test/java/org/springframework/shell/core/command/completion/CompositeCompletionProviderTests.java create mode 100644 spring-shell-core/src/test/java/org/springframework/shell/core/command/completion/EnumCompletionProviderTests.java create mode 100644 spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/component/context/BaseComponentContextTests.java create mode 100644 spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/component/view/control/ViewEventTests.java create mode 100644 spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/component/view/screen/ColorTests.java create mode 100644 spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/component/view/screen/DefaultScreenTests.java create mode 100644 spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/geom/DimensionTests.java create mode 100644 spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/geom/HorizontalAlignTests.java create mode 100644 spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/geom/PositionTests.java create mode 100644 spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/geom/VerticalAlignTests.java create mode 100644 spring-shell-test/src/test/java/org/springframework/shell/test/ShellAssertionsTests.java create mode 100644 spring-shell-test/src/test/java/org/springframework/shell/test/ShellScreenTests.java diff --git a/spring-shell-core/src/test/java/org/springframework/shell/core/FileInputProviderTests.java b/spring-shell-core/src/test/java/org/springframework/shell/core/FileInputProviderTests.java new file mode 100644 index 000000000..881c5d594 --- /dev/null +++ b/spring-shell-core/src/test/java/org/springframework/shell/core/FileInputProviderTests.java @@ -0,0 +1,99 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.core; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class FileInputProviderTests { + + @TempDir + Path tempDir; + + @Test + void shouldReadSingleLine() throws Exception { + // given + File file = createTempFile("hello world"); + + // when / then + try (FileInputProvider provider = new FileInputProvider(file)) { + String input = provider.readInput(); + assertEquals("hello world", input); + } + } + + @Test + void shouldReturnNullOnEmptyFile() throws Exception { + // given + File file = createTempFile(""); + + // when / then + try (FileInputProvider provider = new FileInputProvider(file)) { + String input = provider.readInput(); + assertNull(input); + } + } + + @Test + void shouldHandleLineContinuation() throws Exception { + // given + File file = createTempFile("hello \\\nworld"); + + // when / then + try (FileInputProvider provider = new FileInputProvider(file)) { + String input = provider.readInput(); + assertEquals("hello world", input); + } + } + + @Test + void shouldReturnNullForCommentLine() throws Exception { + // given + File file = createTempFile("// this is a comment"); + + // when / then + try (FileInputProvider provider = new FileInputProvider(file)) { + String input = provider.readInput(); + assertNull(input); + } + } + + @Test + void shouldThrowForNonExistentFile() { + // given + File nonExistent = new File(tempDir.toFile(), "nonexistent.txt"); + + // when / then + assertThrows(FileNotFoundException.class, () -> new FileInputProvider(nonExistent)); + } + + private File createTempFile(String content) throws IOException { + Path file = tempDir.resolve("test-input.txt"); + Files.writeString(file, content); + return file.toFile(); + } + +} diff --git a/spring-shell-core/src/test/java/org/springframework/shell/core/NonInteractiveShellRunnerTests.java b/spring-shell-core/src/test/java/org/springframework/shell/core/NonInteractiveShellRunnerTests.java new file mode 100644 index 000000000..14adf5889 --- /dev/null +++ b/spring-shell-core/src/test/java/org/springframework/shell/core/NonInteractiveShellRunnerTests.java @@ -0,0 +1,105 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.core; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import org.springframework.shell.core.command.AbstractCommand; +import org.springframework.shell.core.command.CommandContext; +import org.springframework.shell.core.command.CommandRegistry; +import org.springframework.shell.core.command.DefaultCommandParser; +import org.springframework.shell.core.command.ExitStatus; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class NonInteractiveShellRunnerTests { + + @TempDir + Path tempDir; + + private CommandRegistry commandRegistry; + + private DefaultCommandParser commandParser; + + private StringWriter outputBuffer; + + private NonInteractiveShellRunner runner; + + @BeforeEach + void setUp() { + commandRegistry = new CommandRegistry(); + commandParser = new DefaultCommandParser(commandRegistry); + outputBuffer = new StringWriter(); + + commandRegistry.registerCommand(new AbstractCommand("greet", "A greeting command") { + @Override + public ExitStatus doExecute(CommandContext commandContext) { + commandContext.outputWriter().println("Hello!"); + commandContext.outputWriter().flush(); + return ExitStatus.OK; + } + }); + + runner = new NonInteractiveShellRunner(commandParser, commandRegistry, new PrintWriter(outputBuffer)); + } + + @Test + void shouldExecuteSingleCommand() throws Exception { + // when + runner.run(new String[] { "greet" }); + + // then + assertTrue(outputBuffer.toString().contains("Hello!")); + } + + @Test + void shouldHandleEmptyArgs() throws Exception { + // when - should not throw + runner.run(new String[] {}); + + // then - output should be empty (error is logged) + assertTrue(outputBuffer.toString().isEmpty()); + } + + @Test + void shouldExecuteScriptFile() throws Exception { + // given + Path scriptFile = tempDir.resolve("commands.txt"); + Files.writeString(scriptFile, "greet\n"); + + // when + runner.run(new String[] { "@" + scriptFile.toAbsolutePath() }); + + // then + assertTrue(outputBuffer.toString().contains("Hello!")); + } + + @Test + void shouldThrowForUnknownCommand() { + // when / then - CommandNotFoundException propagates from CommandExecutor + org.junit.jupiter.api.Assertions.assertThrows( + org.springframework.shell.core.command.CommandNotFoundException.class, + () -> runner.run(new String[] { "unknown" })); + } + +} diff --git a/spring-shell-core/src/test/java/org/springframework/shell/core/command/ClearTests.java b/spring-shell-core/src/test/java/org/springframework/shell/core/command/ClearTests.java new file mode 100644 index 000000000..5dd414321 --- /dev/null +++ b/spring-shell-core/src/test/java/org/springframework/shell/core/command/ClearTests.java @@ -0,0 +1,58 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.core.command; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import org.junit.jupiter.api.Test; + +import org.springframework.shell.core.InputReader; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ClearTests { + + @Test + void executeShouldSendAnsiClearSequence() throws Exception { + // given + Clear clear = new Clear(); + StringWriter stringWriter = new StringWriter(); + ParsedInput parsedInput = ParsedInput.builder().commandName("clear").build(); + CommandContext context = new CommandContext(parsedInput, new CommandRegistry(), new PrintWriter(stringWriter), + new InputReader() { + }); + + // when + ExitStatus exitStatus = clear.execute(context); + + // then + assertEquals(ExitStatus.OK, exitStatus); + assertTrue(stringWriter.toString().contains("\033[H\033[2J")); + } + + @Test + void shouldHaveCorrectMetadata() { + // given + Clear clear = new Clear(); + + // then + assertEquals("Clear the terminal screen", clear.getDescription()); + assertEquals("Built-In Commands", clear.getGroup()); + } + +} diff --git a/spring-shell-core/src/test/java/org/springframework/shell/core/command/CommandBuilderTests.java b/spring-shell-core/src/test/java/org/springframework/shell/core/command/CommandBuilderTests.java new file mode 100644 index 000000000..74b1dd7ce --- /dev/null +++ b/spring-shell-core/src/test/java/org/springframework/shell/core/command/CommandBuilderTests.java @@ -0,0 +1,143 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.core.command; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.jupiter.api.Test; + +import org.springframework.shell.core.InputReader; +import org.springframework.shell.core.command.availability.Availability; +import org.springframework.shell.core.command.availability.AvailabilityProvider; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class CommandBuilderTests { + + @Test + void buildWithConsumerExecutor() throws Exception { + // given + AtomicBoolean invoked = new AtomicBoolean(false); + AbstractCommand command = Command.builder().name("test").description("Test command").execute(ctx -> { + invoked.set(true); + }); + + StringWriter sw = new StringWriter(); + ParsedInput parsedInput = ParsedInput.builder().commandName("test").build(); + CommandContext ctx = new CommandContext(parsedInput, new CommandRegistry(), new PrintWriter(sw), + new InputReader() { + }); + + // when + ExitStatus status = command.execute(ctx); + + // then + assertEquals(ExitStatus.OK, status); + assertTrue(invoked.get()); + assertEquals("test", command.getName()); + assertEquals("Test command", command.getDescription()); + } + + @Test + void buildWithFunctionExecutor() throws Exception { + // given + AbstractCommand command = Command.builder() + .name("hello") + .description("Says hello") + .execute(ctx -> "Hello World"); + + StringWriter sw = new StringWriter(); + ParsedInput parsedInput = ParsedInput.builder().commandName("hello").build(); + CommandContext ctx = new CommandContext(parsedInput, new CommandRegistry(), new PrintWriter(sw), + new InputReader() { + }); + + // when + ExitStatus status = command.execute(ctx); + + // then + assertEquals(ExitStatus.OK, status); + assertTrue(sw.toString().contains("Hello World")); + } + + @Test + void buildWithAliases() { + // given + AbstractCommand command = Command.builder().name("list").aliases("ls", "dir").execute(ctx -> { + }); + + // then + assertEquals(List.of("ls", "dir"), command.getAliases()); + } + + @Test + void buildWithOptions() { + // given + CommandOption opt = CommandOption.with().longName("verbose").shortName('v').build(); + AbstractCommand command = Command.builder().name("cmd").options(opt).execute(ctx -> { + }); + + // then + assertEquals(1, command.getOptions().size()); + assertEquals("verbose", command.getOptions().get(0).longName()); + } + + @Test + void buildWithAvailabilityProvider() throws Exception { + // given + AbstractCommand command = Command.builder() + .name("restricted") + .availabilityProvider(() -> Availability.unavailable("not logged in")) + .execute(ctx -> { + }); + + StringWriter sw = new StringWriter(); + ParsedInput parsedInput = ParsedInput.builder().commandName("restricted").build(); + CommandContext ctx = new CommandContext(parsedInput, new CommandRegistry(), new PrintWriter(sw), + new InputReader() { + }); + + // when + ExitStatus status = command.execute(ctx); + + // then + assertEquals(ExitStatus.AVAILABILITY_ERROR, status); + assertTrue(sw.toString().contains("not logged in")); + } + + @Test + void buildWithHidden() { + // given + AbstractCommand command = Command.builder().name("secret").hidden(true).execute(ctx -> { + }); + + // then + assertTrue(command.isHidden()); + } + + @Test + void buildWithoutNameShouldThrow() { + // when / then + assertThrows(IllegalArgumentException.class, () -> Command.builder().execute(ctx -> { + })); + } + +} diff --git a/spring-shell-core/src/test/java/org/springframework/shell/core/command/CommandExecutorTests.java b/spring-shell-core/src/test/java/org/springframework/shell/core/command/CommandExecutorTests.java new file mode 100644 index 000000000..1a3d8679f --- /dev/null +++ b/spring-shell-core/src/test/java/org/springframework/shell/core/command/CommandExecutorTests.java @@ -0,0 +1,158 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.core.command; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.shell.core.InputReader; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class CommandExecutorTests { + + private CommandRegistry commandRegistry; + + private CommandExecutor commandExecutor; + + @BeforeEach + void setUp() { + this.commandRegistry = new CommandRegistry(); + this.commandExecutor = new CommandExecutor(this.commandRegistry); + } + + @Test + void executeCommandSuccessfully() { + // given + commandRegistry.registerCommand(new AbstractCommand("greet", "A greeting command") { + @Override + public ExitStatus doExecute(CommandContext commandContext) { + commandContext.outputWriter().println("Hello!"); + return ExitStatus.OK; + } + }); + + StringWriter stringWriter = new StringWriter(); + ParsedInput parsedInput = ParsedInput.builder().commandName("greet").build(); + CommandContext context = new CommandContext(parsedInput, commandRegistry, new PrintWriter(stringWriter), + new InputReader() { + }); + + // when + ExitStatus exitStatus = commandExecutor.execute(context); + + // then + assertEquals(ExitStatus.OK.code(), exitStatus.code()); + } + + @Test + void executeCommandNotFoundShouldThrow() { + // given + StringWriter stringWriter = new StringWriter(); + ParsedInput parsedInput = ParsedInput.builder().commandName("unknown").build(); + CommandContext context = new CommandContext(parsedInput, commandRegistry, new PrintWriter(stringWriter), + new InputReader() { + }); + + // when / then + assertThrows(CommandNotFoundException.class, () -> commandExecutor.execute(context)); + } + + @Test + void executeCommandWithSubCommands() { + // given + commandRegistry.registerCommand(new AbstractCommand("git commit", "Commit changes") { + @Override + public ExitStatus doExecute(CommandContext commandContext) { + commandContext.outputWriter().println("committed"); + return ExitStatus.OK; + } + }); + + StringWriter stringWriter = new StringWriter(); + ParsedInput parsedInput = ParsedInput.builder().commandName("git").addSubCommand("commit").build(); + CommandContext context = new CommandContext(parsedInput, commandRegistry, new PrintWriter(stringWriter), + new InputReader() { + }); + + // when + ExitStatus exitStatus = commandExecutor.execute(context); + + // then + assertEquals(ExitStatus.OK.code(), exitStatus.code()); + } + + @Test + void executeCommandWithExceptionShouldWrapInCommandExecutionException() { + // given + commandRegistry.registerCommand(new AbstractCommand("fail", "A failing command") { + @Override + public ExitStatus doExecute(CommandContext commandContext) throws Exception { + throw new RuntimeException("boom"); + } + }); + + StringWriter stringWriter = new StringWriter(); + ParsedInput parsedInput = ParsedInput.builder().commandName("fail").build(); + CommandContext context = new CommandContext(parsedInput, commandRegistry, new PrintWriter(stringWriter), + new InputReader() { + }); + + // when / then + CommandExecutionException exception = assertThrows(CommandExecutionException.class, + () -> commandExecutor.execute(context)); + assertTrue(exception.getMessage().contains("fail")); + } + + @Test + void executeWithPrefixMatchShouldListSubCommands() { + // given + commandRegistry.registerCommand(new AbstractCommand("git commit", "Commit changes") { + @Override + public ExitStatus doExecute(CommandContext commandContext) { + return ExitStatus.OK; + } + }); + commandRegistry.registerCommand(new AbstractCommand("git push", "Push changes") { + @Override + public ExitStatus doExecute(CommandContext commandContext) { + return ExitStatus.OK; + } + }); + + StringWriter stringWriter = new StringWriter(); + ParsedInput parsedInput = ParsedInput.builder().commandName("git").build(); + CommandContext context = new CommandContext(parsedInput, commandRegistry, new PrintWriter(stringWriter), + new InputReader() { + }); + + // when + ExitStatus exitStatus = commandExecutor.execute(context); + + // then + assertEquals(ExitStatus.OK.code(), exitStatus.code()); + String output = stringWriter.toString(); + assertTrue(output.contains("Available sub-commands")); + assertTrue(output.contains("git commit")); + assertTrue(output.contains("git push")); + } + +} diff --git a/spring-shell-core/src/test/java/org/springframework/shell/core/command/CommandOptionTests.java b/spring-shell-core/src/test/java/org/springframework/shell/core/command/CommandOptionTests.java new file mode 100644 index 000000000..60b649ad8 --- /dev/null +++ b/spring-shell-core/src/test/java/org/springframework/shell/core/command/CommandOptionTests.java @@ -0,0 +1,88 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.core.command; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class CommandOptionTests { + + @Test + void isOptionEqualShouldMatchLongName() { + // given + CommandOption option = CommandOption.with().longName("verbose").build(); + + // then + assertTrue(option.isOptionEqual("--verbose")); + assertFalse(option.isOptionEqual("--debug")); + } + + @Test + void isOptionEqualShouldMatchShortName() { + // given + CommandOption option = CommandOption.with().shortName('v').build(); + + // then + assertTrue(option.isOptionEqual("-v")); + assertFalse(option.isOptionEqual("-d")); + } + + @Test + void isOptionEqualShouldNotMatchDefaultShortName() { + // given - default short name is space character + CommandOption option = CommandOption.with().longName("verbose").build(); + + // then + assertFalse(option.isOptionEqual("- ")); + } + + @Test + void builderShouldBuildWithAllFields() { + // when + CommandOption option = CommandOption.with() + .shortName('f') + .longName("file") + .description("Input file") + .required(true) + .defaultValue("data.txt") + .value("input.txt") + .type(String.class) + .build(); + + // then + assertEquals('f', option.shortName()); + assertEquals("file", option.longName()); + assertEquals("Input file", option.description()); + assertTrue(option.required()); + assertEquals("data.txt", option.defaultValue()); + assertEquals("input.txt", option.value()); + assertEquals(String.class, option.type()); + } + + @Test + void builderShouldHaveDefaults() { + // when + CommandOption option = CommandOption.with().build(); + + // then + assertEquals(' ', option.shortName()); + assertEquals(Object.class, option.type()); + } + +} diff --git a/spring-shell-core/src/test/java/org/springframework/shell/core/command/ExitStatusTests.java b/spring-shell-core/src/test/java/org/springframework/shell/core/command/ExitStatusTests.java new file mode 100644 index 000000000..a1e5642db --- /dev/null +++ b/spring-shell-core/src/test/java/org/springframework/shell/core/command/ExitStatusTests.java @@ -0,0 +1,58 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.core.command; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ExitStatusTests { + + @Test + void okStatusShouldHaveCodeZero() { + assertEquals(0, ExitStatus.OK.code()); + assertEquals("OK", ExitStatus.OK.description()); + } + + @Test + void executionErrorStatusShouldHaveCodeMinusOne() { + assertEquals(-1, ExitStatus.EXECUTION_ERROR.code()); + assertEquals("EXECUTION_ERROR", ExitStatus.EXECUTION_ERROR.description()); + } + + @Test + void usageErrorStatusShouldHaveCodeMinusTwo() { + assertEquals(-2, ExitStatus.USAGE_ERROR.code()); + assertEquals("USAGE_ERROR", ExitStatus.USAGE_ERROR.description()); + } + + @Test + void availabilityErrorStatusShouldHaveCodeMinusThree() { + assertEquals(-3, ExitStatus.AVAILABILITY_ERROR.code()); + assertEquals("AVAILABILITY_ERROR", ExitStatus.AVAILABILITY_ERROR.description()); + } + + @Test + void customExitStatusShouldPreserveValues() { + // when + ExitStatus custom = new ExitStatus(42, "CUSTOM"); + + // then + assertEquals(42, custom.code()); + assertEquals("CUSTOM", custom.description()); + } + +} diff --git a/spring-shell-core/src/test/java/org/springframework/shell/core/command/ParsedInputTests.java b/spring-shell-core/src/test/java/org/springframework/shell/core/command/ParsedInputTests.java new file mode 100644 index 000000000..b4d764bd7 --- /dev/null +++ b/spring-shell-core/src/test/java/org/springframework/shell/core/command/ParsedInputTests.java @@ -0,0 +1,108 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.core.command; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ParsedInputTests { + + @Test + void builderShouldBuildWithDefaults() { + // when + ParsedInput parsedInput = ParsedInput.builder().build(); + + // then + assertEquals("", parsedInput.commandName()); + assertTrue(parsedInput.subCommands().isEmpty()); + assertTrue(parsedInput.options().isEmpty()); + assertTrue(parsedInput.arguments().isEmpty()); + } + + @Test + void builderShouldBuildWithCommandName() { + // when + ParsedInput parsedInput = ParsedInput.builder().commandName("test").build(); + + // then + assertEquals("test", parsedInput.commandName()); + } + + @Test + void builderShouldBuildWithSubCommands() { + // when + ParsedInput parsedInput = ParsedInput.builder() + .commandName("git") + .addSubCommand("commit") + .addSubCommand("--amend") + .build(); + + // then + assertEquals("git", parsedInput.commandName()); + assertEquals(2, parsedInput.subCommands().size()); + assertEquals("commit", parsedInput.subCommands().get(0)); + assertEquals("--amend", parsedInput.subCommands().get(1)); + } + + @Test + void builderShouldBuildWithOptions() { + // given + CommandOption option = CommandOption.with().longName("verbose").shortName('v').build(); + + // when + ParsedInput parsedInput = ParsedInput.builder().commandName("cmd").addOption(option).build(); + + // then + assertEquals(1, parsedInput.options().size()); + assertEquals("verbose", parsedInput.options().get(0).longName()); + } + + @Test + void builderShouldBuildWithArguments() { + // given + CommandArgument arg = new CommandArgument(0, "file.txt"); + + // when + ParsedInput parsedInput = ParsedInput.builder().commandName("cmd").addArgument(arg).build(); + + // then + assertEquals(1, parsedInput.arguments().size()); + assertEquals("file.txt", parsedInput.arguments().get(0).value()); + assertEquals(0, parsedInput.arguments().get(0).index()); + } + + @Test + void builtListsShouldBeImmutable() { + // when + ParsedInput parsedInput = ParsedInput.builder() + .commandName("test") + .addSubCommand("sub") + .addOption(CommandOption.with().longName("opt").build()) + .addArgument(new CommandArgument(0, "arg")) + .build(); + + // then - lists should be immutable copies + org.junit.jupiter.api.Assertions.assertThrows(UnsupportedOperationException.class, + () -> parsedInput.subCommands().add("new")); + org.junit.jupiter.api.Assertions.assertThrows(UnsupportedOperationException.class, + () -> parsedInput.options().add(CommandOption.with().build())); + org.junit.jupiter.api.Assertions.assertThrows(UnsupportedOperationException.class, + () -> parsedInput.arguments().add(new CommandArgument(1, "x"))); + } + +} diff --git a/spring-shell-core/src/test/java/org/springframework/shell/core/command/adapter/ConsumerCommandAdapterTests.java b/spring-shell-core/src/test/java/org/springframework/shell/core/command/adapter/ConsumerCommandAdapterTests.java new file mode 100644 index 000000000..e88c6b4d0 --- /dev/null +++ b/spring-shell-core/src/test/java/org/springframework/shell/core/command/adapter/ConsumerCommandAdapterTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.core.command.adapter; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.jupiter.api.Test; + +import org.springframework.shell.core.InputReader; +import org.springframework.shell.core.command.CommandContext; +import org.springframework.shell.core.command.CommandRegistry; +import org.springframework.shell.core.command.ExitStatus; +import org.springframework.shell.core.command.ParsedInput; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ConsumerCommandAdapterTests { + + @Test + void doExecuteShouldInvokeConsumerAndReturnOk() { + // given + AtomicBoolean invoked = new AtomicBoolean(false); + ConsumerCommandAdapter adapter = new ConsumerCommandAdapter("test", "test desc", "group", "help", false, + ctx -> invoked.set(true)); + + ParsedInput parsedInput = ParsedInput.builder().commandName("test").build(); + CommandContext context = new CommandContext(parsedInput, new CommandRegistry(), + new PrintWriter(new StringWriter()), new InputReader() { + }); + + // when + ExitStatus exitStatus = adapter.doExecute(context); + + // then + assertTrue(invoked.get()); + assertEquals(ExitStatus.OK, exitStatus); + } + + @Test + void shouldSetCommandProperties() { + // given + ConsumerCommandAdapter adapter = new ConsumerCommandAdapter("mycmd", "my description", "mygroup", "my help", + true, ctx -> { + }); + + // then + assertEquals("mycmd", adapter.getName()); + assertEquals("my description", adapter.getDescription()); + assertEquals("mygroup", adapter.getGroup()); + assertEquals("my help", adapter.getHelp()); + assertTrue(adapter.isHidden()); + } + +} diff --git a/spring-shell-core/src/test/java/org/springframework/shell/core/command/adapter/FunctionCommandAdapterTests.java b/spring-shell-core/src/test/java/org/springframework/shell/core/command/adapter/FunctionCommandAdapterTests.java new file mode 100644 index 000000000..200101f86 --- /dev/null +++ b/spring-shell-core/src/test/java/org/springframework/shell/core/command/adapter/FunctionCommandAdapterTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.core.command.adapter; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import org.junit.jupiter.api.Test; + +import org.springframework.shell.core.InputReader; +import org.springframework.shell.core.command.CommandContext; +import org.springframework.shell.core.command.CommandRegistry; +import org.springframework.shell.core.command.ExitStatus; +import org.springframework.shell.core.command.ParsedInput; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class FunctionCommandAdapterTests { + + @Test + void doExecuteShouldInvokeFunctionAndPrintOutput() { + // given + FunctionCommandAdapter adapter = new FunctionCommandAdapter("hello", "Says hello", "group", "help", false, + ctx -> "Hello World"); + + StringWriter stringWriter = new StringWriter(); + ParsedInput parsedInput = ParsedInput.builder().commandName("hello").build(); + CommandContext context = new CommandContext(parsedInput, new CommandRegistry(), new PrintWriter(stringWriter), + new InputReader() { + }); + + // when + ExitStatus exitStatus = adapter.doExecute(context); + + // then + assertEquals(ExitStatus.OK, exitStatus); + assertTrue(stringWriter.toString().contains("Hello World")); + } + + @Test + void doExecuteShouldFlushOutput() { + // given + FunctionCommandAdapter adapter = new FunctionCommandAdapter("cmd", "desc", "", "", false, ctx -> "output"); + + StringWriter stringWriter = new StringWriter(); + ParsedInput parsedInput = ParsedInput.builder().commandName("cmd").build(); + CommandContext context = new CommandContext(parsedInput, new CommandRegistry(), new PrintWriter(stringWriter), + new InputReader() { + }); + + // when + adapter.doExecute(context); + + // then + assertTrue(stringWriter.toString().contains("output")); + } + +} diff --git a/spring-shell-core/src/test/java/org/springframework/shell/core/command/availability/AvailabilityTests.java b/spring-shell-core/src/test/java/org/springframework/shell/core/command/availability/AvailabilityTests.java new file mode 100644 index 000000000..5e219cc98 --- /dev/null +++ b/spring-shell-core/src/test/java/org/springframework/shell/core/command/availability/AvailabilityTests.java @@ -0,0 +1,54 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.core.command.availability; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class AvailabilityTests { + + @Test + void availableShouldHaveNullReasonAndBeAvailable() { + // when + Availability availability = Availability.available(); + + // then + assertTrue(availability.isAvailable()); + assertNull(availability.reason()); + } + + @Test + void unavailableShouldHaveReasonAndNotBeAvailable() { + // when + Availability availability = Availability.unavailable("not connected"); + + // then + assertFalse(availability.isAvailable()); + assertEquals("not connected", availability.reason()); + } + + @Test + void unavailableWithNullReasonShouldThrow() { + // when / then + assertThrows(IllegalArgumentException.class, () -> Availability.unavailable(null)); + } + +} diff --git a/spring-shell-core/src/test/java/org/springframework/shell/core/command/completion/CompletionContextTests.java b/spring-shell-core/src/test/java/org/springframework/shell/core/command/completion/CompletionContextTests.java new file mode 100644 index 000000000..c4d3bcbe4 --- /dev/null +++ b/spring-shell-core/src/test/java/org/springframework/shell/core/command/completion/CompletionContextTests.java @@ -0,0 +1,141 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.core.command.completion; + +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import org.springframework.shell.core.command.CommandOption; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +class CompletionContextTests { + + @Test + void currentWordShouldReturnWordAtIndex() { + // given + List words = Arrays.asList("--name", "john"); + CompletionContext ctx = new CompletionContext(words, 1, 4, null, null); + + // then + assertEquals("john", ctx.currentWord()); + } + + @Test + void currentWordShouldReturnNullWhenIndexOutOfBounds() { + // given + List words = Arrays.asList("--name"); + CompletionContext ctx = new CompletionContext(words, 5, 0, null, null); + + // then + assertNull(ctx.currentWord()); + } + + @Test + void currentWordUpToCursorShouldReturnSubstring() { + // given + List words = Arrays.asList("--verbose"); + CompletionContext ctx = new CompletionContext(words, 0, 4, null, null); + + // then + assertEquals("--ve", ctx.currentWordUpToCursor()); + } + + @Test + void currentWordUpToCursorShouldReturnNullWhenNoCurrentWord() { + // given + List words = Arrays.asList("--name"); + CompletionContext ctx = new CompletionContext(words, 5, 0, null, null); + + // then + assertNull(ctx.currentWordUpToCursor()); + } + + @Test + void upToCursorShouldReturnPartialInput() { + // given + List words = Arrays.asList("--name", "john"); + CompletionContext ctx = new CompletionContext(words, 1, 2, null, null); + + // when + String result = ctx.upToCursor(); + + // then + assertEquals("--name jo", result); + } + + @Test + void upToCursorShouldHandleEmptyWords() { + // given + List words = List.of(); + CompletionContext ctx = new CompletionContext(words, 0, 0, null, null); + + // when + String result = ctx.upToCursor(); + + // then + assertEquals("", result); + } + + @Test + void dropShouldRemoveFirstNWords() { + // given + List words = Arrays.asList("sub", "--name", "john"); + CompletionContext ctx = new CompletionContext(words, 2, 3, null, null); + + // when + CompletionContext dropped = ctx.drop(1); + + // then + assertEquals(2, dropped.getWords().size()); + assertEquals("--name", dropped.getWords().get(0)); + assertEquals(1, dropped.getWordIndex()); + } + + @Test + void commandOptionShouldReturnCopyWithOption() { + // given + CompletionContext ctx = new CompletionContext(List.of("val"), 0, 0, null, null); + CommandOption option = CommandOption.with().longName("name").build(); + + // when + CompletionContext withOption = ctx.commandOption(option); + + // then + assertNotNull(withOption.getCommandOption()); + assertEquals("name", withOption.getCommandOption().longName()); + assertNull(ctx.getCommandOption()); + } + + @Test + void gettersShouldReturnCorrectValues() { + // given + List words = Arrays.asList("a", "b"); + CompletionContext ctx = new CompletionContext(words, 1, 3, null, null); + + // then + assertEquals(words, ctx.getWords()); + assertEquals(1, ctx.getWordIndex()); + assertEquals(3, ctx.getPosition()); + assertNull(ctx.getCommand()); + assertNull(ctx.getCommandOption()); + } + +} diff --git a/spring-shell-core/src/test/java/org/springframework/shell/core/command/completion/CompositeCompletionProviderTests.java b/spring-shell-core/src/test/java/org/springframework/shell/core/command/completion/CompositeCompletionProviderTests.java new file mode 100644 index 000000000..ef7e7f400 --- /dev/null +++ b/spring-shell-core/src/test/java/org/springframework/shell/core/command/completion/CompositeCompletionProviderTests.java @@ -0,0 +1,74 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.core.command.completion; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class CompositeCompletionProviderTests { + + @Test + void shouldCombineProposalsFromMultipleProviders() { + // given + CompletionProvider provider1 = ctx -> List.of(new CompletionProposal("alpha"), new CompletionProposal("beta")); + CompletionProvider provider2 = ctx -> List.of(new CompletionProposal("gamma")); + + CompositeCompletionProvider composite = new CompositeCompletionProvider(provider1, provider2); + CompletionContext context = new CompletionContext(List.of(), 0, 0, null, null); + + // when + List proposals = composite.apply(context); + + // then + assertEquals(3, proposals.size()); + assertEquals("alpha", proposals.get(0).value()); + assertEquals("beta", proposals.get(1).value()); + assertEquals("gamma", proposals.get(2).value()); + } + + @Test + void shouldReturnEmptyWhenNoProviders() { + // given + CompositeCompletionProvider composite = new CompositeCompletionProvider(); + CompletionContext context = new CompletionContext(List.of(), 0, 0, null, null); + + // when + List proposals = composite.apply(context); + + // then + assertTrue(proposals.isEmpty()); + } + + @Test + void shouldHandleSingleProvider() { + // given + CompletionProvider provider = ctx -> List.of(new CompletionProposal("only")); + CompositeCompletionProvider composite = new CompositeCompletionProvider(provider); + CompletionContext context = new CompletionContext(List.of(), 0, 0, null, null); + + // when + List proposals = composite.apply(context); + + // then + assertEquals(1, proposals.size()); + assertEquals("only", proposals.get(0).value()); + } + +} diff --git a/spring-shell-core/src/test/java/org/springframework/shell/core/command/completion/EnumCompletionProviderTests.java b/spring-shell-core/src/test/java/org/springframework/shell/core/command/completion/EnumCompletionProviderTests.java new file mode 100644 index 000000000..09a6985e7 --- /dev/null +++ b/spring-shell-core/src/test/java/org/springframework/shell/core/command/completion/EnumCompletionProviderTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.core.command.completion; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class EnumCompletionProviderTests { + + enum Color { + + RED, GREEN, BLUE + + } + + @Test + void shouldProvideAllEnumValues() { + // given + EnumCompletionProvider provider = new EnumCompletionProvider(Color.class); + CompletionContext context = new CompletionContext(List.of(), 0, 0, null, null); + + // when + List proposals = provider.apply(context); + + // then + assertEquals(3, proposals.size()); + assertEquals("RED", proposals.get(0).value()); + assertEquals("GREEN", proposals.get(1).value()); + assertEquals("BLUE", proposals.get(2).value()); + } + + @Test + void shouldProvideEnumValuesWithPrefix() { + // given + EnumCompletionProvider provider = new EnumCompletionProvider(Color.class, "color"); + CompletionContext context = new CompletionContext(List.of(), 0, 0, null, null); + + // when + List proposals = provider.apply(context); + + // then + assertEquals(3, proposals.size()); + assertEquals("color=RED", proposals.get(0).value()); + assertEquals("color=GREEN", proposals.get(1).value()); + assertEquals("color=BLUE", proposals.get(2).value()); + } + + @Test + void shouldThrowForNonEnumType() { + // given + EnumCompletionProvider provider = new EnumCompletionProvider(String.class); + CompletionContext context = new CompletionContext(List.of(), 0, 0, null, null); + + // when / then - getEnumConstants() returns null for non-enum types + org.junit.jupiter.api.Assertions.assertThrows(NullPointerException.class, () -> provider.apply(context)); + } + +} diff --git a/spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/component/context/BaseComponentContextTests.java b/spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/component/context/BaseComponentContextTests.java new file mode 100644 index 000000000..0b0ec2f47 --- /dev/null +++ b/spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/component/context/BaseComponentContextTests.java @@ -0,0 +1,148 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.jline.tui.component.context; + +import java.util.Map; +import java.util.NoSuchElementException; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class BaseComponentContextTests { + + @Test + void putAndGetShouldWork() { + // given + BaseComponentContext context = new BaseComponentContext<>(); + context.put("key", "value"); + + // when + Object value = context.get("key"); + + // then + assertEquals("value", value); + } + + @Test + void getMissingKeyShouldThrow() { + // given + BaseComponentContext context = new BaseComponentContext<>(); + + // when / then + assertThrows(NoSuchElementException.class, () -> context.get("missing")); + } + + @Test + void getWithTypeShouldCast() { + // given + BaseComponentContext context = new BaseComponentContext<>(); + context.put("count", 42); + + // when + Integer value = context.get("count", Integer.class); + + // then + assertEquals(42, value); + } + + @Test + void getWithWrongTypeShouldThrow() { + // given + BaseComponentContext context = new BaseComponentContext<>(); + context.put("count", 42); + + // when / then + assertThrows(IllegalArgumentException.class, () -> context.get("count", String.class)); + } + + @Test + void containsKeyShouldWork() { + // given + BaseComponentContext context = new BaseComponentContext<>(); + context.put("key", "value"); + + // then + assertTrue(context.containsKey("key")); + assertFalse(context.containsKey("other")); + } + + @Test + void streamShouldReturnEntries() { + // given + BaseComponentContext context = new BaseComponentContext<>(); + context.put("a", 1); + context.put("b", 2); + + // when + long count = context.stream().count(); + + // then + assertEquals(2, count); + } + + @Test + void terminalWidthShouldBeNullByDefault() { + // given + BaseComponentContext context = new BaseComponentContext<>(); + + // then + assertNull(context.getTerminalWidth()); + } + + @Test + void setTerminalWidthShouldWork() { + // given + BaseComponentContext context = new BaseComponentContext<>(); + + // when + context.setTerminalWidth(80); + + // then + assertEquals(80, context.getTerminalWidth()); + } + + @Test + void toTemplateModelShouldContainExpectedKeys() { + // given + BaseComponentContext context = new BaseComponentContext<>(); + context.put("key1", "value1"); + context.setTerminalWidth(120); + + // when + Map model = context.toTemplateModel(); + + // then + assertNotNull(model.get("rawValues")); + assertEquals(120, model.get("terminalWidth")); + } + + @Test + void emptyFactoryMethodShouldReturnEmptyContext() { + // when + ComponentContext context = ComponentContext.empty(); + + // then + assertNotNull(context); + assertNull(context.getTerminalWidth()); + } + +} diff --git a/spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/component/view/control/ViewEventTests.java b/spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/component/view/control/ViewEventTests.java new file mode 100644 index 000000000..345426ab9 --- /dev/null +++ b/spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/component/view/control/ViewEventTests.java @@ -0,0 +1,59 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.jline.tui.component.view.control; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.mock; + +class ViewEventTests { + + @Test + void viewDoneEventShouldHaveViewAndEmptyArgs() { + // given + View mockView = mock(View.class); + + // when + ViewDoneEvent event = ViewDoneEvent.of(mockView); + + // then + assertEquals(mockView, event.view()); + assertEquals(ViewEventArgs.EMPTY, event.args()); + } + + @Test + void viewEventArgsShouldHaveEmptyConstant() { + // then + assertNotNull(ViewEventArgs.EMPTY); + } + + @Test + void genericViewDoneEventShouldBeRecord() { + // given + View mockView = mock(View.class); + + // when + ViewDoneEvent.GenericViewDoneEvent event = new ViewDoneEvent.GenericViewDoneEvent(mockView, + ViewEventArgs.EMPTY); + + // then + assertEquals(mockView, event.view()); + assertEquals(ViewEventArgs.EMPTY, event.args()); + } + +} diff --git a/spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/component/view/screen/ColorTests.java b/spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/component/view/screen/ColorTests.java new file mode 100644 index 000000000..cac3263e1 --- /dev/null +++ b/spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/component/view/screen/ColorTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.jline.tui.component.view.screen; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +class ColorTests { + + @Test + void basicColorsShouldHaveCorrectValues() { + assertEquals(0xffffff, Color.WHITE); + assertEquals(0x000000, Color.BLACK); + assertEquals(0xFF0000, Color.RED); + assertEquals(0x00FF00, Color.GREEN); + assertEquals(0x0000FF, Color.BLUE); + assertEquals(0xFFFF00, Color.YELLOW); + assertEquals(0x00FFFF, Color.CYAN); + assertEquals(0xFF00FF, Color.MAGENTA); + } + + @Test + void greyScaleShouldBeOrdered() { + // GREY0 is black, GREY100 is white + assertEquals(0x000000, Color.GREY0); + assertEquals(0xffffff, Color.GREY100); + } + + @Test + void numberedVariantsShouldDifferFromBase() { + // Numbered variants (1-4) have slightly different hex values + assertNotEquals(Color.RED, Color.RED2); + assertNotEquals(Color.RED, Color.RED3); + assertNotEquals(Color.RED, Color.RED4); + } + + @Test + void colorConstantsShouldBePositive() { + // All color hex values should be non-negative + assert Color.RED > 0; + assert Color.GREEN > 0; + assert Color.BLUE > 0; + assert Color.WHITE > 0; + } + +} diff --git a/spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/component/view/screen/DefaultScreenTests.java b/spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/component/view/screen/DefaultScreenTests.java new file mode 100644 index 000000000..cccef4712 --- /dev/null +++ b/spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/component/view/screen/DefaultScreenTests.java @@ -0,0 +1,191 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.jline.tui.component.view.screen; + +import java.util.List; + +import org.jline.utils.AttributedString; +import org.junit.jupiter.api.Test; + +import org.springframework.shell.jline.tui.geom.Position; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class DefaultScreenTests { + + @Test + void shouldCreateWithDimensions() { + // given + DefaultScreen screen = new DefaultScreen(10, 20); + + // then + ScreenItem[][] items = screen.getItems(); + assertEquals(10, items.length); + assertEquals(20, items[0].length); + } + + @Test + void shouldCreateWithZeroDimensions() { + // given + DefaultScreen screen = new DefaultScreen(); + + // then + ScreenItem[][] items = screen.getItems(); + assertEquals(0, items.length); + } + + @Test + void shouldResizeScreen() { + // given + DefaultScreen screen = new DefaultScreen(5, 5); + + // when + screen.resize(10, 15); + + // then + ScreenItem[][] items = screen.getItems(); + assertEquals(10, items.length); + assertEquals(15, items[0].length); + } + + @Test + void resizeWithNegativeRowsShouldThrow() { + // given + DefaultScreen screen = new DefaultScreen(); + + // when / then + assertThrows(IllegalArgumentException.class, () -> screen.resize(-1, 10)); + } + + @Test + void resizeWithNegativeColumnsShouldThrow() { + // given + DefaultScreen screen = new DefaultScreen(); + + // when / then + assertThrows(IllegalArgumentException.class, () -> screen.resize(10, -1)); + } + + @Test + void shouldWriteTextToScreen() { + // given + DefaultScreen screen = new DefaultScreen(5, 20); + Screen.Writer writer = screen.writerBuilder().build(); + + // when + writer.text("Hello", 0, 0); + + // then + ScreenItem[][] items = screen.getItems(); + assertNotNull(items[0][0]); + assertEquals("H", items[0][0].getContent().toString()); + assertEquals("e", items[0][1].getContent().toString()); + assertEquals("l", items[0][2].getContent().toString()); + assertEquals("l", items[0][3].getContent().toString()); + assertEquals("o", items[0][4].getContent().toString()); + } + + @Test + void shouldSetAndGetCursorPosition() { + // given + DefaultScreen screen = new DefaultScreen(10, 10); + Position newPosition = new Position(5, 3); + + // when + screen.setCursorPosition(newPosition); + + // then + assertEquals(newPosition, screen.getCursorPosition()); + } + + @Test + void shouldSetAndGetShowCursor() { + // given + DefaultScreen screen = new DefaultScreen(10, 10); + + // when + screen.setShowCursor(true); + + // then + assertTrue(screen.isShowCursor()); + + // when + screen.setShowCursor(false); + + // then + assertFalse(screen.isShowCursor()); + } + + @Test + void clipShouldReturnNull() { + // given + DefaultScreen screen = new DefaultScreen(10, 10); + + // when / then + assertNull(screen.clip(0, 0, 5, 5)); + } + + @Test + void getScreenLinesShouldReturnAttributedStrings() { + // given + DefaultScreen screen = new DefaultScreen(3, 10); + Screen.Writer writer = screen.writerBuilder().build(); + writer.text("Hi", 0, 0); + + // when + List lines = screen.getScreenLines(); + + // then + assertEquals(3, lines.size()); + assertTrue(lines.get(0).toString().startsWith("Hi")); + } + + @Test + void writerBuilderShouldSupportLayers() { + // given + DefaultScreen screen = new DefaultScreen(5, 10); + + // when - write on layer 0 and layer 1 + Screen.Writer writer0 = screen.writerBuilder().layer(0).build(); + Screen.Writer writer1 = screen.writerBuilder().layer(1).build(); + writer0.text("A", 0, 0); + writer1.text("B", 0, 0); + + // then - layer 1 should override layer 0 + ScreenItem[][] items = screen.getItems(); + assertEquals("B", items[0][0].getContent().toString()); + } + + @Test + void writerBuilderShouldSupportColor() { + // given + DefaultScreen screen = new DefaultScreen(5, 10); + + // when + Screen.Writer writer = screen.writerBuilder().color(Color.RED).build(); + writer.text("X", 0, 0); + + // then + ScreenItem[][] items = screen.getItems(); + assertEquals(Color.RED, items[0][0].getForeground()); + } + +} diff --git a/spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/geom/DimensionTests.java b/spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/geom/DimensionTests.java new file mode 100644 index 000000000..b69dec3b2 --- /dev/null +++ b/spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/geom/DimensionTests.java @@ -0,0 +1,66 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.jline.tui.geom; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +class DimensionTests { + + @Test + void shouldStoreWidthAndHeight() { + // when + Dimension dimension = new Dimension(80, 24); + + // then + assertEquals(80, dimension.width()); + assertEquals(24, dimension.height()); + } + + @Test + void equalDimensionsShouldBeEqual() { + // given + Dimension d1 = new Dimension(100, 50); + Dimension d2 = new Dimension(100, 50); + + // then + assertEquals(d1, d2); + assertEquals(d1.hashCode(), d2.hashCode()); + } + + @Test + void differentDimensionsShouldNotBeEqual() { + // given + Dimension d1 = new Dimension(100, 50); + Dimension d2 = new Dimension(200, 50); + + // then + assertNotEquals(d1, d2); + } + + @Test + void shouldAllowZeroDimensions() { + // when + Dimension dimension = new Dimension(0, 0); + + // then + assertEquals(0, dimension.width()); + assertEquals(0, dimension.height()); + } + +} diff --git a/spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/geom/HorizontalAlignTests.java b/spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/geom/HorizontalAlignTests.java new file mode 100644 index 000000000..b2be517f4 --- /dev/null +++ b/spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/geom/HorizontalAlignTests.java @@ -0,0 +1,36 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.jline.tui.geom; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class HorizontalAlignTests { + + @Test + void shouldHaveThreeValues() { + assertEquals(3, HorizontalAlign.values().length); + } + + @Test + void shouldContainExpectedValues() { + assertEquals(HorizontalAlign.LEFT, HorizontalAlign.valueOf("LEFT")); + assertEquals(HorizontalAlign.CENTER, HorizontalAlign.valueOf("CENTER")); + assertEquals(HorizontalAlign.RIGHT, HorizontalAlign.valueOf("RIGHT")); + } + +} diff --git a/spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/geom/PositionTests.java b/spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/geom/PositionTests.java new file mode 100644 index 000000000..621e1528a --- /dev/null +++ b/spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/geom/PositionTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.jline.tui.geom; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +class PositionTests { + + @Test + void shouldStoreXAndY() { + // when + Position position = new Position(10, 20); + + // then + assertEquals(10, position.x()); + assertEquals(20, position.y()); + } + + @Test + void equalPositionsShouldBeEqual() { + // given + Position p1 = new Position(5, 10); + Position p2 = new Position(5, 10); + + // then + assertEquals(p1, p2); + assertEquals(p1.hashCode(), p2.hashCode()); + } + + @Test + void differentPositionsShouldNotBeEqual() { + // given + Position p1 = new Position(5, 10); + Position p2 = new Position(10, 5); + + // then + assertNotEquals(p1, p2); + } + + @Test + void shouldAllowZeroPosition() { + // when + Position position = new Position(0, 0); + + // then + assertEquals(0, position.x()); + assertEquals(0, position.y()); + } + + @Test + void shouldAllowNegativeCoordinates() { + // when + Position position = new Position(-5, -10); + + // then + assertEquals(-5, position.x()); + assertEquals(-10, position.y()); + } + +} diff --git a/spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/geom/VerticalAlignTests.java b/spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/geom/VerticalAlignTests.java new file mode 100644 index 000000000..db026feaf --- /dev/null +++ b/spring-shell-jline/src/test/java/org/springframework/shell/jline/tui/geom/VerticalAlignTests.java @@ -0,0 +1,36 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.jline.tui.geom; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class VerticalAlignTests { + + @Test + void shouldHaveThreeValues() { + assertEquals(3, VerticalAlign.values().length); + } + + @Test + void shouldContainExpectedValues() { + assertEquals(VerticalAlign.TOP, VerticalAlign.valueOf("TOP")); + assertEquals(VerticalAlign.CENTER, VerticalAlign.valueOf("CENTER")); + assertEquals(VerticalAlign.BOTTOM, VerticalAlign.valueOf("BOTTOM")); + } + +} diff --git a/spring-shell-test/src/test/java/org/springframework/shell/test/ShellAssertionsTests.java b/spring-shell-test/src/test/java/org/springframework/shell/test/ShellAssertionsTests.java new file mode 100644 index 000000000..33c83fb89 --- /dev/null +++ b/spring-shell-test/src/test/java/org/springframework/shell/test/ShellAssertionsTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.test; + +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class ShellAssertionsTests { + + @Test + void assertThatShouldReturnNonNullAssert() { + // given + ShellScreen screen = ShellScreen.of(List.of("line1")); + + // when + ShellScreenAssert result = ShellAssertions.assertThat(screen); + + // then + assertNotNull(result); + } + + @Test + void assertThatShouldReturnUsableAssert() { + // given + ShellScreen screen = ShellScreen.of(Arrays.asList("hello", "world")); + + // when / then - should not throw + ShellAssertions.assertThat(screen).containsText("hello"); + ShellAssertions.assertThat(screen).containsText("world"); + } + + @Test + void assertThatShouldSupportChaining() { + // given + ShellScreen screen = ShellScreen.of(Arrays.asList("hello world", "foo bar")); + + // when / then - chaining should work + ShellAssertions.assertThat(screen).containsText("hello").containsText("foo"); + } + + @Test + void assertThatShouldFailForMissingText() { + // given + ShellScreen screen = ShellScreen.of(List.of("hello")); + + // when / then + assertThrows(AssertionError.class, () -> ShellAssertions.assertThat(screen).containsText("missing")); + } + +} diff --git a/spring-shell-test/src/test/java/org/springframework/shell/test/ShellScreenTests.java b/spring-shell-test/src/test/java/org/springframework/shell/test/ShellScreenTests.java new file mode 100644 index 000000000..67710c1e2 --- /dev/null +++ b/spring-shell-test/src/test/java/org/springframework/shell/test/ShellScreenTests.java @@ -0,0 +1,98 @@ +/* + * Copyright 2025-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ShellScreenTests { + + @Test + void ofShouldCreateScreenWithLines() { + // given + List lines = Arrays.asList("line1", "line2", "line3"); + + // when + ShellScreen screen = ShellScreen.of(lines); + + // then + assertEquals(3, screen.lines().size()); + assertEquals("line1", screen.lines().get(0)); + assertEquals("line2", screen.lines().get(1)); + assertEquals("line3", screen.lines().get(2)); + } + + @Test + void ofShouldCreateScreenWithEmptyLines() { + // when + ShellScreen screen = ShellScreen.of(Collections.emptyList()); + + // then + assertTrue(screen.lines().isEmpty()); + } + + @Test + void ofShouldCreateScreenWithSingleLine() { + // when + ShellScreen screen = ShellScreen.of(List.of("single line")); + + // then + assertEquals(1, screen.lines().size()); + assertEquals("single line", screen.lines().get(0)); + } + + @Test + void equalScreensShouldBeEqual() { + // given + ShellScreen screen1 = ShellScreen.of(List.of("a", "b")); + ShellScreen screen2 = ShellScreen.of(List.of("a", "b")); + + // then + assertEquals(screen1, screen2); + assertEquals(screen1.hashCode(), screen2.hashCode()); + } + + @Test + void differentScreensShouldNotBeEqual() { + // given + ShellScreen screen1 = ShellScreen.of(List.of("a", "b")); + ShellScreen screen2 = ShellScreen.of(List.of("c", "d")); + + // then + assertNotEquals(screen1, screen2); + } + + @Test + void constructorAndFactoryMethodShouldProduceEqualResults() { + // given + List lines = List.of("test"); + + // when + ShellScreen fromConstructor = new ShellScreen(lines); + ShellScreen fromFactory = ShellScreen.of(lines); + + // then + assertEquals(fromConstructor, fromFactory); + } + +}