From 1dc1088933c906d4676056d53395d34ce0a68d01 Mon Sep 17 00:00:00 2001 From: Sam Light Date: Mon, 27 Jul 2026 22:28:18 +0100 Subject: changed to use sort direction --- resources/views/table.blade.php | 4 +- src/Columns/Column.php | 6 ++- src/Support/SortDirections.php | 44 ++++++++++++++++ src/TableComponent.php | 32 +++++++++--- tests/Feature/TableTest.php | 88 ++++++++++++++++++++++++++++++++ tests/Unit/SortDirectionsTest.php | 30 +++++++++++ workbench/app/Livewire/ProductsTable.php | 10 ++-- 7 files changed, 198 insertions(+), 16 deletions(-) create mode 100644 src/Support/SortDirections.php create mode 100644 tests/Unit/SortDirectionsTest.php diff --git a/resources/views/table.blade.php b/resources/views/table.blade.php index d4290a9..65687a1 100644 --- a/resources/views/table.blade.php +++ b/resources/views/table.blade.php @@ -22,8 +22,8 @@
$column->isSortable(), - 'ordered-asc' => $column->name === $order && $orderDirection === 'asc', - 'ordered-desc' => $column->name === $order && $orderDirection === 'desc', + 'ordered-asc' => $column->name === $order && $sortDirection === \SortDirection::Ascending, + 'ordered-desc' => $column->name === $order && $sortDirection === \SortDirection::Descending, ]) @if($column->isSortable()) wire:click="orderBy('{{ $column->name }}')" diff --git a/src/Columns/Column.php b/src/Columns/Column.php index ede94f7..1943a42 100644 --- a/src/Columns/Column.php +++ b/src/Columns/Column.php @@ -31,6 +31,9 @@ class Column */ private ?Closure $slotFn = null; + /** + * @var ?Closure(Builder, SortDirection): void + */ private ?Closure $sortFn = null; /** @@ -92,9 +95,8 @@ class Column * @template TModel of Model * * @param Builder $query - * @param SortDirection|'asc'|'desc'|null $dir */ - public function applySort(Builder $query, SortDirection|string|null $dir): void + public function applySort(Builder $query, SortDirection $dir): void { if ($this->sortFn !== null) { ($this->sortFn)($query, $dir); diff --git a/src/Support/SortDirections.php b/src/Support/SortDirections.php new file mode 100644 index 0000000..def0f53 --- /dev/null +++ b/src/Support/SortDirections.php @@ -0,0 +1,44 @@ + self::ASC, + SortDirection::Descending => self::DESC, + }; + } + + public static function tryFrom(SortDirection|string|null $value): ?SortDirection + { + if ($value instanceof SortDirection) { + return $value; + } + + return match ($value === null ? null : strtolower($value)) { + self::ASC => SortDirection::Ascending, + self::DESC => SortDirection::Descending, + default => null, + }; + } +} diff --git a/src/TableComponent.php b/src/TableComponent.php index e36029e..196d5f8 100644 --- a/src/TableComponent.php +++ b/src/TableComponent.php @@ -13,6 +13,7 @@ use Illuminate\Support\Str; use Illuminate\View\View; use Lightscale\LaralightAssets\Facades\Assets; use Lightscale\LaralightTables\Columns\Column; +use Lightscale\LaralightTables\Support\SortDirections; use Lightscale\LaralightTables\Toolbar\Filter; use Livewire\Attributes\Url; use Livewire\Component; @@ -66,10 +67,12 @@ abstract class TableComponent extends Component public ?string $order = null; /** - * @var SortDirection|'asc'|'desc'|null + * Wire/URL transport only — read through getOrderDirection(). + * + * @var 'asc'|'desc'|null */ #[Url] - public SortDirection|string|null $orderDirection = null; + public ?string $orderDirection = null; public function mount(): void { @@ -115,18 +118,28 @@ abstract class TableComponent extends Component $this->resetPage(); } + public function getOrderDirection(): ?SortDirection + { + return SortDirections::tryFrom($this->orderDirection); + } + + protected function setOrderDirection(?SortDirection $dir): void + { + $this->orderDirection = $dir === null ? null : SortDirections::value($dir); + } + public function orderBy(string $column): void { if ($column === $this->order) { - if ($this->orderDirection === 'desc') { + if ($this->getOrderDirection() === SortDirection::Descending) { $this->order = null; - $this->orderDirection = null; + $this->setOrderDirection(null); } else { - $this->orderDirection = 'desc'; + $this->setOrderDirection(SortDirection::Descending); } } else { $this->order = $column; - $this->orderDirection = 'asc'; + $this->setOrderDirection(SortDirection::Ascending); } } @@ -203,8 +216,10 @@ abstract class TableComponent extends Component protected function applyOrder(Builder $query): void { $column = $this->getOrderColumn(); - if ($column) { - $column->applySort($query, $this->orderDirection); + $dir = $this->getOrderDirection(); + + if ($column !== null && $dir !== null) { + $column->applySort($query, $dir); } } @@ -267,6 +282,7 @@ abstract class TableComponent extends Component return view('laralight-tables::table', [ 'rootClass' => $this->rootClass, 'tableClass' => $this->tableClass, + 'sortDirection' => $this->getOrderDirection(), ] + compact( 'data', 'allColumns', 'columns', 'toolbars', )); diff --git a/tests/Feature/TableTest.php b/tests/Feature/TableTest.php index 8dc327c..8c34095 100644 --- a/tests/Feature/TableTest.php +++ b/tests/Feature/TableTest.php @@ -2,6 +2,7 @@ use Livewire\Livewire; use Workbench\App\Livewire\ProductsTable; +use Workbench\App\Models\Product; describe('products table', function () { it('does render', function () { @@ -13,3 +14,90 @@ describe('products table', function () { ->assertSeeHtml('call('orderBy', 'name') + ->assertSet('order', 'name') + ->assertSet('orderDirection', 'asc') + ->call('orderBy', 'name') + ->assertSet('order', 'name') + ->assertSet('orderDirection', 'desc') + ->call('orderBy', 'name') + ->assertSet('order', null) + ->assertSet('orderDirection', null); + }); + + it('resets to ascending when a different column is clicked', function () { + Livewire::test(ProductsTable::class) + ->call('orderBy', 'name') + ->call('orderBy', 'name') + ->assertSet('orderDirection', 'desc') + ->call('orderBy', 'price') + ->assertSet('order', 'price') + ->assertSet('orderDirection', 'asc'); + }); + + it('exposes the direction as a SortDirection', function () { + $component = Livewire::test(ProductsTable::class); + + expect($component->instance()->getOrderDirection())->toBeNull(); + + $component->call('orderBy', 'name'); + expect($component->instance()->getOrderDirection())->toBe(SortDirection::Ascending); + + $component->call('orderBy', 'name'); + expect($component->instance()->getOrderDirection())->toBe(SortDirection::Descending); + }); + + it('marks the ordered column in the header', function () { + Livewire::test(ProductsTable::class) + ->assertDontSeeHtml('ordered-asc') + ->assertDontSeeHtml('ordered-desc') + ->call('orderBy', 'name') + ->assertSeeHtml('ordered-asc') + ->assertDontSeeHtml('ordered-desc') + ->call('orderBy', 'name') + ->assertSeeHtml('ordered-desc') + ->assertDontSeeHtml('ordered-asc'); + }); + + it('applies the direction to the query', function () { + $lowest = Product::orderBy('price')->orderBy('id')->value('id'); + $highest = Product::orderByDesc('price')->orderByDesc('id')->value('id'); + + expect($lowest)->not->toBe($highest); + + Livewire::test(ProductsTable::class) + // A single row of a single column, so the only is the ordered id. + ->set('activeColumns', ['id']) + ->set('pageSize', 1) + ->call('orderBy', 'price') + ->assertSeeHtml(">{$lowest}") + ->assertDontSeeHtml(">{$highest}") + ->call('orderBy', 'price') + ->assertSeeHtml(">{$highest}") + ->assertDontSeeHtml(">{$lowest}"); + }); + + it('restores the order from the query string', function () { + $highest = Product::orderByDesc('price')->orderByDesc('id')->value('id'); + + Livewire::withQueryParams(['order' => 'price', 'orderDirection' => 'desc']) + ->test(ProductsTable::class) + ->assertSet('orderDirection', 'desc') + ->assertSeeHtml('ordered-desc') + ->set('activeColumns', ['id']) + ->set('pageSize', 1) + ->assertSeeHtml(">{$highest}"); + }); + + it('ignores an unrecognised direction in the query string', function () { + Livewire::withQueryParams(['order' => 'price', 'orderDirection' => 'nonsense']) + ->test(ProductsTable::class) + ->assertOk() + ->assertDontSeeHtml('ordered-asc') + ->assertDontSeeHtml('ordered-desc'); + }); +}); diff --git a/tests/Unit/SortDirectionsTest.php b/tests/Unit/SortDirectionsTest.php new file mode 100644 index 0000000..c63d5bb --- /dev/null +++ b/tests/Unit/SortDirectionsTest.php @@ -0,0 +1,30 @@ +toBe('asc'); + expect(SortDirections::value(SortDirection::Descending))->toBe('desc'); + }); + + it('resolves strings to the enum', function () { + expect(SortDirections::tryFrom('asc'))->toBe(SortDirection::Ascending); + expect(SortDirections::tryFrom('desc'))->toBe(SortDirection::Descending); + }); + + it('resolves strings case insensitively', function () { + expect(SortDirections::tryFrom('ASC'))->toBe(SortDirection::Ascending); + expect(SortDirections::tryFrom('Desc'))->toBe(SortDirection::Descending); + }); + + it('passes an enum straight through', function () { + expect(SortDirections::tryFrom(SortDirection::Descending))->toBe(SortDirection::Descending); + }); + + it('resolves null and unrecognised values to null', function () { + expect(SortDirections::tryFrom(null))->toBeNull(); + expect(SortDirections::tryFrom(''))->toBeNull(); + expect(SortDirections::tryFrom('nonsense'))->toBeNull(); + }); +}); diff --git a/workbench/app/Livewire/ProductsTable.php b/workbench/app/Livewire/ProductsTable.php index f1afce7..92a8cee 100644 --- a/workbench/app/Livewire/ProductsTable.php +++ b/workbench/app/Livewire/ProductsTable.php @@ -4,12 +4,14 @@ namespace Workbench\App\Livewire; use Illuminate\Database\Eloquent\Builder; use Lightscale\LaralightTables\Columns\Column; +use Lightscale\LaralightTables\Support\SortDirections; use Lightscale\LaralightTables\Toolbar; use Lightscale\LaralightTables\Toolbar\Button; use Lightscale\LaralightTables\Toolbar\ColumnSelect; use Lightscale\LaralightTables\Toolbar\PageSize; use Lightscale\LaralightTables\Toolbar\Search; use Lightscale\LaralightTables\Toolbar\SelectFilter; +use SortDirection; use Workbench\App\Models\Category; use Workbench\App\Models\Product; @@ -70,16 +72,16 @@ class ProductsTable extends Table { return [ Column::make('id', 'ID') - ->sortable(fn (Builder $q, string $dir) => $q->orderBy('id', $dir)), + ->sortable(fn (Builder $q, SortDirection $dir) => $q->orderBy('id', SortDirections::value($dir))), Column::make('name', 'Name') - ->sortable(fn (Builder $q, string $dir) => $q->orderBy('name', $dir)), + ->sortable(fn (Builder $q, SortDirection $dir) => $q->orderBy('name', SortDirections::value($dir))), Column::make('category_name', 'Category') ->slot(fn ($r) => $r->category->name), Column::make('price', 'Price') - ->sortable(fn (Builder $q, string $dir) => $q->orderBy('price', $dir)) + ->sortable(fn (Builder $q, SortDirection $dir) => $q->orderBy('price', SortDirections::value($dir))) ->slot(fn ($r, $c) => "£{$r->{$c->name}}"), Column::make('stock', 'Stock') - ->sortable(fn (Builder $q, string $dir) => $q->orderBy('stock', $dir)), + ->sortable(fn (Builder $q, SortDirection $dir) => $q->orderBy('stock', SortDirections::value($dir))), ]; } -- cgit v1.2.3