From 5df13ba2ef4f39f79a14544d74701e87b2aa2242 Mon Sep 17 00:00:00 2001 From: ailkiv Date: Wed, 20 Aug 2025 12:44:45 +0000 Subject: [PATCH 1/2] chore: Added phpunit for the filtering function in Row2Mapper::findAll Signed-off-by: ailkiv --- tests/unit/Database/DatabaseTestCase.php | 69 +++++++++++++++-- tests/unit/Db/Row2MapperFilterTest.php | 80 ++++++++++++++++++++ tests/unit/Db/Row2MapperTestDependencies.php | 42 ++++++++-- 3 files changed, 178 insertions(+), 13 deletions(-) diff --git a/tests/unit/Database/DatabaseTestCase.php b/tests/unit/Database/DatabaseTestCase.php index 8041520c93..f3270325f9 100644 --- a/tests/unit/Database/DatabaseTestCase.php +++ b/tests/unit/Database/DatabaseTestCase.php @@ -191,7 +191,12 @@ protected function createTestColumn(int $tableId, array $data = []) { 'mandatory' => false, 'order_weight' => 0, 'number_prefix' => '', - 'number_suffix' => '' + 'number_suffix' => '', + 'text_default' => '', + 'number_default' => null, + 'datetime_default' => '', + 'selection_default' => '', + 'usergroup_default' => '', ]; $testIdent = $data['test_ident'] ?? null; @@ -316,27 +321,32 @@ protected function addCellsToRow(int $rowId, array $cellsData, array $columnMapp */ protected function insertCellData(int $rowId, int $columnId, $value): void { $qb = $this->connection->getQueryBuilder(); - $result = $qb->select('type') + $result = $qb->select('type', 'subtype') ->from('tables_columns') ->where($qb->expr()->eq('id', $qb->createNamedParameter($columnId))) ->executeQuery(); - $columnType = $result->fetchOne(); + $column = $result->fetch(); $result->closeCursor(); - if (!$columnType) { + if (!$column) { throw new \InvalidArgumentException("Column with ID $columnId not found"); } - $this->insertCellIntoTypeTable($rowId, $columnId, $value, $columnType); + $this->insertCellIntoTypeTable($rowId, $columnId, $value, $column['type'], $column['subtype']); } /** * Inserts cell data into the appropriate type-specific table */ - protected function insertCellIntoTypeTable(int $rowId, int $columnId, $value, string $columnType): void { + protected function insertCellIntoTypeTable(int $rowId, int $columnId, $value, string $columnType, string $columnSubtype): void { $tableName = 'tables_row_cells_' . $columnType; + // Handle selection type - convert values to IDs based on selection_options + if ($columnType === 'selection' && $columnSubtype !== 'check') { + $value = $this->convertSelectionValuesToIds($columnId, $value); + } + $qb = $this->connection->getQueryBuilder(); $qb->insert($tableName) ->setValue('row_id', $qb->createNamedParameter($rowId)) @@ -401,6 +411,53 @@ protected function extractTestIdentMapping(array $results): array { return $mapping; } + /** + * Converts selection values to IDs based on selection_options + */ + protected function convertSelectionValuesToIds(int $columnId, $value) { + // Get column configuration to find selection_options + $qb = $this->connection->getQueryBuilder(); + $result = $qb->select('selection_options') + ->from('tables_columns') + ->where($qb->expr()->eq('id', $qb->createNamedParameter($columnId))) + ->executeQuery(); + + $selectionOptions = $result->fetchOne(); + $result->closeCursor(); + + if (!$selectionOptions) { + throw new \InvalidArgumentException("Column with ID $columnId not found"); + } + + $selectionOptions = json_decode($selectionOptions, true); + + // Create mapping from label to id + $optionMapping = []; + foreach ($selectionOptions as $option) { + if (isset($option['label']) && isset($option['id'])) { + $optionMapping[$option['label']] = $option['id']; + } + } + + // Convert single value or array of values + if (is_array($value)) { + // Multiple selection - convert each value to ID and return as JSON + $convertedValues = []; + foreach ($value as $optionText) { + if (isset($optionMapping[$optionText])) { + $convertedValues[] = $optionMapping[$optionText]; + } + } + return json_encode($convertedValues); + } else { + // Single selection - convert to ID + if (isset($optionMapping[$value])) { + return $optionMapping[$value]; + } + return null; + } + } + /** * Gets ID by test_ident from creation results * @param array $results Array of creation results diff --git a/tests/unit/Db/Row2MapperFilterTest.php b/tests/unit/Db/Row2MapperFilterTest.php index a24e6758fd..9f6a88de34 100644 --- a/tests/unit/Db/Row2MapperFilterTest.php +++ b/tests/unit/Db/Row2MapperFilterTest.php @@ -155,6 +155,86 @@ public static function filterDataProvider(): array { ['Alice'], 'Filter by created_by meta column' ], + + // Text column (surname): hyphenated edge case and a real empty match + 'surname contains son' => [ + [['columnId' => 'surname', 'operator' => 'contains', 'value' => 'son']], + ['Alice', 'Bob', 'Charlie'], + 'Filter surnames containing son' + ], + 'surname ends-with son' => [ + [['columnId' => 'surname', 'operator' => 'ends-with', 'value' => 'son']], + ['Bob', 'Charlie'], + 'Filter surnames ending with son (Thompson-Jones excluded)' + ], + 'surname is-empty' => [ + [['columnId' => 'surname', 'operator' => 'is-empty', 'value' => '']], + ['Diana'], + 'Filter empty surname' + ], + + // Number column (experience_years): equality, >=, <= and empty (Bob has none) + 'experience is-equal 5' => [ + [['columnId' => 'experience_years', 'operator' => 'is-equal', 'value' => '5']], + ['Alice'], + 'Filter experience years equal to 5' + ], + 'experience gte 5' => [ + [['columnId' => 'experience_years', 'operator' => 'is-greater-than-or-equal', 'value' => '5']], + ['Alice', 'Eve'], + 'Filter experience years greater than or equal to 5' + ], + 'experience lte 5' => [ + [['columnId' => 'experience_years', 'operator' => 'is-lower-than-or-equal', 'value' => '5']], + ['Alice', 'Bob', 'Charlie', 'Diana'], + 'Filter experience years lower than or equal to 5 (Bob included via default)' + ], + 'experience is-empty' => [ + [['columnId' => 'experience_years', 'operator' => 'is-empty', 'value' => '']], + ['Bob'], + 'Filter empty experience years' + ], + + // Selection single column (status) + 'status is-equal Inactive' => [ + [['columnId' => 'status', 'operator' => 'is-equal', 'value' => '@selection-id-1']], + ['Bob'], + 'Filter status equal to Inactive (id 1)' + ], + 'status contains Active' => [ + [['columnId' => 'status', 'operator' => 'contains', 'value' => '@selection-id-0']], + ['Alice', 'Charlie', 'Eve'], + 'Filter status containing Active (id 0)' + ], + + // Selection multi column (skills) + 'skills contains PHP' => [ + [['columnId' => 'skills', 'operator' => 'contains', 'value' => '@selection-id-0']], + ['Alice'], + 'Filter skills containing PHP (id 0)' + ], + 'skills is-equal Python' => [ + [['columnId' => 'skills', 'operator' => 'is-equal', 'value' => '@selection-id-3']], + ['Charlie'], + 'Filter skills equal to exactly Python (id 3)' + ], + + // Selection checkbox column (is_available) + 'available is checked' => [ + [['columnId' => 'is_available', 'operator' => 'is-equal', 'value' => '@checked']], + ['Alice', 'Charlie'], + 'Filter available is checked' + ], + 'available is unchecked' => [ + [['columnId' => 'is_available', 'operator' => 'is-equal', 'value' => '@unchecked']], + ['Bob', 'Diana'], + 'Filter available is unchecked' + ], + 'available is-empty' => [ + [['columnId' => 'is_available', 'operator' => 'is-empty', 'value' => '']], + ['Eve'], + 'Filter available is empty' + ], ]; } diff --git a/tests/unit/Db/Row2MapperTestDependencies.php b/tests/unit/Db/Row2MapperTestDependencies.php index 297eaf1674..916ed8ca1e 100644 --- a/tests/unit/Db/Row2MapperTestDependencies.php +++ b/tests/unit/Db/Row2MapperTestDependencies.php @@ -88,7 +88,12 @@ private function initializeTestData(): void { ['test_ident' => 'age', 'title' => 'Age', 'type' => 'number'], ['test_ident' => 'birthday', 'title' => 'Birthday', 'type' => 'datetime'], ['test_ident' => 'department', 'title' => 'Department', 'type' => 'text'], - ['test_ident' => 'score', 'title' => 'Score', 'type' => 'number'] + ['test_ident' => 'score', 'title' => 'Score', 'type' => 'number'], + ['test_ident' => 'surname', 'title' => 'Surname', 'type' => 'text'], + ['test_ident' => 'status', 'title' => 'Status', 'type' => 'selection', 'subtype' => '', 'selection_options' => json_encode([['id' => 0, 'label' => 'Active'], ['id' => 1, 'label' => 'Inactive'], ['id' => 2, 'label' => 'Pending']])], + ['test_ident' => 'skills', 'title' => 'Skills', 'type' => 'selection', 'subtype' => 'multi', 'selection_options' => json_encode([['id' => 0, 'label' => 'PHP'], ['id' => 1, 'label' => 'JavaScript'], ['id' => 2, 'label' => 'SQL'], ['id' => 3, 'label' => 'Python'], ['id' => 4, 'label' => 'Java'], ['id' => 5, 'label' => 'React'], ['id' => 6, 'label' => 'Node.js'], ['id' => 7, 'label' => 'MongoDB'], ['id' => 8, 'label' => 'Docker'], ['id' => 9, 'label' => 'Management'], ['id' => 10, 'label' => 'Communication'], ['id' => 11, 'label' => 'Excel'], ['id' => 12, 'label' => 'Accounting'], ['id' => 13, 'label' => 'Analysis']])], + ['test_ident' => 'is_available', 'title' => 'Available', 'type' => 'selection', 'subtype' => 'check', 'selection_options' => ''], + ['test_ident' => 'experience_years', 'title' => 'Experience (Years)', 'type' => 'number'] ], [ [ @@ -100,7 +105,12 @@ private function initializeTestData(): void { 'age' => 28, 'birthday' => '1995-05-15 10:30:00', 'department' => 'IT', - 'score' => 85.5 + 'score' => 85.5, + 'surname' => 'Thompson-Jones', + 'status' => 'Active', + 'skills' => ['PHP', 'JavaScript', 'SQL', 'Python'], + 'is_available' => '"true"', + 'experience_years' => 5 ] ], [ @@ -112,7 +122,11 @@ private function initializeTestData(): void { 'age' => 32, 'birthday' => '1991-12-03 14:20:00', 'department' => 'HR', - 'score' => 92.0 + 'score' => 92.0, + 'surname' => 'Thompson', + 'status' => 'Inactive', + 'skills' => ['Management', 'Communication'], + 'is_available' => '"false"' ] ], [ @@ -124,7 +138,12 @@ private function initializeTestData(): void { 'age' => 25, 'birthday' => '1998-01-20 08:45:00', 'department' => 'IT', - 'score' => 78.3 + 'score' => 78.3, + 'surname' => 'Wilson', + 'status' => 'Active', + 'skills' => ['Python'], + 'is_available' => '"true"', + 'experience_years' => 2 ] ], [ @@ -136,7 +155,11 @@ private function initializeTestData(): void { 'age' => 25, 'birthday' => '1998-08-10 16:00:00', 'department' => 'Finance', - 'score' => 88.7 + 'score' => 88.7, + 'status' => 'Pending', + 'skills' => ['Excel', 'Accounting', 'Analysis'], + 'is_available' => '"false"', + 'experience_years' => 3 ] ], [ @@ -148,7 +171,11 @@ private function initializeTestData(): void { 'age' => 30, 'birthday' => '1993-03-25 12:15:00', 'department' => 'IT', - 'score' => 95.2 + 'score' => 95.2, + 'surname' => 'Davis', + 'status' => 'Active', + 'skills' => ['React', 'Node.js', 'MongoDB', 'Docker'], + 'experience_years' => 7 ] ] ] @@ -170,7 +197,7 @@ private function initializeTestData(): void { */ protected function setupRealColumnMapper(int $tableId): void { $qb = $this->connection->getQueryBuilder(); - $result = $qb->select('id', 'title', 'type', 'table_id') + $result = $qb->select('id', 'title', 'type', 'subtype', 'table_id') ->from('tables_columns') ->where($qb->expr()->eq('table_id', $qb->createNamedParameter($tableId))) ->executeQuery(); @@ -182,6 +209,7 @@ protected function setupRealColumnMapper(int $tableId): void { $column->setId($row['id']); $column->setTitle($row['title']); $column->setType($row['type']); + $column->setSubtype($row['subtype']); $column->setTableId($row['table_id']); $columns[$row['id']] = $column; $columnTypes[$row['id']] = $row['type']; From 9921f8ff12ccc98b746d410a747af4be23c9f453 Mon Sep 17 00:00:00 2001 From: "Enjeck C." Date: Sat, 29 Aug 2026 21:49:01 +0100 Subject: [PATCH 2/2] fix: rector Signed-off-by: Enjeck C. --- tests/unit/Database/DatabaseTestCase.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/unit/Database/DatabaseTestCase.php b/tests/unit/Database/DatabaseTestCase.php index f3270325f9..4519504a84 100644 --- a/tests/unit/Database/DatabaseTestCase.php +++ b/tests/unit/Database/DatabaseTestCase.php @@ -450,11 +450,7 @@ protected function convertSelectionValuesToIds(int $columnId, $value) { } return json_encode($convertedValues); } else { - // Single selection - convert to ID - if (isset($optionMapping[$value])) { - return $optionMapping[$value]; - } - return null; + return $optionMapping[$value] ?? null; } }