Skip to content
This repository was archived by the owner on Jun 27, 2024. It is now read-only.

Commit b60d2f6

Browse files
committed
UI + tests
1 parent fbbbcd8 commit b60d2f6

16 files changed

+422
-167
lines changed

.github/workflows/php.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ jobs:
5959
cp .env.example .env
6060
composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest
6161
npm run production
62-
php artisan key:generate
6362
touch database/database.sqlite
6463
php artisan migrate:fresh
6564
php artisan dusk:chrome-driver `/opt/google/chrome/chrome --version | cut -d " " -f3 | cut -d "." -f1`

app/.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
APP_NAME=Laravel
22
APP_ENV=local
3-
APP_KEY=
3+
APP_KEY=base64:Ianw0xnfJCVCZsjGZOu8phM+yL2R1sP7y5TQAEKat0M=
44
APP_DEBUG=true
55
APP_URL=http://127.0.0.1:8000
66
ASSET_URL=http://127.0.0.1:8000
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace App\Http\Resources;
4+
5+
use Illuminate\Http\Resources\Json\JsonResource;
6+
7+
class UserResource extends JsonResource
8+
{
9+
/**
10+
* Transform the resource into an array.
11+
*
12+
* @param \Illuminate\Http\Request $request
13+
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
14+
*/
15+
public function toArray($request)
16+
{
17+
return [
18+
'id' => $this->id,
19+
'name' => $this->name,
20+
'email' => $this->email,
21+
'language_code' => $this->language_code,
22+
];
23+
}
24+
}

app/app/Http/UserTableView.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
namespace App\Http;
4+
5+
use App\Http\Resources\UserResource;
6+
use App\Models\User;
7+
use Inertia\Inertia;
8+
use ProtoneMedia\LaravelQueryBuilderInertiaJs\InertiaTable;
9+
use Spatie\QueryBuilder\AllowedFilter;
10+
use Spatie\QueryBuilder\QueryBuilder;
11+
12+
class UserTableView
13+
{
14+
public function __invoke($resource = false, $paginateMethod)
15+
{
16+
$globalSearch = AllowedFilter::callback('global', function ($query, $value) {
17+
$query->where(function ($query) use ($value) {
18+
$query->where('name', 'LIKE', "%{$value}%")->orWhere('email', 'LIKE', "%{$value}%");
19+
});
20+
});
21+
22+
$users = QueryBuilder::for(User::class)
23+
->defaultSort('name')
24+
->allowedSorts(['name', 'email', 'language_code'])
25+
->allowedFilters(['name', 'email', 'language_code', $globalSearch])
26+
->{$paginateMethod}(10)
27+
->withQueryString();
28+
29+
return Inertia::render('Users', [
30+
'users' => $resource ? UserResource::collection($users) : $users,
31+
])->table(function (InertiaTable $table) {
32+
$table
33+
->withGlobalSearch()
34+
->column(key: 'name', searchable: true, sortable: true, canBeHidden: false)
35+
->column(key: 'email', searchable: true, sortable: true)
36+
->column(key: 'language_code', label: 'Language')
37+
->column(label: 'Actions', custom: true)
38+
->selectFilter(key: 'language_code', label: 'Language', options: [
39+
'en' => 'English',
40+
'nl' => 'Dutch',
41+
]);
42+
});
43+
}
44+
}

app/routes/web.php

Lines changed: 8 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,13 @@
11
<?php
22

3-
use App\Models\User;
3+
use App\Http\UserTableView;
44
use Illuminate\Support\Facades\Route;
5-
use Inertia\Inertia;
6-
use ProtoneMedia\LaravelQueryBuilderInertiaJs\InertiaTable;
7-
use Spatie\QueryBuilder\AllowedFilter;
8-
use Spatie\QueryBuilder\QueryBuilder;
95

