| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers.h" |
| |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/renderer/core/layout/ng/table/ng_table_node.h" |
| #include "third_party/blink/renderer/core/testing/core_unit_test_helper.h" |
| |
| namespace blink { |
| |
| class NGTableAlgorithmHelpersTest : public RenderingTest { |
| public: |
| NGTableTypes::Column MakeColumn(int min_width, |
| int max_width, |
| base::Optional<float> percent = base::nullopt, |
| bool is_constrained = false) { |
| return NGTableTypes::Column{LayoutUnit(min_width), |
| LayoutUnit(max_width), |
| percent, |
| /* border_padding */ LayoutUnit(), |
| is_constrained, |
| /* is_collapsed */ false, |
| /* is_table_fixed */ false, |
| /* is_mergeable */ false}; |
| } |
| |
| NGTableTypes::Row MakeRow(int block_size, |
| bool is_constrained = false, |
| bool has_rowspan_start = false, |
| base::Optional<float> percent = base::nullopt) { |
| return NGTableTypes::Row{ |
| LayoutUnit(block_size), LayoutUnit(), percent, 0, 0, |
| is_constrained, false, has_rowspan_start, false}; |
| } |
| |
| NGTableTypes::Section MakeSection( |
| NGTableTypes::Rows* rows, |
| int block_size, |
| wtf_size_t rowspan = 1, |
| base::Optional<float> percent = base::nullopt) { |
| wtf_size_t start_row = rows->size(); |
| for (wtf_size_t i = 0; i < rowspan; i++) |
| rows->push_back(MakeRow(10)); |
| bool is_constrained = percent || block_size != 0; |
| bool is_tbody = true; |
| return NGTableTypes::Section{ |
| start_row, rowspan, LayoutUnit(block_size), percent, is_constrained, |
| is_tbody, false}; |
| } |
| }; |
| |
| TEST_F(NGTableAlgorithmHelpersTest, DistributeColspanAutoPercent) { |
| NGTableTypes::ColspanCell colspan_cell(NGTableTypes::CellInlineConstraint(), |
| 0, 3); |
| colspan_cell.start_column = 0; |
| colspan_cell.span = 3; |
| |
| scoped_refptr<NGTableTypes::Columns> column_constraints = |
| base::MakeRefCounted<NGTableTypes::Columns>(); |
| |
| colspan_cell.cell_inline_constraint.percent = 60.0f; |
| NGTableTypes::ColspanCells colspan_cells; |
| colspan_cells.push_back(colspan_cell); |
| |
| // Distribute over non-percent columns proportial to max size. |
| // Columns: 10px, 20px, 30% |
| // Distribute 60%: 10% 20% 30% |
| column_constraints->data.Shrink(0); |
| column_constraints->data.push_back(MakeColumn(0, 10)); |
| column_constraints->data.push_back(MakeColumn(0, 20)); |
| column_constraints->data.push_back(MakeColumn(0, 10, 30)); |
| NGTableAlgorithmHelpers::DistributeColspanCellsToColumns( |
| colspan_cells, LayoutUnit(), false, column_constraints.get()); |
| EXPECT_EQ(column_constraints->data[0].percent, 10); |
| EXPECT_EQ(column_constraints->data[1].percent, 20); |
| |
| // Distribute evenly over empty columns. |
| // Columns: 0px 0px 10% |
| // Distribute 60%: 25% 25% 10% |
| column_constraints->data.Shrink(0); |
| column_constraints->data.push_back(MakeColumn(0, 0)); |
| column_constraints->data.push_back(MakeColumn(0, 0)); |
| column_constraints->data.push_back(MakeColumn(0, 10, 10)); |
| NGTableAlgorithmHelpers::DistributeColspanCellsToColumns( |
| colspan_cells, LayoutUnit(), false, column_constraints.get()); |
| EXPECT_EQ(column_constraints->data[0].percent, 25); |
| EXPECT_EQ(column_constraints->data[1].percent, 25); |
| } |
| |
| TEST_F(NGTableAlgorithmHelpersTest, DistributeColspanAutoSizeUnconstrained) { |
| NGTableTypes::ColspanCell colspan_cell(NGTableTypes::CellInlineConstraint(), |
| 0, 3); |
| colspan_cell.start_column = 0; |
| colspan_cell.span = 3; |
| |
| scoped_refptr<NGTableTypes::Columns> column_constraints = |
| base::MakeRefCounted<NGTableTypes::Columns>(); |
| |
| // Columns distributing over auto columns. |
| colspan_cell.cell_inline_constraint.min_inline_size = LayoutUnit(100); |
| colspan_cell.cell_inline_constraint.max_inline_size = LayoutUnit(100); |
| NGTableTypes::ColspanCells colspan_cells; |
| colspan_cells.push_back(colspan_cell); |
| // Distribute over non-percent columns proportial to max size. |
| // Columns min/max: 0/10, 0/10, 0/20 |
| // Distribute 25, 25, 50 |
| column_constraints->data.Shrink(0); |
| column_constraints->data.push_back(MakeColumn(0, 10)); |
| column_constraints->data.push_back(MakeColumn(0, 10)); |
| column_constraints->data.push_back(MakeColumn(0, 20)); |
| NGTableAlgorithmHelpers::DistributeColspanCellsToColumns( |
| colspan_cells, LayoutUnit(), false, column_constraints.get()); |
| EXPECT_EQ(column_constraints->data[0].min_inline_size, 25); |
| EXPECT_EQ(column_constraints->data[1].min_inline_size, 25); |
| EXPECT_EQ(column_constraints->data[2].min_inline_size, 50); |
| } |
| |
| TEST_F(NGTableAlgorithmHelpersTest, DistributeColspanAutoSizeConstrained) { |
| NGTableTypes::ColspanCell colspan_cell(NGTableTypes::CellInlineConstraint(), |
| 0, 3); |
| colspan_cell.start_column = 0; |
| colspan_cell.span = 3; |
| |
| scoped_refptr<NGTableTypes::Columns> column_constraints = |
| base::MakeRefCounted<NGTableTypes::Columns>(); |
| |
| // Columns distributing over auto columns. |
| colspan_cell.cell_inline_constraint.min_inline_size = LayoutUnit(100); |
| colspan_cell.cell_inline_constraint.max_inline_size = LayoutUnit(100); |
| NGTableTypes::ColspanCells colspan_cells; |
| colspan_cells.push_back(colspan_cell); |
| // Distribute over fixed columns proportial to: |
| // Columns min/max: 0/10, 0/10, 0/20 |
| // Distribute 25, 25, 50 |
| column_constraints->data.Shrink(0); |
| column_constraints->data.push_back(MakeColumn(0, 10, base::nullopt, true)); |
| column_constraints->data.push_back(MakeColumn(10, 10, base::nullopt, true)); |
| column_constraints->data.push_back(MakeColumn(0, 20, base::nullopt, true)); |
| NGTableAlgorithmHelpers::DistributeColspanCellsToColumns( |
| colspan_cells, LayoutUnit(), false, column_constraints.get()); |
| EXPECT_EQ(column_constraints->data[0].min_inline_size, 25); |
| EXPECT_EQ(column_constraints->data[1].min_inline_size, 25); |
| EXPECT_EQ(column_constraints->data[2].min_inline_size, 50); |
| } |
| |
| TEST_F(NGTableAlgorithmHelpersTest, DistributeColspanAutoExactMaxSize) { |
| // If column widths sum match table widths exactly, column widths |
| // should not be redistributed at all. |
| // The error occurs if widths are redistributed, and column widths |
| // change due to floating point rounding. |
| LayoutUnit column_widths[] = {LayoutUnit(0.1), LayoutUnit(22.123456), |
| LayoutUnit(33.789012), LayoutUnit(2000.345678)}; |
| scoped_refptr<NGTableTypes::Columns> column_constraints = |
| base::MakeRefCounted<NGTableTypes::Columns>(); |
| column_constraints->data.Shrink(0); |
| column_constraints->data.push_back( |
| NGTableTypes::Column{LayoutUnit(0), column_widths[0], base::nullopt, |
| LayoutUnit(), false, false, false, false}); |
| column_constraints->data.push_back( |
| NGTableTypes::Column{LayoutUnit(3.33333), column_widths[1], base::nullopt, |
| LayoutUnit(), false, false, false, false}); |
| column_constraints->data.push_back( |
| NGTableTypes::Column{LayoutUnit(3.33333), column_widths[2], base::nullopt, |
| LayoutUnit(), false, false, false, false}); |
| column_constraints->data.push_back( |
| NGTableTypes::Column{LayoutUnit(0), column_widths[3], base::nullopt, |
| LayoutUnit(), false, false, false, false}); |
| |
| LayoutUnit assignable_table_inline_size = |
| column_widths[0] + column_widths[1] + column_widths[2] + column_widths[3]; |
| Vector<LayoutUnit> column_sizes = |
| NGTableAlgorithmHelpers::SynchronizeAssignableTableInlineSizeAndColumns( |
| assignable_table_inline_size, LayoutUnit(), false, |
| *column_constraints); |
| EXPECT_EQ(column_sizes[0], column_widths[0]); |
| EXPECT_EQ(column_sizes[1], column_widths[1]); |
| EXPECT_EQ(column_sizes[2], column_widths[2]); |
| EXPECT_EQ(column_sizes[3], column_widths[3]); |
| } |
| |
| TEST_F(NGTableAlgorithmHelpersTest, ComputeGridInlineMinMax) { |
| SetBodyInnerHTML(R"HTML( |
| <div style="display: flex;"> |
| <table id=target></table> |
| <div> |
| )HTML"); |
| NGTableNode node(To<LayoutBox>(GetLayoutObjectByElementId("target"))); |
| |
| scoped_refptr<NGTableTypes::Columns> column_constraints = |
| base::MakeRefCounted<NGTableTypes::Columns>(); |
| |
| LayoutUnit undistributable_space; |
| bool is_fixed_layout = false; |
| bool is_layout_pass = true; |
| bool skip_collapsed_columns = false; |
| |
| // No percentages, just sums up min/max. |
| column_constraints->data.push_back(MakeColumn(10, 100)); |
| column_constraints->data.push_back(MakeColumn(20, 200)); |
| column_constraints->data.push_back(MakeColumn(30, 300)); |
| |
| MinMaxSizes minmax = NGTableAlgorithmHelpers::ComputeGridInlineMinMax( |
| node, *column_constraints, undistributable_space, is_fixed_layout, |
| is_layout_pass, skip_collapsed_columns); |
| EXPECT_EQ(minmax.min_size, LayoutUnit(60)); |
| EXPECT_EQ(minmax.max_size, LayoutUnit(600)); |
| |
| // Percentage: 99px max size/10% cell => |
| // table max size of 100%/10% * 99px |
| column_constraints->data.Shrink(0); |
| column_constraints->data.push_back(MakeColumn(10, 99, 10)); |
| column_constraints->data.push_back(MakeColumn(10, 10)); |
| column_constraints->data.push_back(MakeColumn(10, 10)); |
| minmax = NGTableAlgorithmHelpers::ComputeGridInlineMinMax( |
| node, *column_constraints, undistributable_space, is_fixed_layout, |
| is_layout_pass, skip_collapsed_columns); |
| EXPECT_EQ(minmax.min_size, LayoutUnit(30)); |
| EXPECT_EQ(minmax.max_size, LayoutUnit(990)); |
| |
| is_layout_pass = false; |
| minmax = NGTableAlgorithmHelpers::ComputeGridInlineMinMax( |
| node, *column_constraints, undistributable_space, is_fixed_layout, |
| is_layout_pass, skip_collapsed_columns); |
| EXPECT_EQ(minmax.min_size, LayoutUnit(30)); |
| EXPECT_EQ(minmax.max_size, LayoutUnit(119)); |
| |
| // Percentage: total percentage of 20%, and non-percent width of 800 => |
| // table max size of 800 + (20% * 800/80%) = 1000 |
| is_layout_pass = true; |
| column_constraints->data.Shrink(0); |
| column_constraints->data.push_back(MakeColumn(10, 100, 10)); |
| column_constraints->data.push_back(MakeColumn(10, 10, 10)); |
| column_constraints->data.push_back(MakeColumn(10, 800)); |
| minmax = NGTableAlgorithmHelpers::ComputeGridInlineMinMax( |
| node, *column_constraints, undistributable_space, is_fixed_layout, |
| is_layout_pass, skip_collapsed_columns); |
| EXPECT_EQ(minmax.min_size, LayoutUnit(30)); |
| EXPECT_EQ(minmax.max_size, LayoutUnit(1000)); |
| } |
| |
| TEST_F(NGTableAlgorithmHelpersTest, DistributeRowspanCellToRows) { |
| NGTableTypes::CellBlockConstraint cell_block_constraint{ |
| LayoutUnit(300), LayoutUnit(), NGBoxStrut(), 0, 0, 3, |
| EVerticalAlign::kTop, true}; |
| NGTableTypes::RowspanCell rowspan_cell = NGTableTypes::CreateRowspanCell( |
| 0, 3, &cell_block_constraint, base::nullopt); |
| NGTableTypes::Rows rows; |
| |
| // Distribute to regular rows, rows grow in proportion to size. |
| rows.push_back(MakeRow(10)); |
| rows.push_back(MakeRow(20)); |
| rows.push_back(MakeRow(30)); |
| NGTableAlgorithmHelpers::DistributeRowspanCellToRows(rowspan_cell, |
| LayoutUnit(), &rows); |
| EXPECT_EQ(rows[0].block_size, LayoutUnit(50)); |
| EXPECT_EQ(rows[1].block_size, LayoutUnit(100)); |
| EXPECT_EQ(rows[2].block_size, LayoutUnit(150)); |
| |
| // If some rows are empty, non-empty row gets everything |
| rows.Shrink(0); |
| rows.push_back(MakeRow(0)); |
| rows.push_back(MakeRow(10)); |
| rows.push_back(MakeRow(0)); |
| NGTableAlgorithmHelpers::DistributeRowspanCellToRows(rowspan_cell, |
| LayoutUnit(), &rows); |
| EXPECT_EQ(rows[0].block_size, LayoutUnit(0)); |
| EXPECT_EQ(rows[1].block_size, LayoutUnit(300)); |
| EXPECT_EQ(rows[2].block_size, LayoutUnit(0)); |
| |
| // If all rows are empty,last row gets everything. |
| rows.Shrink(0); |
| rows.push_back(MakeRow(0)); |
| rows.push_back(MakeRow(0)); |
| rows.push_back(MakeRow(0)); |
| NGTableAlgorithmHelpers::DistributeRowspanCellToRows(rowspan_cell, |
| LayoutUnit(), &rows); |
| EXPECT_EQ(rows[0].block_size, LayoutUnit(0)); |
| EXPECT_EQ(rows[1].block_size, LayoutUnit(0)); |
| EXPECT_EQ(rows[2].block_size, LayoutUnit(300)); |
| } |
| |
| TEST_F(NGTableAlgorithmHelpersTest, DistributeSectionFixedBlockSizeToRows) { |
| NGTableTypes::Rows rows; |
| |
| // Percentage rows get percentage, rest is distributed evenly. |
| rows.push_back(MakeRow(100)); |
| rows.push_back(MakeRow(100, true, false, 50)); |
| rows.push_back(MakeRow(100)); |
| NGTableAlgorithmHelpers::DistributeSectionFixedBlockSizeToRows( |
| 0, 3, LayoutUnit(1000), LayoutUnit(), LayoutUnit(1000), &rows); |
| EXPECT_EQ(rows[0].block_size, LayoutUnit(250)); |
| EXPECT_EQ(rows[1].block_size, LayoutUnit(500)); |
| EXPECT_EQ(rows[2].block_size, LayoutUnit(250)); |
| } |
| |
| TEST_F(NGTableAlgorithmHelpersTest, DistributeTableBlockSizeToSections) { |
| NGTableTypes::Sections sections; |
| NGTableTypes::Rows rows; |
| |
| // Empty sections only grow if there are no other growable sections. |
| sections.push_back(MakeSection(&rows, 0)); |
| sections.push_back(MakeSection(&rows, 100)); |
| NGTableAlgorithmHelpers::DistributeTableBlockSizeToSections( |
| LayoutUnit(), LayoutUnit(500), §ions, &rows); |
| EXPECT_EQ(sections[0].block_size, LayoutUnit(0)); |
| |
| // Sections with % block size grow to percentage. |
| sections.Shrink(0); |
| rows.Shrink(0); |
| sections.push_back(MakeSection(&rows, 100, 1, 90)); |
| sections.push_back(MakeSection(&rows, 100)); |
| NGTableAlgorithmHelpers::DistributeTableBlockSizeToSections( |
| LayoutUnit(), LayoutUnit(1000), §ions, &rows); |
| EXPECT_EQ(sections[0].block_size, LayoutUnit(900)); |
| EXPECT_EQ(sections[1].block_size, LayoutUnit(100)); |
| |
| // When table height is greater than sum of intrinsic heights, |
| // intrinsic heights are computed, and then they grow in |
| // proportion to intrinsic height. |
| sections.Shrink(0); |
| rows.Shrink(0); |
| sections.push_back(MakeSection(&rows, 100, 1, 30)); |
| sections.push_back(MakeSection(&rows, 100)); |
| // 30% section grows to 300px. |
| // Extra 600 px is distributed between 300 and 100 px proportionally. |
| // TODO(atotic) Is this what we want? FF/Edge/Legacy all disagree. |
| NGTableAlgorithmHelpers::DistributeTableBlockSizeToSections( |
| LayoutUnit(), LayoutUnit(1000), §ions, &rows); |
| EXPECT_EQ(sections[0].block_size, LayoutUnit(750)); |
| EXPECT_EQ(sections[1].block_size, LayoutUnit(250)); |
| |
| // If there is a constrained section, and an unconstrained section, |
| // unconstrained section grows. |
| sections.Shrink(0); |
| rows.Shrink(0); |
| NGTableTypes::Section section(MakeSection(&rows, 100)); |
| section.is_constrained = false; |
| sections.push_back(section); |
| sections.push_back(MakeSection(&rows, 100)); |
| NGTableAlgorithmHelpers::DistributeTableBlockSizeToSections( |
| LayoutUnit(), LayoutUnit(1000), §ions, &rows); |
| EXPECT_EQ(sections[0].block_size, LayoutUnit(900)); |
| EXPECT_EQ(sections[1].block_size, LayoutUnit(100)); |
| } |
| |
| } // namespace blink |