From 25f36eaf54ce074746e925650a845b3096b16ffa Mon Sep 17 00:00:00 2001 From: Benjamin Nussbaum Date: Mon, 25 Aug 2025 14:43:22 +0200 Subject: [PATCH] feat: add Liquid filter 'group_by' --- app/Liquid/Filters/Data.php | 24 +++++ tests/Feature/PluginInlineTemplatesTest.php | 24 +++++ tests/Unit/Liquid/Filters/DataTest.php | 104 ++++++++++++++++++++ 3 files changed, 152 insertions(+) diff --git a/app/Liquid/Filters/Data.php b/app/Liquid/Filters/Data.php index 61343b2..4437032 100644 --- a/app/Liquid/Filters/Data.php +++ b/app/Liquid/Filters/Data.php @@ -39,4 +39,28 @@ class Data extends FiltersProvider return $fallback; } + + /** + * Group a collection by a specific key + * + * @param array $collection The collection to group + * @param string $key The key to group by + * @return array The grouped collection + */ + public function group_by(array $collection, string $key): array + { + $grouped = []; + + foreach ($collection as $item) { + if (is_array($item) && array_key_exists($key, $item)) { + $groupKey = $item[$key]; + if (! isset($grouped[$groupKey])) { + $grouped[$groupKey] = []; + } + $grouped[$groupKey][] = $item; + } + } + + return $grouped; + } } diff --git a/tests/Feature/PluginInlineTemplatesTest.php b/tests/Feature/PluginInlineTemplatesTest.php index 7451d93..ce83d9d 100644 --- a/tests/Feature/PluginInlineTemplatesTest.php +++ b/tests/Feature/PluginInlineTemplatesTest.php @@ -228,4 +228,28 @@ LIQUID // Should return the fallback value $this->assertStringContainsString('Not Found', $result); } + + public function test_plugin_with_group_by_filter(): void + { + $plugin = Plugin::factory()->create([ + 'markup_language' => 'liquid', + 'render_markup' => <<<'LIQUID' +{{ collection | group_by: 'age' | json }} +LIQUID + , + 'data_payload' => [ + 'collection' => [ + ['name' => 'Ryan', 'age' => 35], + ['name' => 'Sara', 'age' => 29], + ['name' => 'Jimbob', 'age' => 29], + ], + ], + ]); + + $result = $plugin->render('full'); + + // Should output JSON representation of grouped data + $this->assertStringContainsString('"35":[{"name":"Ryan","age":35}]', $result); + $this->assertStringContainsString('"29":[{"name":"Sara","age":29},{"name":"Jimbob","age":29}]', $result); + } } diff --git a/tests/Unit/Liquid/Filters/DataTest.php b/tests/Unit/Liquid/Filters/DataTest.php index 131dc24..8145088 100644 --- a/tests/Unit/Liquid/Filters/DataTest.php +++ b/tests/Unit/Liquid/Filters/DataTest.php @@ -133,3 +133,107 @@ test('find_by filter handles items without the specified key', function () { $result = $filter->find_by($collection, 'name', 'Ryan'); expect($result)->toBe(['name' => 'Ryan', 'age' => 35]); }); + +test('group_by filter groups collection by age', function () { + $filter = new Data(); + $collection = [ + ['name' => 'Ryan', 'age' => 35], + ['name' => 'Sara', 'age' => 29], + ['name' => 'Jimbob', 'age' => 29], + ]; + + $result = $filter->group_by($collection, 'age'); + + expect($result)->toBe([ + 35 => [['name' => 'Ryan', 'age' => 35]], + 29 => [ + ['name' => 'Sara', 'age' => 29], + ['name' => 'Jimbob', 'age' => 29], + ], + ]); +}); + +test('group_by filter groups collection by name', function () { + $filter = new Data(); + $collection = [ + ['name' => 'Ryan', 'age' => 35], + ['name' => 'Sara', 'age' => 29], + ['name' => 'Ryan', 'age' => 30], + ]; + + $result = $filter->group_by($collection, 'name'); + + expect($result)->toBe([ + 'Ryan' => [ + ['name' => 'Ryan', 'age' => 35], + ['name' => 'Ryan', 'age' => 30], + ], + 'Sara' => [['name' => 'Sara', 'age' => 29]], + ]); +}); + +test('group_by filter handles empty collection', function () { + $filter = new Data(); + $collection = []; + + $result = $filter->group_by($collection, 'age'); + expect($result)->toBe([]); +}); + +test('group_by filter handles collection with non-array items', function () { + $filter = new Data(); + $collection = [ + 'not an array', + ['name' => 'Ryan', 'age' => 35], + null, + ['name' => 'Sara', 'age' => 29], + ]; + + $result = $filter->group_by($collection, 'age'); + + expect($result)->toBe([ + 35 => [['name' => 'Ryan', 'age' => 35]], + 29 => [['name' => 'Sara', 'age' => 29]], + ]); +}); + +test('group_by filter handles items without the specified key', function () { + $filter = new Data(); + $collection = [ + ['age' => 35], + ['name' => 'Ryan', 'age' => 35], + ['title' => 'Developer'], + ['name' => 'Sara', 'age' => 29], + ]; + + $result = $filter->group_by($collection, 'age'); + + expect($result)->toBe([ + 35 => [ + ['age' => 35], + ['name' => 'Ryan', 'age' => 35], + ], + 29 => [['name' => 'Sara', 'age' => 29]], + ]); +}); + +test('group_by filter handles mixed data types as keys', function () { + $filter = new Data(); + $collection = [ + ['name' => 'Ryan', 'active' => true], + ['name' => 'Sara', 'active' => false], + ['name' => 'Jimbob', 'active' => true], + ['name' => 'Alice', 'active' => null], + ]; + + $result = $filter->group_by($collection, 'active'); + + expect($result)->toBe([ + 1 => [ // PHP converts true to 1 + ['name' => 'Ryan', 'active' => true], + ['name' => 'Jimbob', 'active' => true], + ], + 0 => [['name' => 'Sara', 'active' => false]], // PHP converts false to 0 + '' => [['name' => 'Alice', 'active' => null]], // PHP converts null keys to empty string + ]); +});