10-
Route::get('/users', function () {
11-
$globalSearch = AllowedFilter::callback('global', function ($query, $value) {
12-
$query->where(function ($query) use ($value) {
13-
$query->where('name', 'LIKE', "%{$value}%")->orWhere('email', 'LIKE', "%{$value}%");
14-
});
15-
});
6+
$table = new UserTableView;
167

17-
$users = QueryBuilder::for(User::class)
18-
->defaultSort('name')
19-
->allowedSorts(['name', 'email', 'language_code'])
20-
->allowedFilters(['name', 'email', 'language_code', $globalSearch])
21-
->paginate(10)
22-
->withQueryString();
23-
24-
return Inertia::render('Users', [
25-
'users' => $users,
26-
])->table(function (InertiaTable $table) {
27-
$table
28-
->withGlobalSearch()
29-
->column(key: 'name', searchable: true, sortable: true, canBeHidden: false)
30-
->column(key: 'email', searchable: true, sortable: true)
31-
->column(key: 'language_code', label: 'Language')
32-
->column(label: 'Actions', custom: true)
33-
->selectFilter(key: 'language_code', label: 'Language', options: [
34-
'en' => 'English',
35-
'nl' => 'Dutch',
36-
]);
37-
});
38-
});
8+
Route::get('/users/eloquent', fn () => $table(resource: false, paginateMethod: 'paginate'));
9+
Route::get('/users/resource', fn () => $table(resource: true, paginateMethod: 'paginate'));
10+
Route::get('/users/eloquent/simple', fn () => $table(resource: false, paginateMethod: 'simplePaginate'));
11+
Route::get('/users/resource/simple', fn () => $table(resource: true, paginateMethod: 'simplePaginate'));
12+
Route::get('/users/eloquent/cursor', fn () => $table(resource: false, paginateMethod: 'cursorPaginate'));
13+
Route::get('/users/resource/cursor', fn () => $table(resource: true, paginateMethod: 'cursorPaginate'));

app/tests/Browser/AutoFillTest.php

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,56 @@
22

33
namespace Tests\Browser;
44

5+
use App\Models\User;
56
use Illuminate\Support\Str;
67
use Laravel\Dusk\Browser;
78
use Tests\DuskTestCase;
89

910
class AutoFillTest extends DuskTestCase
1011
{
1112
/** @test */
12-
public function it_generates_the_table_header()
13+
public function it_generates_the_table_header_with_sort_buttons()
1314
{
1415
$this->browse(function (Browser $browser) {
15-
$browser->visit('/users')
16+
$browser->visit('/users/eloquent')
17+
// Header names
1618
->assertSeeIn('th:nth-child(1)', Str::upper('Name'))
1719
->assertSeeIn('th:nth-child(2)', Str::upper('Email'))
1820
->assertSeeIn('th:nth-child(3)', Str::upper('Language'))
19-
->assertSeeIn('th:nth-child(4)', Str::upper('Actions'));
21+
->assertSeeIn('th:nth-child(4)', Str::upper('Actions'))
22+
23+
// Sort buttons
24+
->assertPresent('th:nth-child(1) button')
25+
->assertPresent('th:nth-child(2) button')
26+
->assertNotPresent('th:nth-child(3) button')
27+
->assertNotPresent('th:nth-child(4) button');
28+
});
29+
}
30+
31+
/** @test */
32+
public function it_generates_the_table_body_with_a_custom_action_column()
33+
{
34+
$this->browse(function (Browser $browser) {
35+
$users = User::orderBy('name')->limit(10)->get();
36+
37+
$browser->visit('/users/eloquent')
38+
// First user
39+
->assertSeeIn('tr:first-child td:nth-child(1)', $users->first()->name)
40+
->assertSeeIn('tr:first-child td:nth-child(2)', $users->first()->email)
41+
->assertSeeIn('tr:first-child td:nth-child(3)', $users->first()->language_code)
42+
->within('tr:first-child td:nth-child(4)', function (Browser $browser) use ($users) {
43+
$browser->assertSeeLink('Edit')
44+
->assertAttribute('a', 'href', "/users/{$users->first()->id}/edit");
45+
})
46+
47+
// Last user
48+
->assertSeeIn('tr:last-child td:nth-child(1)', $users->last()->name)
49+
->assertSeeIn('tr:last-child td:nth-child(2)', $users->last()->email)
50+
->assertSeeIn('tr:last-child td:nth-child(3)', $users->last()->language_code)
51+
->within('tr:last-child td:nth-child(4)', function (Browser $browser) use ($users) {
52+
$browser->assertSeeLink('Edit')
53+
->assertAttribute('a', 'href', "/users/{$users->last()->id}/edit");
54+
});
2055
});
2156
}
2257
}

