Repository: Nukesor/comfy-table
Pull request #115 (2023-06-16 11:24:57)

Several formatting fixes and other fixes


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5baeee7..df7fc37 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 
 - Fix a panic when working with extreme paddings, where `(padding.left + padding.right) > u16::MAX`.
 - Fix a panic when working with extremely long content, where `(content_width + padding) > u16::MAX`.
+- Properly enforce lower boundery constraints.
+  Previously, "normal" columns were allocated before lower boundaries were respected.
+  This could lead to scenarios, where the table would grow beyond the specified size, when there was a lower boundary.
+- Fix calculation of column widths for empty columns.
+  The minimum content width for a column is `1` char, but the `column_max_content_widths` function on the table returned a `0` width for fully empty columns.
+  This resulted in tables becoming larger than specified if there were any empty columns.
+
+## Misc
+
+- Extend property tests, which lead to the discovery some bugs.
 
 ## [7.0.0] - 2023-06-06
 
diff --git a/src/style/column.rs b/src/style/column.rs
index 639d050..10e4bd6 100644
--- a/src/style/column.rs
+++ b/src/style/column.rs
@@ -16,8 +16,13 @@ pub enum ColumnConstraint {
     /// Enforce a absolute width for a column.
     Absolute(Width),
     /// Specify a lower boundary, either fixed or as percentage of the total width.
+    /// A column with this constraint will be at least as wide as specified.
+    /// If the content isn't as long as that boundary, it will be padded.
+    /// If the column has longer content and is allowed to grow, the column may take more space.
     LowerBoundary(Width),
     /// Specify a upper boundary, either fixed or as percentage of the total width.
+    /// A column with this constriant will be at most as wide as specified.
+    /// The column may be smaller than that width.
     UpperBoundary(Width),
     /// Specify both, an upper and a lower boundary.
     Boundaries { lower: Width, upper: Width },
diff --git a/src/table.rs b/src/table.rs
index fad5b01..0ccc815 100644
--- a/src/table.rs
+++ b/src/table.rs
@@ -682,7 +682,9 @@ impl Table {
             // Get the max width for each cell of the row
             let row_max_widths = row.max_content_widths();
             for (index, width) in row_max_widths.iter().enumerate() {
-                let width = (*width).try_into().unwrap_or(u16::MAX);
+                let mut width = (*width).try_into().unwrap_or(u16::MAX);
+                // A column's content is at least 1 char wide.
+                width = std::cmp::max(1, width);
 
                 // Set a new max, if the current cell is the longest for that column.
                 let current_max = max_widths[index];
diff --git a/src/utils/arrangement/dynamic.rs b/src/utils/arrangement/dynamic.rs
index 1d06235..a69a0b6 100644
--- a/src/utils/arrangement/dynamic.rs
+++ b/src/utils/arrangement/dynamic.rs
@@ -10,13 +10,13 @@ use crate::{Column, Table};
 /// Try to find the best fit for a given content and table_width
 ///
 /// 1. Determine the amount of available space after applying fixed columns, padding, and borders.
-/// 2. Check if there are any columns that require less space than the average
-///    remaining space for the remaining columns. (This includes the MaxWidth constraint).
-/// 3. Take those columns, fix their size and add the surplus in space to the remaining space.
-/// 4. Repeat step 2-3 until no columns with smaller size than average remaining space are left.
-/// 5. Now that we know how much space we have to work with, we have to check again for
+/// 2. Now that we know how much space we have to work with, we have to check again for
 ///    LowerBoundary constraints. If there are any columns that have a higher LowerBoundary,
 ///    we have to fix that column to this size.
+/// 3. Check if there are any columns that require less space than the average
+///    remaining space for the remaining columns. (This includes the MaxWidth constraint).
+/// 4. Take those columns, fix their size and add the surplus in space to the remaining space.
+/// 5. Repeat step 2-3 until no columns with smaller size than average remaining space are left.
 /// 6. At this point, the remaining spaces is equally distributed between all columns.
 ///    It get's a little tricky now. Check the documentation of [optimize_space_after_split]
 ///    for more information.
@@ -36,26 +36,18 @@ pub fn arrange(
 
     // Step 1
     // Find out how much space there is left.
-    let remaining_width: usize =
+    let mut remaining_width: usize =
         available_content_width(table, infos, visible_columns, table_width);
+    let mut remaining_columns = count_remaining_columns(visible_columns, infos);
 
     #[cfg(feature = "debug")]
     println!(
         "dynamic::arrange: Table width: {table_width}, Start remaining width {remaining_width}"
     );
+    #[cfg(feature = "debug")]
+    println!("dynamic::arrange: Max content widths: {max_content_widths:#?}");
 
-    // Step 2-4.
-    // Find all columns that require less space than the average.
-    // Returns the remaining available width and the amount of remaining columns that need handling
-    let (mut remaining_width, mut remaining_columns) = find_columns_that_fit_into_average(
-        table,
-        infos,
-        remaining_width,
-        visible_columns,
-        max_content_widths,
-    );
-
-    // Step 5.
+    // Step 2.
     //
     // Iterate through all undecided columns and enforce LowerBoundary constraints, if they're
     // bigger than the current average space.
@@ -69,6 +61,18 @@ pub fn arrange(
         );
     }
 
+    // Step 3-5.
+    // Find all columns that require less space than the average.
+    // Returns the remaining available width and the amount of remaining columns that need handling
+    let (mut remaining_width, mut remaining_columns) = find_columns_that_fit_into_average(
+        table,
+        infos,
+        remaining_width,
+        remaining_columns,
+        visible_columns,
+        max_content_widths,
+    );
+
     #[cfg(feature = "debug")]
     {
         println!("After less than average: {infos:#?}");
@@ -193,11 +197,11 @@ fn find_columns_that_fit_into_average(
     table: &Table,
     infos: &mut DisplayInfos,
     mut remaining_width: usize,
+    mut remaining_columns: usize,
     visible_columns: usize,
     max_content_widths: &[u16],
 ) -> (usize, usize) {
     let mut found_smaller = true;
-    let mut remaining_columns = count_remaining_columns(visible_columns, infos);
     while found_smaller {
         found_smaller = false;
 
diff --git a/src/utils/formatting/content_split/mod.rs b/src/utils/formatting/content_split/mod.rs
index 4c654d8..09f957c 100644
--- a/src/utils/formatting/content_split/mod.rs
+++ b/src/utils/formatting/content_split/mod.rs
@@ -95,22 +95,19 @@ pub fn split_line(line: &str, info: &ColumnDisplayInfo, delimiter: char) -> Vec<
             // Only add delimiter, if we're not on a fresh line
             if !new_line {
                 current_line.push(delimiter);
-                //remaining_width = remaining_width.saturating_sub(1);
             }
 
             let (mut next, mut remaining) = split_long_word(remaining_width, &next);
 
-            // TODO: This is a pretty hefty hack, but it's needed for now.
+            // This is a ugly hack, but it's needed for now.
             //
-            // Scenario: We try to split a word that doesn't fit into the current line.
-            // It's supposed to be a new line, with a width of 1. However, the next char in line
-            // is a multi-character UTF-8 symbol.
+            // Scenario: The current column has to have a width of 1 and we work with a new line.
+            // However, the next char is a multi-character UTF-8 symbol.
             //
-            // Since a, for instance, two-character wide symbol doesn't fit into a 1-character
-            // column, this code would loop endlessly. (There's no legitimate way to split that
-            // word.)
+            // Since a multi-character wide symbol doesn't fit into a 1-character column,
+            // this code would loop endlessly. (There's no legitimate way to split that character.)
             // Hence, we have to live with the fact, that this line will look broken, as we put a
-            // two-character wide symbol into it.
+            // two-character wide symbol into it, despite the line being formatted for 1 character.
             if new_line && next.is_empty() {
                 let mut chars = remaining.chars();
                 next.push(chars.next().unwrap());


Test patch:
diff --git a/tests/all/add_predicate.rs b/tests/all/add_predicate.rs
index 3e38245..689f609 100644
--- a/tests/all/add_predicate.rs
+++ b/tests/all/add_predicate.rs
@@ -32,7 +32,7 @@ fn add_predicate_single_true() {
 |                      | add some             |                        |
 |                      | multi line stuff     |                        |
 +----------------------+----------------------+------------------------+";
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 #[test]
@@ -61,7 +61,7 @@ fn add_predicate_single_false() {
 +================================================================+
 | This is a text | This is another text | This is the third text |
 +----------------+----------------------+------------------------+";
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 #[test]
@@ -98,7 +98,7 @@ fn add_predicate_single_mixed() {
 |                      | add some             |                        |
 |                      | multi line stuff     |                        |
 +----------------------+----------------------+------------------------+";
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 #[test]
@@ -127,7 +127,7 @@ fn add_predicate_single_wrong_row_count() {
 +================================================================+
 | This is a text | This is another text | This is the third text |
 +----------------+----------------------+------------------------+";
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 #[test]
@@ -161,7 +161,7 @@ fn add_predicate_multi_true() {
 |                      | add some             |                        |
 |                      | multi line stuff     |                        |
 +----------------------+----------------------+------------------------+";
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 #[test]
@@ -190,7 +190,7 @@ fn add_predicate_multi_false() {
 +================================================================+
 | This is a text | This is another text | This is the third text |
 +----------------+----------------------+------------------------+";
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 #[test]
@@ -232,7 +232,7 @@ fn add_predicate_multi_mixed() {
 |                      | add some             |                        |
 |                      | multi line stuff     |                        |
 +----------------------+----------------------+------------------------+";
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 #[test]
@@ -261,5 +261,5 @@ fn add_predicate_multi_wrong_rows_count() {
 +================================================================+
 | This is a text | This is another text | This is the third text |
 +----------------+----------------------+------------------------+";
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
diff --git a/tests/all/alignment_test.rs b/tests/all/alignment_test.rs
index f18e7dd..7b07f42 100644
--- a/tests/all/alignment_test.rs
+++ b/tests/all/alignment_test.rs
@@ -44,5 +44,5 @@ fn cell_alignment() {
 |---------------------+---------------------+---------------------|
 | Left                |        Center       |               Right |
 +---------------------+---------------------+---------------------+";
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
diff --git a/tests/all/combined_test.rs b/tests/all/combined_test.rs
index 65281db..7a0d58d 100644
--- a/tests/all/combined_test.rs
+++ b/tests/all/combined_test.rs
@@ -52,5 +52,5 @@ fn combined_features() {
 │                     ┆ atshoulddynamicallywrap       ┆                        │
 └─────────────────────┴───────────────────────────────┴────────────────────────┘";
     println!("{expected}");
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
diff --git a/tests/all/constraints_test.rs b/tests/all/constraints_test.rs
index c52e79d..36884dc 100644
--- a/tests/all/constraints_test.rs
+++ b/tests/all/constraints_test.rs
@@ -53,7 +53,7 @@ fn fixed_max_min_constraints() {
 |          | stuff  |          |
 +----------+--------+----------+";
     println!("{expected}");
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 
     // Now try this again when using dynamic content arrangement
     // The table tries to arrange to 28 characters,
@@ -98,7 +98,7 @@ fn fixed_max_min_constraints() {
 |          | f  |          |
 +----------+----+----------+";
     println!("{expected}");
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 #[test]
@@ -121,7 +121,7 @@ fn unnecessary_max_min_constraints() {
 |      | multi line stuff     |                        |
 +------+----------------------+------------------------+";
     println!("{expected}");
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 
     // Now test for dynamic content arrangement
     table.set_content_arrangement(ContentArrangement::Dynamic);
@@ -137,7 +137,7 @@ fn unnecessary_max_min_constraints() {
 |      | multi line stuff     |                        |
 +------+----------------------+------------------------+";
     println!("{expected}");
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 #[test]
@@ -175,7 +175,7 @@ fn constraints_bigger_than_table_width() {
 | l |                              |                        |
 +---+------------------------------+------------------------+";
     println!("{expected}");
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 #[test]
@@ -205,7 +205,7 @@ fn percentage() {
 |       | stuff         |              |
 +-------+---------------+--------------+";
     println!("{expected}");
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 #[test]
@@ -228,7 +228,7 @@ fn max_100_percentage() {
 +--------------------------------------+";
     println!("{expected}");
     assert_table_line_width(&table, 40);
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 #[test]
@@ -261,7 +261,7 @@ fn percentage_second() {
 |              | stuff    |          |
 +--------------+----------+----------+";
     println!("{expected}");
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 #[test]
@@ -294,7 +294,7 @@ fn max_percentage() {
 |      | stuff    |          |
 +------+----------+----------+";
     println!("{expected}");
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 #[test]
@@ -333,7 +333,7 @@ fn min_max_boundary() {
 |                  | stuff         |          |
 +------------------+---------------+----------+";
     println!("{expected}");
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 #[rstest::rstest]
@@ -353,5 +353,5 @@ fn empty_table(#[case] arrangement: ContentArrangement) {
 |   |
 +---+";
     println!("{expected}");
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
diff --git a/tests/all/content_arrangement_test.rs b/tests/all/content_arrangement_test.rs
index 08afed3..fc427a5 100644
--- a/tests/all/content_arrangement_test.rs
+++ b/tests/all/content_arrangement_test.rs
@@ -65,7 +65,7 @@ fn simple_dynamic_table() {
 +--------+-------+------+";
     println!("{expected}");
     assert_table_line_width(&table, 25);
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 /// Individual rows can be configured to have a max height.
@@ -121,7 +121,7 @@ fn table_with_truncate() {
 +----------------+--------+-------+";
     println!("{expected}");
     assert_table_line_width(&table, 35);
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 /// This table checks the scenario, where a column has a big max_width, but a lot of the assigned
@@ -152,7 +152,7 @@ fn distribute_space_after_split() {
     println!("{expected}");
 
     assert_table_line_width(&table, 80);
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 /// A single column get's split and a lot of the available isn't used afterward.
@@ -176,7 +176,7 @@ fn unused_space_after_split() {
 +---------------------+";
     println!("{expected}");
     assert_table_line_width(&table, 23);
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 /// The full width of a table should be used, even if the space isn't used.
@@ -199,7 +199,7 @@ fn dynamic_full_width_after_split() {
 +------------------------------------------------+";
     println!("{expected}");
     assert_table_line_width(&table, 50);
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 /// This table checks the scenario, where a column has a big max_width, but a lot of the assigned
@@ -223,7 +223,7 @@ fn dynamic_full_width() {
 +-----------------------------------+----------------------+-------------------+";
     println!("{expected}");
     assert_table_line_width(&table, 80);
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 /// Test that a table is displayed in its full width, if the `table.width` is set to the exact
@@ -263,7 +263,7 @@ fn dynamic_exact_width() {
 └─────┴─────┴───────────┘";
         println!("{expected}");
         assert_table_line_width(table, 25);
-        assert_eq!("\n".to_string() + &table.to_string(), expected);
+        assert_eq!(expected, "\n".to_string() + &table.to_string());
     }
 }
 
@@ -302,7 +302,7 @@ fn dynamic_slightly_smaller() {
 └─────┴─────┴──────────┘";
     println!("{expected}");
     assert_table_line_width(table, 24);
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 /// This failed on a python integration test case in the polars project.
@@ -348,5 +348,5 @@ fn polar_python_test_tbl_width_chars() {
 └───────────────────┴─────────────────────────────────────┴────────────┘";
     println!("{expected}");
     assert_table_line_width(table, 72);
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
diff --git a/tests/all/custom_delimiter_test.rs b/tests/all/custom_delimiter_test.rs
index 222a00e..a33693a 100644
--- a/tests/all/custom_delimiter_test.rs
+++ b/tests/all/custom_delimiter_test.rs
@@ -57,5 +57,5 @@ fn full_custom_delimiters() {
 |                   | g else.          |
 +-------------------+------------------+";
     println!("{expected}");
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
diff --git a/tests/all/hidden_test.rs b/tests/all/hidden_test.rs
index b097ff6..2689afe 100644
--- a/tests/all/hidden_test.rs
+++ b/tests/all/hidden_test.rs
@@ -70,7 +70,7 @@ fn hidden_columns() {
 │      ┆ add some             ┆                        │
 │      ┆ multi line stuff     ┆                        │
 └──────┴──────────────────────┴────────────────────────┘";
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 /// Make sure dynamic adjustment still works with hidden columns
@@ -99,7 +99,7 @@ fn hidden_columns_with_dynamic_adjustment() {
 │      ┆ line   ┆       │
 │      ┆ stuff  ┆       │
 └──────┴────────┴───────┘";
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 /// Nothing breaks, if all columns are hidden
@@ -121,5 +121,5 @@ fn only_hidden_columns() {
 ╞╡
 ├┤
 └┘";
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
diff --git a/tests/all/inner_style_test.rs b/tests/all/inner_style_test.rs
index 9b757c6..fa8018e 100644
--- a/tests/all/inner_style_test.rs
+++ b/tests/all/inner_style_test.rs
@@ -51,7 +51,7 @@ fn styled_table() {
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
 │ cell5                                   ┆ cell6                                   │
 └─────────────────────────────────────────┴─────────────────────────────────────────┘";
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 #[test]
@@ -73,7 +73,7 @@ fn no_style_styled_table() {
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
 │ cell5                                   ┆ cell6                                   │
 └─────────────────────────────────────────┴─────────────────────────────────────────┘";
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 #[test]
@@ -94,5 +94,5 @@ fn styled_text_only_table() {
 ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
 │ cell5                                   ┆ cell6                                   │
 └─────────────────────────────────────────┴─────────────────────────────────────────┘";
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
diff --git a/tests/all/modifiers_test.rs b/tests/all/modifiers_test.rs
index 43713e2..475a709 100644
--- a/tests/all/modifiers_test.rs
+++ b/tests/all/modifiers_test.rs
@@ -30,5 +30,5 @@ fn utf8_round_corners() {
 ╰─────────┴─────────┴───────────╯";
 
     println!("{table}");
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
diff --git a/tests/all/padding_test.rs b/tests/all/padding_test.rs
index 88bb164..23391b0 100644
--- a/tests/all/padding_test.rs
+++ b/tests/all/padding_test.rs
@@ -33,5 +33,5 @@ fn custom_padding() {
 |-------------------+-----------+-----------|
 |     Three One     | Three Two |Three Three|
 +-------------------+-----------+-----------+";
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
diff --git a/tests/all/presets_test.rs b/tests/all/presets_test.rs
index 803ac45..3a2d82a 100644
--- a/tests/all/presets_test.rs
+++ b/tests/all/presets_test.rs
@@ -27,7 +27,7 @@ fn test_ascii_full() {
 | c     | d     |
 +-------+-------+";
     println!("{expected}");
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 #[test]
@@ -43,7 +43,7 @@ fn test_ascii_full_condensed() {
 | c     | d     |
 +-------+-------+";
     println!("{expected}");
-    assert_eq!("\n".to_string() + &table.trim_fmt(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.trim_fmt());
 }
 
 #[test]
@@ -58,7 +58,7 @@ fn test_ascii_no_borders() {
 -------+-------
  c     | d";
     println!("{expected}");
-    assert_eq!("\n".to_string() + &table.trim_fmt(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.trim_fmt());
 }
 
 #[test]
@@ -75,7 +75,7 @@ fn test_ascii_borders_only() {
 | c       d     |
 +---------------+";
     println!("{expected}");
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 #[test]
@@ -91,7 +91,7 @@ fn test_ascii_borders_only_condensed() {
 | c       d     |
 +---------------+";
     println!("{expected}");
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 #[test]
@@ -108,7 +108,7 @@ fn test_ascii_horizontal_only() {
  c       d
 ---------------";
     println!("{expected}");
-    assert_eq!("\n".to_string() + &table.trim_fmt(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.trim_fmt());
 }
 
 #[test]
@@ -122,7 +122,7 @@ fn test_ascii_markdown() {
 | a     | b     |
 | c     | d     |";
     println!("{expected}");
-    assert_eq!("\n".to_string() + &table.trim_fmt(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.trim_fmt());
 }
 
 #[test]
@@ -139,7 +139,7 @@ fn test_utf8_full() {
 │ c     ┆ d     │
 └───────┴───────┘";
     println!("{expected}");
-    assert_eq!("\n".to_string() + &table.trim_fmt(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.trim_fmt());
 }
 
 #[test]
@@ -155,7 +155,7 @@ fn test_utf8_full_condensed() {
 │ c     ┆ d     │
 └───────┴───────┘";
     println!("{expected}");
-    assert_eq!("\n".to_string() + &table.trim_fmt(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.trim_fmt());
 }
 
 #[test]
@@ -170,7 +170,7 @@ fn test_utf8_no_borders() {
 ╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌
  c     ┆ d";
     println!("{expected}");
-    assert_eq!("\n".to_string() + &table.trim_fmt(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.trim_fmt());
 }
 
 #[test]
@@ -187,7 +187,7 @@ fn test_utf8_horizontal_only() {
  c       d
 ───────────────";
     println!("{expected}");
-    assert_eq!("\n".to_string() + &table.trim_fmt(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.trim_fmt());
 }
 
 #[test]
@@ -200,5 +200,5 @@ fn test_nothing() {
  a      b
  c      d";
     println!("{expected}");
-    assert_eq!("\n".to_string() + &table.trim_fmt(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.trim_fmt());
 }
diff --git a/tests/all/property_test.proptest-regressions b/tests/all/property_test.proptest-regressions
index e5097f3..a85a805 100644
--- a/tests/all/property_test.proptest-regressions
+++ b/tests/all/property_test.proptest-regressions
@@ -13,3 +13,4 @@ cc d5305ce51fb8e961cc04af56393095c991b9ad218ac62b4f85e6abf218b8b2ad # shrinks to
 cc 0d4d3573fbe99ac650d858c7a306f8c2ed7cf5fb5998f2c30495bb37849c2c5b # shrinks to mut table = Table { columns: [Column { index: 0, padding: (1, 1), delimiter: None, cell_alignment: None, constraint: Some(Absolute(Percentage(0))) }], style: {MiddleHeaderIntersections: '=', HorizontalLines: '-', BottomBorderIntersections: '+', BottomRightCorner: '+', RightBorderIntersections: '|', RightBorder: '|', BottomBorder: '-', MiddleIntersections: '+', TopRightCorner: '+', LeftHeaderIntersection: '+', TopLeftCorner: '+', BottomLeftCorner: '+', VerticalLines: '|', TopBorderIntersections: '+', LeftBorder: '|', TopBorder: '-', HeaderLines: '=', LeftBorderIntersections: '|', RightHeaderIntersection: '+'}, header: None, rows: [Row { index: Some(0), cells: [Cell { content: [""], delimiter: None, alignment: None, fg: None, bg: None, attributes: [] }], max_height: None }], arrangement: DynamicFullWidth, delimiter: None, no_tty: false, use_stderr: false, width: None, enforce_styling: false, style_text_only: false }, table_width = 0
 cc 5e2562ec5b8d70abf05ea49ba65a178f086f1f3ed9251506f7b3e6bbb9091015 # shrinks to mut table = Table { columns: [Column { index: 0, padding: (1, 1), delimiter: None, cell_alignment: None, constraint: None }, Column { index: 1, padding: (1, 1), delimiter: None, cell_alignment: None, constraint: None }, Column { index: 2, padding: (1, 1), delimiter: None, cell_alignment: None, constraint: Some(UpperBoundary(Percentage(0))) }], style: {BottomBorderIntersections: '+', MiddleIntersections: '+', HorizontalLines: '-', RightHeaderIntersection: '+', VerticalLines: '|', TopLeftCorner: '+', BottomLeftCorner: '+', LeftBorder: '|', HeaderLines: '=', RightBorderIntersections: '|', TopBorder: '-', MiddleHeaderIntersections: '=', TopRightCorner: '+', RightBorder: '|', LeftBorderIntersections: '|', BottomRightCorner: '+', BottomBorder: '-', TopBorderIntersections: '+', LeftHeaderIntersection: '+'}, header: None, rows: [Row { index: Some(0), cells: [], max_height: None }, Row { index: Some(1), cells: [], max_height: None }, Row { index: Some(2), cells: [], max_height: None }, Row { index: Some(3), cells: [], max_height: None }, Row { index: Some(4), cells: [], max_height: None }, Row { index: Some(5), cells: [], max_height: None }, Row { index: Some(6), cells: [], max_height: None }, Row { index: Some(7), cells: [Cell { content: [""], delimiter: None, alignment: None, fg: None, bg: None, attributes: [] }, Cell { content: [""], delimiter: None, alignment: None, fg: None, bg: None, attributes: [] }, Cell { content: [""], delimiter: None, alignment: None, fg: None, bg: None, attributes: [] }], max_height: None }], arrangement: Dynamic, delimiter: None, no_tty: false, use_stderr: false, width: None, enforce_styling: false, style_text_only: false }, table_width = 351
 cc ac6d94f68bcd585b1ffff6b3b3e10b625d79ad96ec317ad147601c68bee5aa99 # shrinks to mut table = Table { columns: [Column { index: 0, padding: (1, 1), delimiter: None, cell_alignment: None, constraint: Some(UpperBoundary(Percentage(127))) }, Column { index: 1, padding: (1, 1), delimiter: None, cell_alignment: Some(Left), constraint: Some(LowerBoundary(Percentage(23))) }, Column { index: 2, padding: (1, 1), delimiter: None, cell_alignment: Some(Left), constraint: Some(ContentWidth) }], style: {TopRightCorner: '+', RightHeaderIntersection: '+', LeftBorderIntersections: '|', RightBorderIntersections: '|', BottomRightCorner: '+', RightBorder: '|', MiddleHeaderIntersections: '=', TopLeftCorner: '+', TopBorderIntersections: '+', MiddleIntersections: '+', BottomBorderIntersections: '+', LeftHeaderIntersection: '+', BottomLeftCorner: '+', HorizontalLines: '-', TopBorder: '-', BottomBorder: '-', LeftBorder: '|', VerticalLines: '|', HeaderLines: '='}, header: None, rows: [Row { index: Some(0), cells: [], max_height: None }, Row { index: Some(1), cells: [], max_height: None }, Row { index: Some(2), cells: [Cell { content: [""], delimiter: None, alignment: None, fg: None, bg: None, attributes: [] }, Cell { content: ["rz_m___Dksx_c__KdvK__fh"], delimiter: None, alignment: Some(Right), fg: None, bg: None, attributes: [] }, Cell { content: ["i_swmNSsyuOtR_UfklUqR_"], delimiter: None, alignment: Some(Center), fg: None, bg: None, attributes: [] }], max_height: None }, Row { index: Some(3), cells: [Cell { content: ["xJ_iEc_IGix____hf_GPlKlnqKZr_"], delimiter: None, alignment: Some(Left), fg: None, bg: None, attributes: [] }], max_height: None }], arrangement: Dynamic, delimiter: None, no_tty: false, use_stderr: false, width: None, enforce_styling: false, style_text_only: false }, table_width = 77
+cc 3a4d50f1e0f97ce25469b6d06fcd1f9b8dcfb4571a6c6c32b3fac1c88ab05606 # shrinks to mut table = Table { columns: [Column { index: 0, padding: (1, 1), delimiter: None, cell_alignment: None, constraint: Some(UpperBoundary(Fixed(23))) }, Column { index: 1, padding: (1, 1), delimiter: None, cell_alignment: None, constraint: None }], style: {HorizontalLines: '-', BottomLeftCorner: '+', RightHeaderIntersection: '+', TopBorder: '-', RightBorder: '|', BottomBorder: '-', MiddleIntersections: '+', HeaderLines: '=', TopLeftCorner: '+', VerticalLines: '|', TopRightCorner: '+', RightBorderIntersections: '|', LeftHeaderIntersection: '+', BottomBorderIntersections: '+', LeftBorder: '|', LeftBorderIntersections: '|', TopBorderIntersections: '+', BottomRightCorner: '+', MiddleHeaderIntersections: '='}, header: None, rows: [Row { index: Some(0), cells: [Cell { content: ["aAAAa_A_aAAaAAaaaaaa_"], delimiter: None, alignment: None, fg: None, bg: None, attributes: [] }, Cell { content: [""], delimiter: None, alignment: None, fg: None, bg: None, attributes: [] }], max_height: None }], arrangement: Dynamic, delimiter: None, no_tty: false, use_stderr: false, width: None, enforce_styling: false, style_text_only: false }, table_width = 9
diff --git a/tests/all/property_test.rs b/tests/all/property_test.rs
index 95df0dc..b38b20f 100644
--- a/tests/all/property_test.rs
+++ b/tests/all/property_test.rs
@@ -78,7 +78,7 @@ fn columns_and_rows() -> impl Strategy<
                 cell_alignments.push(cell_alignment());
             }
             // Add a strategy that creates random cell content with a length of 0 to column_count
-            // TODO: Move this back to ".*" once we properly handle multi-space UTF-8 chars.
+            //
             // UTF-8 characters completely break table alignment in edge-case situations (e.g. 1 space columns).
             // UTF-8 characters can be multiple characters wide, which conflicts with the 1 space
             // column fallback, as well as fixed-width-, percental- and max-column-constraints.
@@ -158,11 +158,12 @@ prop_compose! {
 proptest! {
     #![proptest_config({
         let mut config = ProptestConfig::with_cases(512);
-        config.max_shrink_iters = 2000;
+        config.max_shrink_iters = 5000;
         config
     })]
     #[test]
     fn random_tables(mut table in table(), table_width in table_width()) {
+        println!("\n\n\n------ START TEST ------\n\n\n");
         table.set_width(table_width);
 
         // Make sure the table builds without any panics
@@ -177,37 +178,42 @@ proptest! {
         // Get the length of the very first line.
         // We're lateron going to ensure, that all lines have the same length.
         let line_length = if let Some(line) = line_iter.next() {
-            line.len()
+            line.trim().len()
         } else {
             0
         };
 
+        // Make sure all lines have the same length
         for line in line_iter {
-            // Make sure all lines have the same length
             if line.len() != line_length {
                 return build_error(&formatted, "Each line of a printed table has to have the same length!");
             }
         }
 
-        // TODO: This is a bit tricky.
-        //       A table can be larger than the specified width, if the user forces it to be
-        //       larger.
         // Make sure that the table is within its width, if arrangement isn't enabled.
-        //match content_arrangement{
-        //    ContentArrangement::Disabled => (),
-        //    _ => {
-        //        let expected_max = table.width().unwrap();
-        //        let actual = line_length;
-        //        if actual > expected_max.into() {
-        //            return build_error(
-        //                &formatted,
-        //                &format!("Expected table to be smaller than line length!\n\
-        //                Actual: {actual}, Expected max: {expected_max}\n\
-        //                Arrangement: {content_arrangement:?}"
-        //            ));
-        //        }
-        //    }
-        //}
+        // This is a bit tricky.
+        // A table can be larger than the specified width, if the user forces it to be larger.
+        #[cfg(feature = "integration_test")]
+        {
+            let current_arrangement = table.content_arrangement();
+            match current_arrangement {
+                ContentArrangement::Disabled => (),
+                _ => {
+                    let expected_max = determine_max_table_width(&table);
+
+                    // A line can be a bit longer than u16::MAX due to formatting and borders.
+                    let actual: u16 = line_length.try_into().unwrap_or(u16::MAX);
+                    if actual > expected_max.into() {
+                        return build_error(
+                            &formatted,
+                            &format!("Expected table to be smaller than line length!\n\
+                            Actual: {actual}, Expected max: {expected_max}\n\
+                            Arrangement: {current_arrangement:?}"
+                        ));
+                    }
+                }
+            }
+        }
 
         #[cfg(feature = "integration_test")]
         // Only run this test, if the `integration_test` is enabled.
@@ -223,6 +229,70 @@ fn build_error(table: &str, context: &str) -> Result<(), TestCaseError> {
     ))
 }
 
+/// The user can actually force a table to be longer than the specified `table.width()`
+/// by specifying [ColumnConstraint]s.
+#[cfg(feature = "integration_test")]
+fn determine_max_table_width(table: &Table) -> u16 {
+    use comfy_table::utils::arrangement::helper::count_border_columns;
+    let table_width = table.width().unwrap();
+
+    // The max value that will be enforced by constraints.
+    // We start with `2` for the side borders.
+    let visible_columns = table
+        .column_iter()
+        .filter(|column| !column.is_hidden())
+        .count();
+
+    // Initialize the value for the min width enforced by constraints.
+    // Borders may exist, but they are not included in constraints, which is why we have to
+    // explicitly add them.
+    let mut constraint_min_width: u16 = count_border_columns(table, visible_columns)
+        .try_into()
+        .unwrap_or(u16::MAX);
+
+    // Get the max content widths for each column.
+    // This is necessary for the `ContentWidth` constraint.
+    let max_content_widths = table.column_max_content_widths();
+
+    // Calculate the enforced widths by any constraints.
+    for (index, column) in table.column_iter().enumerate() {
+        if let Some(constraint) = column.constraint() {
+            match constraint {
+                ColumnConstraint::ContentWidth => {
+                    constraint_min_width = constraint_min_width
+                        .saturating_add(max_content_widths[index])
+                        .saturating_add(column.padding_width());
+                }
+                ColumnConstraint::Absolute(width) => {
+                    constraint_min_width = constraint_min_width
+                        .saturating_add(absolute_width(table, width))
+                        .saturating_add(column.padding_width());
+                }
+                ColumnConstraint::LowerBoundary(width)
+                | ColumnConstraint::Boundaries { lower: width, .. } => {
+                    constraint_min_width = constraint_min_width
+                        .saturating_add(absolute_width(table, width))
+                        .saturating_add(column.padding_width());
+                }
+                ColumnConstraint::Hidden => {}
+                _ => {
+                    // Add the padding and the min-width of `1` for this column
+                    constraint_min_width = constraint_min_width
+                        .saturating_add(column.padding_width())
+                        .saturating_add(1);
+                }
+            }
+        } else {
+            // Add the padding + 1 space for all columns without constraints.
+            constraint_min_width = constraint_min_width
+                .saturating_add(column.padding_width())
+                .saturating_add(1);
+        }
+    }
+
+    std::cmp::max(table_width, constraint_min_width)
+}
+
 /// Enforce that Column constraints are enforced as expected in `Dynamic` mode.
 #[cfg(feature = "integration_test")]
 fn enforce_constraints(
@@ -377,15 +447,15 @@ fn enforce_constraints(
 /// Resolve an absolute value from a given boundary
 #[cfg(feature = "integration_test")]
 pub fn absolute_width(table: &Table, width: &Width) -> u16 {
+    use comfy_table::utils::arrangement::constraint::absolute_value_from_width;
+
     let visible_columns = table
         .column_iter()
         .filter(|column| !column.is_hidden())
         .count();
 
-    comfy_table::utils::arrangement::constraint::absolute_value_from_width(
-        table,
-        width,
-        visible_columns,
-    )
-    .expect("Expected table to have a width")
+    let computed_width = absolute_value_from_width(table, width, visible_columns)
+        .expect("Expected table to have a width");
+
+    std::cmp::max(1, computed_width)
 }
diff --git a/tests/all/simple_test.rs b/tests/all/simple_test.rs
index 5b139bc..282b658 100644
--- a/tests/all/simple_test.rs
+++ b/tests/all/simple_test.rs
@@ -29,7 +29,7 @@ fn simple_table() {
 |                      | add some             |                        |
 |                      | multi line stuff     |                        |
 +----------------------+----------------------+------------------------+";
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 #[test]
@@ -52,7 +52,7 @@ fn missing_column_table() {
 |-----------+---------+-----------|
 | Three One |         |           |
 +-----------+---------+-----------+";
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 #[test]
@@ -75,7 +75,7 @@ fn single_column_table() {
 |-----------|
 | Three One |
 +-----------+";
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 #[test]
diff --git a/tests/all/styling_test.rs b/tests/all/styling_test.rs
index 032b897..d247620 100644
--- a/tests/all/styling_test.rs
+++ b/tests/all/styling_test.rs
@@ -47,7 +47,7 @@ fn styled_table() {
 │                     ┆\u{1b}[38;5;14m\u{1b}[4m add some             \u{1b}[0m┆                               │
 │                     ┆\u{1b}[38;5;14m\u{1b}[4m multi line stuff     \u{1b}[0m┆                               │
 └─────────────────────┴──────────────────────┴───────────────────────────────┘";
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 #[test]
@@ -66,7 +66,7 @@ fn no_style_styled_table() {
 │                     ┆ add some             ┆                               │
 │                     ┆ multi line stuff     ┆                               │
 └─────────────────────┴──────────────────────┴───────────────────────────────┘";
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 #[test]
@@ -85,5 +85,5 @@ fn styled_text_only_table() {
 │                     ┆ \u{1b}[38;5;14m\u{1b}[4mmulti line stuff\u{1b}[0m     ┆                               │
 └─────────────────────┴──────────────────────┴───────────────────────────────┘";
 
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
diff --git a/tests/all/utf_8_characters.rs b/tests/all/utf_8_characters.rs
index 38ce09d..c469587 100644
--- a/tests/all/utf_8_characters.rs
+++ b/tests/all/utf_8_characters.rs
@@ -32,7 +32,7 @@ fn multi_character_utf8_symbols() {
 |                      | add some             |                        |
 |                      | multi line stuff     |                        |
 +----------------------+----------------------+------------------------+";
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }
 
 #[test]
@@ -55,5 +55,5 @@ fn multi_character_utf8_word_splitting() {
 | f    |
 +------+";
     println!("{expected}");
-    assert_eq!("\n".to_string() + &table.to_string(), expected);
+    assert_eq!(expected, "\n".to_string() + &table.to_string());
 }