summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Light <samlight1994@gmail.com>2026-07-27 22:28:18 +0100
committerSam Light <samlight1994@gmail.com>2026-07-27 22:28:18 +0100
commit1dc1088933c906d4676056d53395d34ce0a68d01 (patch)
treeb3c39b1e58305e2b0985678b720991883372667f
parent827a53363e7a03329d6e5972d9d9be99d17c889e (diff)
changed to use sort directionHEADv3.0.0master
-rw-r--r--resources/views/table.blade.php4
-rw-r--r--src/Columns/Column.php6
-rw-r--r--src/Support/SortDirections.php44
-rw-r--r--src/TableComponent.php32
-rw-r--r--tests/Feature/TableTest.php88
-rw-r--r--tests/Unit/SortDirectionsTest.php30
-rw-r--r--workbench/app/Livewire/ProductsTable.php10
7 files changed, 198 insertions, 16 deletions
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 @@
<div
@class([
'ordered' => $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<Model>, SortDirection): void
+ */
private ?Closure $sortFn = null;
/**
@@ -92,9 +95,8 @@ class Column
* @template TModel of Model
*
* @param Builder<TModel> $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 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Lightscale\LaralightTables\Support;
+
+use SortDirection;
+
+/**
+ * Converts between SortDirection and the 'asc'/'desc' strings used by the
+ * query builder and the Livewire wire/URL boundary.
+ *
+ * SortDirection is a pure enum, so it carries no value of its own.
+ */
+final class SortDirections
+{
+ public const ASC = 'asc';
+
+ public const DESC = 'desc';
+
+ /**
+ * @return 'asc'|'desc'
+ */
+ public static function value(SortDirection $dir): string
+ {
+ return match ($dir) {
+ SortDirection::Ascending => 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('<td');
});
});
+
+describe('products table ordering', function () {
+ it('cycles a column through ascending, descending and unordered', function () {
+ Livewire::test(ProductsTable::class)
+ ->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 <td> is the ordered id.
+ ->set('activeColumns', ['id'])
+ ->set('pageSize', 1)
+ ->call('orderBy', 'price')
+ ->assertSeeHtml(">{$lowest}</td>")
+ ->assertDontSeeHtml(">{$highest}</td>")
+ ->call('orderBy', 'price')
+ ->assertSeeHtml(">{$highest}</td>")
+ ->assertDontSeeHtml(">{$lowest}</td>");
+ });
+
+ 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}</td>");
+ });
+
+ 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 @@
+<?php
+
+use Lightscale\LaralightTables\Support\SortDirections;
+
+describe('SortDirections', function () {
+ it('converts the enum to a query builder direction', function () {
+ expect(SortDirections::value(SortDirection::Ascending))->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))),
];
}