app/tests/Browser/PaginationTest.php

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
<?php
2+
3+
namespace Tests\Browser;
4+
5+
use App\Models\User;
6+
use Laravel\Dusk\Browser;
7+
use Tests\DuskTestCase;
8+
9+
class PaginationTest extends DuskTestCase
10+
{
11+
public function fullUrls()
12+
{
13+
return [
14+
['/users/eloquent'],
15+
['/users/resource'],
16+
];
17+
}
18+
19+
public function simpleUrls()
20+
{
21+
return [
22+
['/users/eloquent/', 'simple'],
23+
['/users/resource/', 'simple'],
24+
['/users/eloquent/', 'cursor'],
25+
['/users/resource/', 'cursor'],
26+
];
27+
}
28+
29+
/**
30+
* @test
31+
* @dataProvider fullUrls
32+
*/
33+
public function it_generates_a_paginator_with_links($url)
34+
{
35+
$this->browse(function (Browser $browser) use ($url) {
36+
$users = User::query()
37+
->select(['id', 'name'])
38+
->orderBy('name')
39+
->get();
40+
41+
$browser
42+
->visit($url)
43+
->resize(1920, 1080)
44+
->assertMissing('@pagination-simple-previous')
45+
->assertMissing('@pagination-simple-next')
46+
47+
->assertSeeIn('tr:first-child td:nth-child(1)', $users->get(0)->name)
48+
->assertSeeIn('tr:last-child td:nth-child(1)', $users->get(9)->name)
49+
->assertMissing('@pagination-previous')
50+
->press('@pagination-next')
51+
->waitUntilMissingText($users->get(0)->name)
52+
->assertSeeIn('tr:first-child td:nth-child(1)', $users->get(10)->name)
53+
->assertSeeIn('tr:last-child td:nth-child(1)', $users->get(19)->name)
54+
->press('@pagination-3')
55+
->waitUntilMissingText($users->get(10)->name)
56+
->assertSeeIn('tr:first-child td:nth-child(1)', $users->get(20)->name)
57+
->assertSeeIn('tr:last-child td:nth-child(1)', $users->get(29)->name)
58+
->press('@pagination-previous')
59+
->waitUntilMissingText($users->get(20)->name)
60+
->assertSeeIn('tr:first-child td:nth-child(1)', $users->get(10)->name)
61+
->assertSeeIn('tr:last-child td:nth-child(1)', $users->get(19)->name)
62+
->press('@pagination-10')
63+
->waitUntilMissingText($users->get(10)->name)
64+
->assertMissing('@pagination-next')
65+
66+
->press('@pagination-1')
67+
->waitUntilMissingText($users->get(99)->name)
68+
69+
// mobile pagination
70+
->resize(320, 480)
71+
72+
->assertMissing('@pagination-next')
73+
->assertMissing('@pagination-previous')
74+
75+
->assertSeeIn('tr:first-child td:nth-child(1)', $users->get(0)->name)
76+
->assertSeeIn('tr:last-child td:nth-child(1)', $users->get(9)->name)
77+
->assertMissing('@pagination-simple-previous')
78+
->press('@pagination-simple-next')
79+
->waitUntilMissingText($users->get(0)->name)
80+
->assertSeeIn('tr:first-child td:nth-child(1)', $users->get(10)->name)
81+
->assertSeeIn('tr:last-child td:nth-child(1)', $users->get(19)->name)
82+
->visit($url . '?page=10')
83+
->waitUntilMissingText($users->get(10)->name)
84+
->assertMissing('@pagination-simple-next')
85+
->press('@pagination-simple-previous')
86+
->waitUntilMissingText($users->get(99)->name)
87+
->assertSeeIn('tr:first-child td:nth-child(1)', $users->get(80)->name)
88+
->assertSeeIn('tr:last-child td:nth-child(1)', $users->get(89)->name);
89+
});
90+
}
91+
92+
/**
93+
* @test
94+
* @dataProvider simpleUrls
95+
*/
96+
public function it_generates_a_simple_paginator($url, $method)
97+
{
98+
$url .= $method;
99+
100+
$this->browse(function (Browser $browser) use ($url, $method) {
101+
$users = User::query()
102+
->select(['id', 'name'])
103+
->orderBy('name')
104+
->get();
105+
106+
$pageTen = $method === 'simple' ? '?page=10' : '?cursor=eyJuYW1lIjoiUHJvZi4gVGhlbyBTY2hpbW1lbCIsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0';
107+
108+
$browser
109+
->visit($url)
110+
->resize(1920, 1080)
111+
112+
->assertMissing('@pagination-next')
113+
->assertMissing('@pagination-previous')
114+
115+
->assertSeeIn('tr:first-child td:nth-child(1)', $users->get(0)->name)
116+
->assertSeeIn('tr:last-child td:nth-child(1)', $users->get(9)->name)
117+
->assertMissing('@pagination-simple-previous')
118+
->press('@pagination-simple-next')
119+
->waitUntilMissingText($users->get(0)->name)
120+
->assertSeeIn('tr:first-child td:nth-child(1)', $users->get(10)->name)
121+
->assertSeeIn('tr:last-child td:nth-child(1)', $users->get(19)->name)
122+
->visit($url . $pageTen)
123+
->waitUntilMissingText($users->get(10)->name)
124+
->assertMissing('@pagination-simple-next')
125+
->press('@pagination-simple-previous')
126+
->waitUntilMissingText($users->get(99)->name)
127+
->assertSeeIn('tr:first-child td:nth-child(1)', $users->get(80)->name)
128+
->assertSeeIn('tr:last-child td:nth-child(1)', $users->get(89)->name)
129+
130+
// mobile pagination
131+
->visit($url)
132+
->resize(320, 480)
133+
134+
->assertMissing('@pagination-next')
135+
->assertMissing('@pagination-previous')
136+
137+
->assertSeeIn('tr:first-child td:nth-child(1)', $users->get(0)->name)
138+
->assertSeeIn('tr:last-child td:nth-child(1)', $users->get(9)->name)
139+
->assertMissing('@pagination-simple-previous')
140+
->press('@pagination-simple-next')
141+
->waitUntilMissingText($users->get(0)->name)
142+
->assertSeeIn('tr:first-child td:nth-child(1)', $users->get(10)->name)
143+
->assertSeeIn('tr:last-child td:nth-child(1)', $users->get(19)->name)
144+
->visit($url . $pageTen)
145+
->waitUntilMissingText($users->get(10)->name)
146+
->assertMissing('@pagination-simple-next')
147+
->press('@pagination-simple-previous')
148+
->waitUntilMissingText($users->get(99)->name)
149+
->assertSeeIn('tr:first-child td:nth-child(1)', $users->get(80)->name)
150+
->assertSeeIn('tr:last-child td:nth-child(1)', $users->get(89)->name);
151+
});
152+
}
153+
}

0 commit comments

Comments
 (0)