diff --git a/spring-shell-table/pom.xml b/spring-shell-table/pom.xml
index 097e3fa8..7e392fbd 100644
--- a/spring-shell-table/pom.xml
+++ b/spring-shell-table/pom.xml
@@ -25,7 +25,7 @@
spring-shell-parent
3.0.0-SNAPSHOT
-
+
Library to Display Fancy ASCII art Tables
@@ -33,6 +33,11 @@
org.springframework
spring-beans
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
diff --git a/spring-shell-table/src/test/java/org/springframework/shell/table/AbstractTestWithSample.java b/spring-shell-table/src/test/java/org/springframework/shell/table/AbstractTestWithSample.java
new file mode 100644
index 00000000..6592b9c1
--- /dev/null
+++ b/spring-shell-table/src/test/java/org/springframework/shell/table/AbstractTestWithSample.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2015-2022 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.table;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.TestInfo;
+
+import org.springframework.util.Assert;
+import org.springframework.util.FileCopyUtils;
+
+/**
+ * Base class that allows reading a sample result rendering of a table, based on the actual
+ * class and method name of the test.
+ *
+ * @author Eric Bottard
+ */
+public class AbstractTestWithSample {
+
+ private String testName;
+
+ @BeforeEach
+ public void setup(TestInfo testInfo) {
+ testName = testInfo.getDisplayName().replace("()", "");
+ }
+
+ protected String sample() throws IOException {
+ String sampleName = String.format("%s-%s.txt",
+ this.getClass().getSimpleName(), testName);
+ InputStream stream = TableTest.class.getResourceAsStream(sampleName);
+ Assert.notNull(stream, "Can't find expected rendering result at " + sampleName);
+ return FileCopyUtils.copyToString(new InputStreamReader(stream, "UTF-8")).replace("&", "");
+ }
+
+ /**
+ * Generate a simple rows x columns model made of chars.
+ */
+ protected TableModel generate(int rows, int columns) {
+ Character[][] data = new Character[rows][columns];
+ for (int row = 0; row < rows; row++) {
+ data[row] = new Character[columns];
+ for (int column = 0; column < columns; column++) {
+ data[row][column] = (char) ('a' + row * columns + column);
+ }
+ }
+ return new ArrayTableModel(data);
+ }
+}
diff --git a/spring-shell-table/src/test/java/org/springframework/shell/table/ArrayTableModelTest.java b/spring-shell-table/src/test/java/org/springframework/shell/table/ArrayTableModelTest.java
new file mode 100644
index 00000000..f4cd976e
--- /dev/null
+++ b/spring-shell-table/src/test/java/org/springframework/shell/table/ArrayTableModelTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2015-2022 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.table;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Tests for ArrayTableModel.
+ *
+ * @author Eric Bottard
+ */
+public class ArrayTableModelTest {
+
+ @Test
+ public void testValid() {
+ TableModel model = new ArrayTableModel(new String[][] {{"a", "b"}, {"c", "d"}});
+ assertThat(model.getColumnCount()).isEqualTo(2);
+ assertThat(model.getRowCount()).isEqualTo(2);
+ assertThat(model.getValue(0, 1)).isEqualTo("b");
+ }
+
+ @Test
+ public void testEmpty() {
+ TableModel model = new ArrayTableModel(new String[][] {});
+ assertThat(model.getColumnCount()).isEqualTo(0);
+ assertThat(model.getRowCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void testInvalidDimensions() {
+ assertThatThrownBy(() -> {
+ new ArrayTableModel(new String[][] {{"a", "b"}, {"c", "d", "e"}});
+ }).isInstanceOf(IllegalArgumentException.class);
+ }
+}
diff --git a/spring-shell-table/src/test/java/org/springframework/shell/table/BeanListTableModelTest.java b/spring-shell-table/src/test/java/org/springframework/shell/table/BeanListTableModelTest.java
new file mode 100644
index 00000000..6cc97931
--- /dev/null
+++ b/spring-shell-table/src/test/java/org/springframework/shell/table/BeanListTableModelTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2015-2022 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.table;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+
+/**
+ * Test for BeanListTableModel.
+ *
+ * @author Eric Bottard
+ */
+public class BeanListTableModelTest extends AbstractTestWithSample {
+
+ @Test
+ public void testSimpleConstructor() throws IOException {
+
+ List data = data();
+
+ Table table = new TableBuilder(new BeanListTableModel(Person.class, data)).build();
+ String result = table.render(80);
+ assertThat(result).isEqualTo(sample());
+ }
+
+ @Test
+ public void testExplicitPropertyNames() throws IOException {
+
+ List data = data();
+
+ Table table = new TableBuilder(new BeanListTableModel(data, "lastName", "firstName")).build();
+ String result = table.render(80);
+ assertThat(result).isEqualTo(sample());
+ }
+
+ @Test
+ public void testHeaderRow() throws IOException {
+
+ List data = data();
+
+ LinkedHashMap header = new LinkedHashMap();
+ header.put("lastName", "Last Name");
+ header.put("firstName", "First Name");
+
+ Table table = new TableBuilder(new BeanListTableModel(data, header)).build();
+ String result = table.render(80);
+ assertThat(result).isEqualTo(sample());
+ }
+
+ private List data() {
+ List data = new ArrayList();
+ data.add(new Person("Alice", "Clark", 12));
+ data.add(new Person("Bob", "Smith", 42));
+ data.add(new Person("Sarah", "Connor", 38));
+ return data;
+ }
+
+ public static class Person {
+ private int age;
+
+ private String firstName;
+
+ private String lastName;
+
+ public Person(String firstName, String lastName, int age) {
+ this.age = age;
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+ }
+}
diff --git a/spring-shell-table/src/test/java/org/springframework/shell/table/BorderFactoryTest.java b/spring-shell-table/src/test/java/org/springframework/shell/table/BorderFactoryTest.java
new file mode 100644
index 00000000..b80aa674
--- /dev/null
+++ b/spring-shell-table/src/test/java/org/springframework/shell/table/BorderFactoryTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2015-2022 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.table;
+
+import java.io.IOException;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.shell.table.BorderStyle.fancy_double;
+
+
+/**
+ * Tests for convenience borders factory.
+ *
+ * @author Eric Bottard
+ */
+public class BorderFactoryTest extends AbstractTestWithSample {
+
+ @Test
+ public void testOutlineBorder() throws IOException {
+ TableModel model = generate(3, 3);
+ Table table = new TableBuilder(model).addOutlineBorder(fancy_double).build();
+ String result = table.render(80);
+ assertThat(result).isEqualTo(sample());
+ }
+
+ @Test
+ public void testFullBorder() throws IOException {
+ TableModel model = generate(3, 3);
+ Table table = new TableBuilder(model).addFullBorder(fancy_double).build();
+ String result = table.render(80);
+ assertThat(result).isEqualTo(sample());
+ }
+
+ @Test
+ public void testHeaderBorder() throws IOException {
+ TableModel model = generate(3, 3);
+ Table table = new TableBuilder(model).addHeaderBorder(fancy_double).build();
+ String result = table.render(80);
+ assertThat(result).isEqualTo(sample());
+ }
+
+ @Test
+ public void testHeaderAndVerticalsBorder() throws IOException {
+ TableModel model = generate(3, 3);
+ Table table = new TableBuilder(model).addHeaderAndVerticalsBorders(fancy_double).build();
+ String result = table.render(80);
+ assertThat(result).isEqualTo(sample());
+ }
+}
diff --git a/spring-shell-table/src/test/java/org/springframework/shell/table/BorderStyleTests.java b/spring-shell-table/src/test/java/org/springframework/shell/table/BorderStyleTests.java
new file mode 100644
index 00000000..cc93e48b
--- /dev/null
+++ b/spring-shell-table/src/test/java/org/springframework/shell/table/BorderStyleTests.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2015-2022 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.table;
+
+import java.io.IOException;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+
+/**
+ * Tests for BorderStyle rendering and combinations.
+ *
+ * @author Eric Bottard
+ */
+public class BorderStyleTests extends AbstractTestWithSample {
+
+ @Test
+ public void testOldSchool() throws IOException {
+ Table table = new TableBuilder(generate(2, 2)).addFullBorder(BorderStyle.oldschool).build();
+ assertThat(table.render(10)).isEqualTo(sample());
+ }
+
+ @Test
+ public void testFancySimple() throws IOException {
+ Table table = new TableBuilder(generate(2, 2)).addFullBorder(BorderStyle.fancy_light).build();
+ assertThat(table.render(10)).isEqualTo(sample());
+ }
+
+ @Test
+ public void testFancyHeavy() throws IOException {
+ Table table = new TableBuilder(generate(2, 2)).addFullBorder(BorderStyle.fancy_heavy).build();
+ assertThat(table.render(10)).isEqualTo(sample());
+ }
+
+ @Test
+ public void testFancyDouble() throws IOException {
+ Table table = new TableBuilder(generate(2, 2)).addFullBorder(BorderStyle.fancy_double).build();
+ assertThat(table.render(10)).isEqualTo(sample());
+ }
+
+ @Test
+ public void testAir() throws IOException {
+ Table table = new TableBuilder(generate(2, 2)).addFullBorder(BorderStyle.air).build();
+ assertThat(table.render(10)).isEqualTo(sample());
+ }
+
+ @Test
+ public void testMixedOldSchoolWithAir() throws IOException {
+ Table table = new TableBuilder(generate(2, 2))
+ .addFullBorder(BorderStyle.air)
+ .addOutlineBorder(BorderStyle.oldschool)
+ .build();
+ assertThat(table.render(10)).isEqualTo(sample());
+ }
+
+ @Test
+ public void testMixedFancyLightAndHeavy() throws IOException {
+ Table table = new TableBuilder(generate(2, 2))
+ .addFullBorder(BorderStyle.fancy_heavy)
+ .addOutlineBorder(BorderStyle.fancy_light)
+ .build();
+ assertThat(table.render(10)).isEqualTo(sample());
+ }
+
+ @Test
+ public void testMixedFancyHeavyAndLight() throws IOException {
+ Table table = new TableBuilder(generate(2, 2))
+ .addFullBorder(BorderStyle.fancy_light)
+ .addOutlineBorder(BorderStyle.fancy_heavy)
+ .build();
+ assertThat(table.render(10)).isEqualTo(sample());
+ }
+
+ @Test
+ public void testMixedDoubleAndSingle() throws IOException {
+ Table table = new TableBuilder(generate(2, 2))
+ .addFullBorder(BorderStyle.fancy_light)
+ .addOutlineBorder(BorderStyle.fancy_double)
+ .build();
+ assertThat(table.render(10)).isEqualTo(sample());
+ }
+
+ @Test
+ public void testMixedSingleAndDouble() throws IOException {
+ Table table = new TableBuilder(generate(2, 2))
+ .addFullBorder(BorderStyle.fancy_double)
+ .addOutlineBorder(BorderStyle.fancy_light)
+ .build();
+ assertThat(table.render(10)).isEqualTo(sample());
+ }
+
+ @Test
+ public void testMixedLightInternalAndHeavy() throws IOException {
+ Table table = new TableBuilder(generate(3, 3))
+ .addFullBorder(BorderStyle.fancy_heavy)
+ .paintBorder(BorderStyle.fancy_light, BorderSpecification.OUTLINE).fromRowColumn(1, 1).toRowColumn(2, 2)
+ .build();
+ assertThat(table.render(10)).isEqualTo(sample());
+ }
+
+ @Test
+ public void testMixedHeavyInternalAndLight() throws IOException {
+ Table table = new TableBuilder(generate(3, 3))
+ .addFullBorder(BorderStyle.fancy_light)
+ .paintBorder(BorderStyle.fancy_heavy, BorderSpecification.OUTLINE).fromRowColumn(1, 1).toRowColumn(2, 2)
+ .build();
+ assertThat(table.render(10)).isEqualTo(sample());
+ }
+
+ @Test
+ public void testHeavyOutlineAndHeader_LightVerticals_AirHorizontals() throws IOException {
+ Table table = new TableBuilder(generate(4, 4))
+ .addOutlineBorder(BorderStyle.fancy_heavy)
+ .paintBorder(BorderStyle.fancy_light, BorderSpecification.INNER_VERTICAL).fromTopLeft().toBottomRight()
+ .paintBorder(BorderStyle.air, BorderSpecification.INNER_HORIZONTAL).fromTopLeft().toBottomRight()
+ .paintBorder(BorderStyle.fancy_heavy, BorderSpecification.OUTLINE).fromTopLeft().toRowColumn(1, 4)
+ .build();
+ assertThat(table.render(10)).isEqualTo(sample());
+ }
+}
diff --git a/spring-shell-table/src/test/java/org/springframework/shell/table/DelimiterTextWrapperTest.java b/spring-shell-table/src/test/java/org/springframework/shell/table/DelimiterTextWrapperTest.java
new file mode 100644
index 00000000..e0cf049b
--- /dev/null
+++ b/spring-shell-table/src/test/java/org/springframework/shell/table/DelimiterTextWrapperTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2015-2022 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.table;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for DelimiterTextWrapper.
+ *
+ * @author Eric Bottard
+ */
+public class DelimiterTextWrapperTest {
+
+ private TextWrapper wrapper = new DelimiterTextWrapper();
+
+ @Test
+ public void testNoWordSplit() {
+ String[] text = new String[] {"the quick brown fox jumps over the lazy dog."};
+ assertThat(wrapper.wrap(text, 10)).containsExactly("the quick ", "brown fox ", "jumps over", "the lazy ",
+ "dog. ");
+ }
+
+ @Test
+ public void testWordSplit() {
+ String[] text = new String[] {"the quick brown fox jumps over the lazy dog."};
+ assertThat(wrapper.wrap(text, 4)).containsExactly("the ", "quic", "k ", "brow", "n ", "fox ", "jump",
+ "s ", "over", "the ", "lazy", "dog.");
+ }
+}
diff --git a/spring-shell-table/src/test/java/org/springframework/shell/table/KeyValueRenderingTests.java b/spring-shell-table/src/test/java/org/springframework/shell/table/KeyValueRenderingTests.java
new file mode 100644
index 00000000..0a8ec240
--- /dev/null
+++ b/spring-shell-table/src/test/java/org/springframework/shell/table/KeyValueRenderingTests.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2015-2022 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.table;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests related to rendering Maps.
+ *
+ * @author Eric Bottard
+ */
+public class KeyValueRenderingTests extends AbstractTestWithSample {
+
+ @Test
+ public void testRenderConstrained() throws IOException {
+ Map values = new LinkedHashMap();
+ values.put("a", "b");
+ values.put("long-key", "c");
+ values.put("d", "long-value");
+ TableModel model = new ArrayTableModel(new Object[][] {{"Thing", "Properties"}, {"Something", values}});
+ TableBuilder tableBuilder = new TableBuilder(model)
+ .addHeaderAndVerticalsBorders(BorderStyle.fancy_light);
+ Tables.configureKeyValueRendering(tableBuilder, " = ");
+ Table table = tableBuilder.build();
+ String result = table.render(10);
+ assertThat(result).isEqualTo(sample());
+ }
+
+ @Test
+ public void testRenderUnconstrained() throws IOException {
+ Map values = new LinkedHashMap();
+ values.put("a", "b");
+ values.put("long-key", "c");
+ values.put("d", "long-value");
+ TableModel model = new ArrayTableModel(new Object[][] {{"Thing", "Properties"}, {"Something", values}});
+ TableBuilder tableBuilder = new TableBuilder(model)
+ .addHeaderAndVerticalsBorders(BorderStyle.fancy_light);
+ Tables.configureKeyValueRendering(tableBuilder, " = ");
+ Table table = tableBuilder.build();
+ String result = table.render(80);
+ assertThat(result).isEqualTo(sample());
+ }
+}
diff --git a/spring-shell-table/src/test/java/org/springframework/shell/table/TableModelBuilderTests.java b/spring-shell-table/src/test/java/org/springframework/shell/table/TableModelBuilderTests.java
new file mode 100644
index 00000000..3dbae064
--- /dev/null
+++ b/spring-shell-table/src/test/java/org/springframework/shell/table/TableModelBuilderTests.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2015-2022 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.table;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Tests for TableModelBuilder.
+ *
+ * @author Eric Bottard
+ */
+public class TableModelBuilderTests {
+
+ @Test
+ public void emptyModel() {
+ TableModelBuilder builder = new TableModelBuilder();
+ TableModel model = builder.build();
+ assertThat(model.getColumnCount()).isEqualTo(0);
+ assertThat(model.getColumnCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void testFrozen() {
+ assertThatThrownBy(() -> {
+ TableModelBuilder builder = new TableModelBuilder();
+ builder.addRow().addValue(5);
+ builder.build();
+ builder.addRow();
+ }).isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ public void testAddingTooManyValues() {
+ assertThatThrownBy(() -> {
+ TableModelBuilder builder = new TableModelBuilder();
+ builder.addRow().addValue(5);
+ builder.addRow().addValue(1).addValue(2);
+ builder.build();
+ }).isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ public void testAddingLessValues() {
+ assertThatThrownBy(() -> {
+ TableModelBuilder builder = new TableModelBuilder();
+ builder.addRow().addValue(1).addValue(2);
+ builder.addRow().addValue(5);
+ builder.addRow();
+ builder.build();
+ }).isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ public void simpleBuild() {
+ TableModelBuilder builder = new TableModelBuilder();
+ builder
+ .addRow()
+ .addValue(7).addValue(2)
+ .addRow()
+ .addValue(3).addValue(5.5)
+ .addRow()
+ .addValue(1).addValue(4);
+
+ TableModel model = builder.build();
+ assertThat(model.getColumnCount()).isEqualTo(2);
+ assertThat(model.getRowCount()).isEqualTo(3);
+ assertThat(model.getValue(1, 1)).isEqualTo(5.5);
+ }
+}
diff --git a/spring-shell-table/src/test/java/org/springframework/shell/table/TableModelTest.java b/spring-shell-table/src/test/java/org/springframework/shell/table/TableModelTest.java
new file mode 100644
index 00000000..db3cf918
--- /dev/null
+++ b/spring-shell-table/src/test/java/org/springframework/shell/table/TableModelTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2015-2022 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.table;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for TableModel.
+ *
+ * @author Eric Bottard
+ */
+public class TableModelTest {
+
+ @Test
+ public void testTranspose() {
+ TableModel model = new ArrayTableModel(new String[][] {{"a", "b", "c"}, {"d", "e", "f"}});
+
+ assertThat(model.transpose().getColumnCount()).isEqualTo(2);
+ assertThat(model.transpose().getRowCount()).isEqualTo(3);
+ assertThat(model.transpose().getValue(2, 1)).isEqualTo("f");
+ }
+}
diff --git a/spring-shell-table/src/test/java/org/springframework/shell/table/TableTest.java b/spring-shell-table/src/test/java/org/springframework/shell/table/TableTest.java
new file mode 100644
index 00000000..e3981668
--- /dev/null
+++ b/spring-shell-table/src/test/java/org/springframework/shell/table/TableTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2015-2022 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.table;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.shell.table.SimpleHorizontalAligner.*;
+import static org.springframework.shell.table.SimpleVerticalAligner.*;
+
+import java.io.IOException;
+
+
+/**
+ * Tests for Table rendering.
+ *
+ * @author Eric Bottard
+ */
+public class TableTest extends AbstractTestWithSample {
+
+ @Test
+ public void testEmptyModel() {
+ TableModel model = new ArrayTableModel(new Object[0][0]);
+ Table table = new TableBuilder(model).build();
+ String result = table.render(80);
+ assertThat(result).isEqualTo("");
+ }
+
+ @Test
+ public void testPreformattedModel() {
+ TableModel model = generate(2, 2);
+ Table table = new TableBuilder(model).build();
+ String result = table.render(80);
+ assertThat(result).isEqualTo("ab\ncd\n");
+ }
+
+ @Test
+ public void testExpandingColumns() throws IOException {
+ TableModel model = new ArrayTableModel(new String[][] {{"a", "b"}, {"ccc", "d"}});
+ Table table = new TableBuilder(model).build();
+ String result = table.render(80);
+ assertThat(result).isEqualTo(sample());
+ }
+
+ @Test
+ public void testRightAlignment() throws IOException {
+ TableModel model = new ArrayTableModel(new String[][] {{"a\na\na", "bbb"}, {"ccc", "d"}});
+ Table table = new TableBuilder(model).on(CellMatchers.column(1)).addAligner(right).build();
+ String result = table.render(80);
+ assertThat(result).isEqualTo(sample());
+ }
+
+ @Test
+ public void testVerticalAlignment() throws IOException {
+ TableModel model = new ArrayTableModel(new String[][] {{"a\na\na", "bbb"}, {"ccc", "d"}});
+ Table table = new TableBuilder(model).on(CellMatchers.row(0)).addAligner(middle).build();
+ String result = table.render(80);
+ assertThat(result).isEqualTo(sample());
+ }
+
+ @Test
+ public void testAutoWrapping() throws IOException {
+ TableModel model = new ArrayTableModel(new String[][] {{"this is a long line", "bbb"}, {"ccc", "d"}});
+ Table table = new TableBuilder(model).build();
+ String result = table.render(10);
+ assertThat(result).isEqualTo(sample());
+ }
+
+ @Test
+ public void testOverflow() throws IOException {
+ TableModel model = new ArrayTableModel(new String[][] {{"this is a long line", "bbb"}, {"ccc", "d"}});
+ Table table = new TableBuilder(model).build();
+ String result = table.render(3);
+ assertThat(result).isEqualTo(sample());
+ }
+
+ @Test
+ public void testEmptyCellsVerticalAligner() throws IOException {
+ TableModel model = new ArrayTableModel(new String[][] {{"a", "b"}, {null, null}});
+ Table table = new TableBuilder(model).on(CellMatchers.table()).addAligner(SimpleVerticalAligner.middle).build();
+ table.render(3);
+ }
+}
diff --git a/spring-shell-table/src/test/resources/org/springframework/shell/table/BeanListTableModelTest-testExplicitPropertyNames.txt b/spring-shell-table/src/test/resources/org/springframework/shell/table/BeanListTableModelTest-testExplicitPropertyNames.txt
new file mode 100644
index 00000000..3f0574d5
--- /dev/null
+++ b/spring-shell-table/src/test/resources/org/springframework/shell/table/BeanListTableModelTest-testExplicitPropertyNames.txt
@@ -0,0 +1,3 @@
+Clark Alice&
+Smith Bob &
+ConnorSarah&
diff --git a/spring-shell-table/src/test/resources/org/springframework/shell/table/BeanListTableModelTest-testHeaderRow.txt b/spring-shell-table/src/test/resources/org/springframework/shell/table/BeanListTableModelTest-testHeaderRow.txt
new file mode 100644
index 00000000..21155574
--- /dev/null
+++ b/spring-shell-table/src/test/resources/org/springframework/shell/table/BeanListTableModelTest-testHeaderRow.txt
@@ -0,0 +1,4 @@
+Last NameFirst Name&
+Clark Alice &
+Smith Bob &
+Connor Sarah &
diff --git a/spring-shell-table/src/test/resources/org/springframework/shell/table/BeanListTableModelTest-testSimpleConstructor.txt b/spring-shell-table/src/test/resources/org/springframework/shell/table/BeanListTableModelTest-testSimpleConstructor.txt
new file mode 100644
index 00000000..c2b587b3
--- /dev/null
+++ b/spring-shell-table/src/test/resources/org/springframework/shell/table/BeanListTableModelTest-testSimpleConstructor.txt
@@ -0,0 +1,3 @@
+12AliceClark &
+42Bob Smith &
+38SarahConnor&
diff --git a/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderFactoryTest-testFullBorder.txt b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderFactoryTest-testFullBorder.txt
new file mode 100644
index 00000000..8646d826
--- /dev/null
+++ b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderFactoryTest-testFullBorder.txt
@@ -0,0 +1,7 @@
+╔═╦═╦═╗
+║a║b║c║
+╠═╬═╬═╣
+║d║e║f║
+╠═╬═╬═╣
+║g║h║i║
+╚═╩═╩═╝
diff --git a/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderFactoryTest-testHeaderAndVerticalsBorder.txt b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderFactoryTest-testHeaderAndVerticalsBorder.txt
new file mode 100644
index 00000000..6049d713
--- /dev/null
+++ b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderFactoryTest-testHeaderAndVerticalsBorder.txt
@@ -0,0 +1,6 @@
+╔═╦═╦═╗
+║a║b║c║
+╠═╬═╬═╣
+║d║e║f║
+║g║h║i║
+╚═╩═╩═╝
diff --git a/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderFactoryTest-testHeaderBorder.txt b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderFactoryTest-testHeaderBorder.txt
new file mode 100644
index 00000000..1b02abec
--- /dev/null
+++ b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderFactoryTest-testHeaderBorder.txt
@@ -0,0 +1,6 @@
+╔═══╗
+║abc║
+╠═══╣
+║def║
+║ghi║
+╚═══╝
diff --git a/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderFactoryTest-testOutlineBorder.txt b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderFactoryTest-testOutlineBorder.txt
new file mode 100644
index 00000000..14ca640d
--- /dev/null
+++ b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderFactoryTest-testOutlineBorder.txt
@@ -0,0 +1,5 @@
+╔═══╗
+║abc║
+║def║
+║ghi║
+╚═══╝
diff --git a/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testAir.txt b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testAir.txt
new file mode 100644
index 00000000..76e94529
--- /dev/null
+++ b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testAir.txt
@@ -0,0 +1,5 @@
+ &
+ a b &
+ &
+ c d &
+ &
diff --git a/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testFancyDouble.txt b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testFancyDouble.txt
new file mode 100644
index 00000000..5ee0a47d
--- /dev/null
+++ b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testFancyDouble.txt
@@ -0,0 +1,5 @@
+╔═╦═╗
+║a║b║
+╠═╬═╣
+║c║d║
+╚═╩═╝
diff --git a/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testFancyHeavy.txt b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testFancyHeavy.txt
new file mode 100644
index 00000000..6483343f
--- /dev/null
+++ b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testFancyHeavy.txt
@@ -0,0 +1,5 @@
+┏━┳━┓
+┃a┃b┃
+┣━╋━┫
+┃c┃d┃
+┗━┻━┛
diff --git a/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testFancySimple.txt b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testFancySimple.txt
new file mode 100644
index 00000000..fc56b325
--- /dev/null
+++ b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testFancySimple.txt
@@ -0,0 +1,5 @@
+┌─┬─┐
+│a│b│
+├─┼─┤
+│c│d│
+└─┴─┘
diff --git a/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testHeavyOutlineAndHeader_LightVerticals_AirHorizontals.txt b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testHeavyOutlineAndHeader_LightVerticals_AirHorizontals.txt
new file mode 100644
index 00000000..e1132f0d
--- /dev/null
+++ b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testHeavyOutlineAndHeader_LightVerticals_AirHorizontals.txt
@@ -0,0 +1,9 @@
+┏━┯━┯━┯━┓
+┃a│b│c│d┃
+┣━┿━┿━┿━┫
+┃e│f│g│h┃
+┃ │ │ │ ┃
+┃i│j│k│l┃
+┃ │ │ │ ┃
+┃m│n│o│p┃
+┗━┷━┷━┷━┛
diff --git a/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testMixedDoubleAndSingle.txt b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testMixedDoubleAndSingle.txt
new file mode 100644
index 00000000..f03ef353
--- /dev/null
+++ b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testMixedDoubleAndSingle.txt
@@ -0,0 +1,5 @@
+╔═╤═╗
+║a│b║
+╟─┼─╢
+║c│d║
+╚═╧═╝
diff --git a/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testMixedFancyHeavyAndLight.txt b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testMixedFancyHeavyAndLight.txt
new file mode 100644
index 00000000..4ae9817c
--- /dev/null
+++ b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testMixedFancyHeavyAndLight.txt
@@ -0,0 +1,5 @@
+┏━┯━┓
+┃a│b┃
+┠─┼─┨
+┃c│d┃
+┗━┷━┛
diff --git a/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testMixedFancyLightAndHeavy.txt b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testMixedFancyLightAndHeavy.txt
new file mode 100644
index 00000000..793f1f98
--- /dev/null
+++ b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testMixedFancyLightAndHeavy.txt
@@ -0,0 +1,5 @@
+┌─┰─┐
+│a┃b│
+┝━╋━┥
+│c┃d│
+└─┸─┘
diff --git a/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testMixedHeavyInternalAndLight.txt b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testMixedHeavyInternalAndLight.txt
new file mode 100644
index 00000000..8a2be312
--- /dev/null
+++ b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testMixedHeavyInternalAndLight.txt
@@ -0,0 +1,7 @@
+┌─┬─┬─┐
+│a│b│c│
+├─╆━╅─┤
+│d┃e┃f│
+├─╄━╃─┤
+│g│h│i│
+└─┴─┴─┘
diff --git a/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testMixedLightInternalAndHeavy.txt b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testMixedLightInternalAndHeavy.txt
new file mode 100644
index 00000000..208de4c9
--- /dev/null
+++ b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testMixedLightInternalAndHeavy.txt
@@ -0,0 +1,7 @@
+┏━┳━┳━┓
+┃a┃b┃c┃
+┣━╃─╄━┫
+┃d│e│f┃
+┣━╅─╆━┫
+┃g┃h┃i┃
+┗━┻━┻━┛
diff --git a/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testMixedOldSchoolWithAir.txt b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testMixedOldSchoolWithAir.txt
new file mode 100644
index 00000000..4898ef20
--- /dev/null
+++ b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testMixedOldSchoolWithAir.txt
@@ -0,0 +1,5 @@
++---+
+|a b|
+| |
+|c d|
++---+
diff --git a/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testMixedSingleAndDouble.txt b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testMixedSingleAndDouble.txt
new file mode 100644
index 00000000..b38fe0d0
--- /dev/null
+++ b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testMixedSingleAndDouble.txt
@@ -0,0 +1,5 @@
+┌─╥─┐
+│a║b│
+╞═╬═╡
+│c║d│
+└─╨─┘
diff --git a/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testMixedSingleInternalAndDouble.txt b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testMixedSingleInternalAndDouble.txt
new file mode 100644
index 00000000..e69de29b
diff --git a/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testOldSchool.txt b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testOldSchool.txt
new file mode 100644
index 00000000..1dfa83b8
--- /dev/null
+++ b/spring-shell-table/src/test/resources/org/springframework/shell/table/BorderStyleTests-testOldSchool.txt
@@ -0,0 +1,5 @@
++-+-+
+|a|b|
++-+-+
+|c|d|
++-+-+
diff --git a/spring-shell-table/src/test/resources/org/springframework/shell/table/KeyValueRenderingTests-testRenderConstrained.txt b/spring-shell-table/src/test/resources/org/springframework/shell/table/KeyValueRenderingTests-testRenderConstrained.txt
new file mode 100644
index 00000000..1fa83024
--- /dev/null
+++ b/spring-shell-table/src/test/resources/org/springframework/shell/table/KeyValueRenderingTests-testRenderConstrained.txt
@@ -0,0 +1,9 @@
+┌─────────┬──────────┐
+│Thing │Properties│
+├─────────┼──────────┤
+│Something│a = b │
+│ │long-key │
+│ │ = c │
+│ │d = │
+│ │long-value│
+└─────────┴──────────┘
diff --git a/spring-shell-table/src/test/resources/org/springframework/shell/table/KeyValueRenderingTests-testRenderUnconstrained.txt b/spring-shell-table/src/test/resources/org/springframework/shell/table/KeyValueRenderingTests-testRenderUnconstrained.txt
new file mode 100644
index 00000000..7029aca1
--- /dev/null
+++ b/spring-shell-table/src/test/resources/org/springframework/shell/table/KeyValueRenderingTests-testRenderUnconstrained.txt
@@ -0,0 +1,7 @@
+┌─────────┬─────────────────────┐
+│Thing │Properties │
+├─────────┼─────────────────────┤
+│Something│ a = b │
+│ │long-key = c │
+│ │ d = long-value│
+└─────────┴─────────────────────┘
diff --git a/spring-shell-table/src/test/resources/org/springframework/shell/table/TableTest-testAutoWrapping.txt b/spring-shell-table/src/test/resources/org/springframework/shell/table/TableTest-testAutoWrapping.txt
new file mode 100644
index 00000000..27ff050f
--- /dev/null
+++ b/spring-shell-table/src/test/resources/org/springframework/shell/table/TableTest-testAutoWrapping.txt
@@ -0,0 +1,4 @@
+this isbbb&
+a long &
+line &
+ccc d &
diff --git a/spring-shell-table/src/test/resources/org/springframework/shell/table/TableTest-testExpandingColumns.txt b/spring-shell-table/src/test/resources/org/springframework/shell/table/TableTest-testExpandingColumns.txt
new file mode 100644
index 00000000..9e0cab8e
--- /dev/null
+++ b/spring-shell-table/src/test/resources/org/springframework/shell/table/TableTest-testExpandingColumns.txt
@@ -0,0 +1,2 @@
+a b&
+cccd&
diff --git a/spring-shell-table/src/test/resources/org/springframework/shell/table/TableTest-testOverflow.txt b/spring-shell-table/src/test/resources/org/springframework/shell/table/TableTest-testOverflow.txt
new file mode 100644
index 00000000..f52d06da
--- /dev/null
+++ b/spring-shell-table/src/test/resources/org/springframework/shell/table/TableTest-testOverflow.txt
@@ -0,0 +1,5 @@
+thisbbb&
+is a &
+long &
+line &
+ccc d &
diff --git a/spring-shell-table/src/test/resources/org/springframework/shell/table/TableTest-testRightAlignment.txt b/spring-shell-table/src/test/resources/org/springframework/shell/table/TableTest-testRightAlignment.txt
new file mode 100644
index 00000000..c8bdc6ec
--- /dev/null
+++ b/spring-shell-table/src/test/resources/org/springframework/shell/table/TableTest-testRightAlignment.txt
@@ -0,0 +1,4 @@
+a bbb&
+a &
+a &
+ccc d&
diff --git a/spring-shell-table/src/test/resources/org/springframework/shell/table/TableTest-testVerticalAlignment.txt b/spring-shell-table/src/test/resources/org/springframework/shell/table/TableTest-testVerticalAlignment.txt
new file mode 100644
index 00000000..c19dd858
--- /dev/null
+++ b/spring-shell-table/src/test/resources/org/springframework/shell/table/TableTest-testVerticalAlignment.txt
@@ -0,0 +1,4 @@
+a &
+a bbb&
+a &
+cccd &