From e74b68b2985ec1df933c7a1950622c275c259787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Sim=C3=A3o?= Date: Tue, 9 May 2023 13:51:11 -0300 Subject: [PATCH 1/2] feat(block): add support to tables --- src/Blocks/BlockType.php | 2 + src/Blocks/Table.php | 191 +++++++++++++++++++++++++++++++++++++++ src/Blocks/TableRow.php | 87 ++++++++++++++++++ 3 files changed, 280 insertions(+) create mode 100644 src/Blocks/Table.php create mode 100644 src/Blocks/TableRow.php diff --git a/src/Blocks/BlockType.php b/src/Blocks/BlockType.php index 064c4762..f279fa39 100644 --- a/src/Blocks/BlockType.php +++ b/src/Blocks/BlockType.php @@ -25,6 +25,8 @@ enum BlockType: string case Bookmark = "bookmark"; case Equation = "equation"; case Divider = "divider"; + case Table = "table"; + case TableRow = "table_row"; case TableOfContents = "table_of_contents"; case Breadcrumb = "breadcrumb"; case Column = "column"; diff --git a/src/Blocks/Table.php b/src/Blocks/Table.php new file mode 100644 index 00000000..5f2358e6 --- /dev/null +++ b/src/Blocks/Table.php @@ -0,0 +1,191 @@ +, + * }, + * } + * + * @psalm-immutable + */ +class Table implements BlockInterface +{ + /** @param TableRow[] $rows */ + private function __construct( + private readonly BlockMetadata $metadata, + public readonly int $tableWidth, + public readonly bool $hasColumnHeader, + public readonly bool $hasRowHeader, + public readonly array $rows, + ) { + $metadata->checkType(BlockType::Table); + } + + public static function create(): self + { + $block = BlockMetadata::create(BlockType::Table); + + return new self($block, 1, false, false, []); + } + + public static function fromArray(array $array): self + { + /** @psalm-var BlockMetadataJson $array */ + $block = BlockMetadata::fromArray($array); + + /** @psalm-var TableJson $array */ + $table = $array["table"]; + + $tableWidth = $table["table_width"]; + $hasColumnHeader = $table["has_column_header"]; + $hasRowHeader = $table["has_row_header"]; + $rows = array_map(fn(array $row) => TableRow::fromArray($row), $table["children"]); + + return new self($block, $tableWidth, $hasColumnHeader, $hasRowHeader, $rows); + } + + public function toArray(): array + { + $array = $this->metadata->toArray(); + + $array["table"] = [ + "table_width" => $this->tableWidth, + "has_column_header" => $this->hasColumnHeader, + "has_row_header" => $this->hasRowHeader, + "children" => array_map(fn(TableRow $row) => $row->toArray(), $this->rows), + ]; + + return $array; + } + + public function metadata(): BlockMetadata + { + return $this->metadata; + } + + public function changeWidth(int $tableWidth): self + { + return new self( + $this->metadata->update(), + $tableWidth, + $this->hasColumnHeader, + $this->hasRowHeader, + $this->rows, + ); + } + + public function enableColumnHeader(): self + { + return new self( + $this->metadata->update(), + $this->tableWidth, + true, + $this->hasRowHeader, + $this->rows, + ); + } + + public function disableColumnHeader(): self + { + return new self( + $this->metadata->update(), + $this->tableWidth, + false, + $this->hasRowHeader, + $this->rows, + ); + } + + public function enableRowHeader(): self + { + return new self( + $this->metadata->update(), + $this->tableWidth, + $this->hasColumnHeader, + true, + $this->rows, + ); + } + + public function disableRowHeader(): self + { + return new self( + $this->metadata->update(), + $this->tableWidth, + $this->hasColumnHeader, + false, + $this->rows, + ); + } + + public function changeRows(TableRow ...$rows): self + { + $hasChildren = (count($rows) > 0); + + return new self( + $this->metadata->updateHasChildren($hasChildren), + $this->tableWidth, + $this->hasColumnHeader, + $this->hasRowHeader, + $rows, + ); + } + + public function addRow(TableRow $row): self + { + $rows = $this->rows; + $rows[] = $row; + + return new self( + $this->metadata->updateHasChildren(true), + $this->tableWidth, + $this->hasColumnHeader, + $this->hasRowHeader, + $rows, + ); + } + + public function changeChildren(BlockInterface ...$children): self + { + foreach ($children as $child) { + if ($child::class !== TableRow::class) { + throw BlockException::wrongType(BlockType::TableRow); + } + } + + /** @psalm-var TableRow[] $children */ + return $this->changeRows(...$children); + } + + public function addChild(BlockInterface $child): self + { + if ($child::class !== TableRow::class) { + throw BlockException::wrongType(BlockType::TableRow); + } + + /** @psalm-var TableRow $child */ + return $this->addRow($child); + } + + public function archive(): BlockInterface + { + return new self( + $this->metadata->archive(), + $this->tableWidth, + $this->hasColumnHeader, + $this->hasRowHeader, + $this->rows, + ); + } +} diff --git a/src/Blocks/TableRow.php b/src/Blocks/TableRow.php new file mode 100644 index 00000000..d4504d6d --- /dev/null +++ b/src/Blocks/TableRow.php @@ -0,0 +1,87 @@ +> + * }, + * } + * + * @psalm-immutable + */ +class TableRow implements BlockInterface +{ + /** @param RichText[][] $cells */ + private function __construct( + private readonly BlockMetadata $metadata, + public readonly array $cells, + ) { + $metadata->checkType(BlockType::TableRow); + } + + public static function create(): self + { + $block = BlockMetadata::create(BlockType::TableRow); + + return new self($block, []); + } + + public static function fromArray(array $array): self + { + /** @psalm-var BlockMetadataJson $array */ + $metadata = BlockMetadata::fromArray($array); + + /** @psalm-var TableRowJson $array */ + $cells = array_map( + fn(array $cell) => array_map(fn(array $text) => RichText::fromArray($text), $cell), + $array["table_row"]["cells"], + ); + + return new self($metadata, $cells); + } + + public function toArray(): array + { + $array = $this->metadata->toArray(); + + $array["table_row"] = [ + "cells" => array_map( + fn(array $c) => array_map(fn(RichText $t) => $t->toArray(), $c), + $this->cells + ), + ]; + + return $array; + } + + public function metadata(): BlockMetadata + { + return $this->metadata; + } + + public function changeChildren(BlockInterface ...$children): self + { + throw BlockException::noChindrenSupport(); + } + + public function addChild(BlockInterface $child): self + { + throw BlockException::noChindrenSupport(); + } + + public function archive(): BlockInterface + { + return new self( + $this->metadata->archive(), + $this->cells, + ); + } +} From 5f5267ec65075c961ab263e8beabe2d3ad3868cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Sim=C3=A3o?= Date: Fri, 27 Oct 2023 12:27:09 -0300 Subject: [PATCH 2/2] Add unit tests --- src/Blocks/TableRow.php | 8 +++ tests/Unit/Blocks/TableTest.php | 103 ++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 tests/Unit/Blocks/TableTest.php diff --git a/src/Blocks/TableRow.php b/src/Blocks/TableRow.php index d4504d6d..c7967a52 100644 --- a/src/Blocks/TableRow.php +++ b/src/Blocks/TableRow.php @@ -62,6 +62,14 @@ public function toArray(): array return $array; } + public function addCell(RichText ...$cell): self + { + $cells = $this->cells; + $cells[] = $cell; + + return new self($this->metadata, $cells); + } + public function metadata(): BlockMetadata { return $this->metadata; diff --git a/tests/Unit/Blocks/TableTest.php b/tests/Unit/Blocks/TableTest.php new file mode 100644 index 00000000..713588f8 --- /dev/null +++ b/tests/Unit/Blocks/TableTest.php @@ -0,0 +1,103 @@ +assertEmpty($table->rows); + $this->assertFalse($table->hasColumnHeader); + $this->assertFalse($table->hasRowHeader); + $this->assertSame(1, $table->tableWidth); + $this->assertSame(BlockType::Table, $table->metadata()->type); + } + + public function test_change_width(): void + { + $table = Table::create()->changeWidth(3); + + $this->assertSame(3, $table->tableWidth); + } + + public function test_enable_column_header(): void + { + $table = Table::create()->enableColumnHeader(); + + $this->assertTrue($table->hasColumnHeader); + } + + public function test_disable_column_header(): void + { + $table = Table::create()->enableColumnHeader()->disableColumnHeader(); + + $this->assertFalse($table->hasColumnHeader); + } + + public function test_enable_row_header(): void + { + $table = Table::create()->enableRowHeader(); + + $this->assertTrue($table->hasRowHeader); + } + + public function test_disable_row_header(): void + { + $table = Table::create()->enableRowHeader()->disableRowHeader(); + + $this->assertFalse($table->hasRowHeader); + } + + public function test_change_rows(): void + { + $rows = [ + $this->createRow("A1", "B1"), + $this->createRow("A2", "B2"), + $this->createRow("A3", "B3"), + ]; + + $table = Table::create() + ->changeWidth(2) + ->changeRows(...$rows); + + $this->assertEquals($rows, $table->rows); + } + + public function test_add_row(): void + { + $row = $this->createRow("A1", "B1"); + + $table = Table::create() + ->changeWidth(2) + ->addRow($row); + + $this->assertEquals($row, $table->rows[0]); + } + + public function test_remove_all_rows(): void + { + $row = $this->createRow("A1", "B1"); + + $table = Table::create() + ->changeWidth(2) + ->addRow($row) + ->changeRows(); + + $this->assertEmpty($table->rows); + } + + private function createRow(string $col1, string $col2): TableRow + { + return TableRow::create() + ->addCell(RichText::fromString($col1)) + ->addCell(RichText::fromString($col2)); + } +}