Skip to content

Commit 3977497

Browse files
authored
Merge pull request #675 from code16/new-testing-api
New testing API
2 parents 86883da + 3b9b877 commit 3977497

35 files changed

Lines changed: 2359 additions & 82 deletions

docs/.vitepress/sidebar.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ export function sidebar(): DefaultTheme.SidebarItem[] {
101101
{ text: 'Sharp Context', link: '/guide/context.md' },
102102
{ text: 'Sharp built-in solution for uploads', link: '/guide/sharp-uploads.md' },
103103
{ text: 'Data localization in Form and Show Page', link: '/guide/data-localization.md' },
104-
{ text: 'Testing with Sharp', link: '/guide/testing-with-sharp.md' },
104+
{ text: 'Testing', link: '/guide/testing.md' },
105+
{ text: 'Testing (legacy API)', link: '/guide/testing-legacy.md' },
105106
{ text: 'Artisan Generators', link: '/guide/artisan-generators.md' },
106107
{ text: 'Style & Visual Theme', link: '/guide/style-visual-theme.md' }
107108
]
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
# Testing with Sharp
1+
# Testing with Sharp (legacy API)
2+
3+
::: warning
4+
This page documents the old Testing API, we recommend using the new [Testing API](/guide/testing).
5+
:::
26

37
Sharp provides a few assertions and helpers to help you test your Sharp code.
48

docs/guide/testing.md

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
# Testing
2+
3+
::: tip INFO
4+
This page documents the new Testing API. If you use the legacy one, please refer to [Testing (legacy)](../legacy/testing.md).
5+
:::
6+
7+
Sharp provides a fluent testing API to help you test your Sharp code. These assertions and helpers are designed to be used in Feature tests.
8+
9+
## The `SharpAssertions` trait
10+
11+
To use Sharp's testing helpers, include the `Code16\Sharp\Utils\Testing\SharpAssertions` trait in your TestCase class:
12+
13+
```php
14+
use Code16\Sharp\Utils\Testing\SharpAssertions;
15+
16+
abstract class TestCase extends BaseTestCase
17+
{
18+
use SharpAssertions;
19+
20+
// ...
21+
}
22+
```
23+
24+
or in `Pest.php`:
25+
26+
```php
27+
use Code16\Sharp\Utils\Testing\SharpAssertions;
28+
29+
pest()
30+
->extend(\Tests\TestCase::class)
31+
->use(SharpAssertions::class);
32+
```
33+
34+
## Authentication
35+
36+
### `loginAsSharpUser($user)`
37+
38+
Sharp provides a helper to log in a user. By default, it will use the `SharpAssertions` internal logic to ensure the user is authorized to access Sharp.
39+
40+
```php
41+
it('allows the user to access the list', function () {
42+
$user = User::factory()->create();
43+
44+
$this
45+
->loginAsSharpUser($user)
46+
->sharpList(Post::class)
47+
->get()
48+
->assertOk();
49+
});
50+
```
51+
52+
## Testing Entity Lists
53+
54+
Use `sharpList()` to test your Entity Lists.
55+
56+
### `sharpList(string $entityKey)`
57+
58+
Starts a fluent interaction with an Entity List.
59+
60+
```php
61+
$this->sharpList(Post::class)
62+
->get()
63+
->assertOk()
64+
->assertListData(fn (AssertableJson $data) => $data
65+
->count(3)
66+
->has('0.title', 'My first post')
67+
->etc()
68+
);
69+
```
70+
71+
### Filtering the list
72+
73+
You can use `withFilter()` to apply filters to the list before calling `get()` or a command.
74+
75+
```php
76+
$this->sharpList(Post::class)
77+
->withFilter(CategoryFilter::class, 1)
78+
->get()
79+
->assertOk();
80+
```
81+
82+
### Entity Commands
83+
84+
You can call an Entity Command directly from the list:
85+
86+
```php
87+
$this->sharpList(Post::class)
88+
->entityCommand(ExportPosts::class)
89+
->post()
90+
->assertOk()
91+
->assertReturnsDownload('posts.csv');
92+
```
93+
94+
If the command has a form, you can test it:
95+
96+
```php
97+
$this->sharpList(Post::class)
98+
->entityCommand(ExportPosts::class)
99+
->getForm()
100+
->assertFormData(fn (AssertableJson $data) => $data
101+
->where('format', 'xls')
102+
->etc()
103+
)
104+
->post(['format' => 'csv'])
105+
->assertOk();
106+
```
107+
108+
### Instance Commands
109+
110+
Similarly, you can call an Instance Command:
111+
112+
```php
113+
$this->sharpList(Post::class)
114+
->instanceCommand(PublishPost::class, 1)
115+
->post()
116+
->assertOk()
117+
->assertReturnsReload();
118+
```
119+
120+
### Multi-step Commands (Wizards)
121+
122+
For commands that have multiple steps, you can use `getNextStepForm()`:
123+
124+
```php
125+
$this->sharpList(Post::class)
126+
->entityCommand(MyWizardCommand::class)
127+
->getForm()
128+
->post(['step1_data' => 'value'])
129+
->assertReturnsStep('step2')
130+
->getNextStepForm()
131+
->assertFormData(fn (AssertableJson $data) => $data
132+
->where('step2_field', 'default')
133+
->etc()
134+
)
135+
->post(['step2_data' => 'value'])
136+
->assertOk();
137+
```
138+
139+
## Testing Show Pages
140+
141+
Use `sharpShow()` to test your Show Pages.
142+
143+
### `sharpShow(string $entityKey, $instanceId)`
144+
145+
Starts a fluent interaction with a Show Page.
146+
147+
```php
148+
$this->sharpShow(Post::class, 1)
149+
->get()
150+
->assertOk()
151+
->assertShowData(fn (AssertableJson $data) => $data
152+
->where('title', 'My first post')
153+
->where('author', 'John Doe')
154+
->etc()
155+
);
156+
```
157+
158+
### Instance Commands from Show
159+
160+
```php
161+
$this->sharpShow(Post::class, 1)
162+
->instanceCommand(PublishPost::class)
163+
->post()
164+
->assertOk();
165+
```
166+
167+
### List & dashboard fields
168+
169+
Show Pages can contain embedded Entity Lists or Dashboards. You can test them using `sharpListField()` and `sharpDashboardField()`.
170+
171+
#### `sharpListField(string $entityKey)`
172+
173+
```php
174+
$this->sharpShow(Post::class, 1)
175+
->sharpListField(Comment::class)
176+
->get()
177+
->assertOk()
178+
->assertListData(fn (AssertableJson $data) => $data
179+
->count(5)
180+
);
181+
```
182+
183+
#### `sharpDashboardField(string $entityKey)`
184+
185+
```php
186+
$this->sharpShow(User::class, 1)
187+
->sharpDashboardField(UserStatsDashboard::class)
188+
->get()
189+
->assertOk();
190+
```
191+
192+
### Nested shows
193+
194+
There are some cases where you have nested shows by navigating through Show List fields. You can chain `sharpShow()` calls to simulate the correct breadcrumb :
195+
196+
```php
197+
$this->sharpList(Post::class)
198+
->sharpShow(Post::class, 1)
199+
->sharpListField(Comment::class)
200+
->sharpShow(Comment::class, 1)
201+
->assertOk();
202+
```
203+
204+
## Testing Forms
205+
206+
Use `sharpForm()` to test your Forms.
207+
208+
### `sharpForm(string $entityKey, $instanceId = null)`
209+
210+
Starts a fluent interaction with a Form. If `$instanceId` is provided, it targets an edit form; otherwise, it targets a creation form.
211+
212+
### Creating and Updating
213+
214+
```php
215+
// Create
216+
$this->sharpForm(Post::class)
217+
->store(['title' => 'New Post'])
218+
->assertValid()
219+
->assertRedirect();
220+
221+
// Update
222+
$this->sharpForm(Post::class, 1)
223+
->update(['title' => 'Updated Post'])
224+
->assertValid()
225+
->assertRedirect();
226+
```
227+
228+
### Testing the "Creation" or "Edit" request itself
229+
230+
If you want to test that the form displays correctly:
231+
232+
```php
233+
$this->sharpForm(Post::class, 1)
234+
->edit()
235+
->assertOk()
236+
->assertFormData(fn (AssertableJson $data) => $data
237+
->where('title', 'Existing Post')
238+
->etc()
239+
);
240+
```
241+
242+
From an `AssertableForm` (the result of `edit()` or `create()`), you can also call `update()` or `store()`:
243+
244+
```php
245+
$this->sharpForm(Post::class, 1)
246+
->edit()
247+
->update(['title' => 'New title'])
248+
->assertValid();
249+
```
250+
251+
## Testing Dashboards
252+
253+
Use `sharpDashboard()` to test your Dashboards.
254+
255+
### `sharpDashboard(string $entityKey)`
256+
257+
Starts a fluent interaction with a Dashboard.
258+
259+
```php
260+
$this->sharpDashboard(MyDashboard::class)
261+
->get()
262+
->assertOk();
263+
```
264+
265+
### Filtering the dashboard
266+
267+
```php
268+
$this->sharpDashboard(MyDashboard::class)
269+
->withFilter(PeriodFilter::class, ['start' => '2023-01-01', 'end' => '2023-01-31'])
270+
->get()
271+
->assertOk();
272+
```
273+
274+
### Dashboard Commands
275+
276+
```php
277+
$this->sharpDashboard(MyDashboard::class)
278+
->dashboardCommand(RefreshStats::class)
279+
->post()
280+
->assertOk();
281+
```

docs/guide/upgrading/9.0.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ All assertions, like for instance `assertSharpHasAuthorization`, were removed be
404404

405405
This means you also need to remove all `$this->initSharpAssertions()` calls from your tests.
406406

407-
Of course, the test helpers remain available, see the dedicated [testing documentation](../testing-with-sharp.md).
407+
Of course, the test helpers remain available, see the dedicated [testing documentation](../testing.md).
408408

409409
Also take note that the `withSharpCurrentBreadcrumb()` method is now deprecated, in favor of the new `withSharpBreadcrumb()` method also documented in the section linked above.
410410

ide.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,17 @@
1414
"parameters": [
1515
1
1616
]
17+
},
18+
{
19+
"classFqn": [
20+
"Code16\\Sharp\\Utils\\Testing\\Commands\\AssertableCommand"
21+
],
22+
"methodNames": [
23+
"assertReturnsView"
24+
],
25+
"parameters": [
26+
1
27+
]
1728
}
1829
]
1930
},

src/Data/Commands/CommandFormData.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public function __construct(
2323
/** @var array<string> */
2424
public ?array $locales = null,
2525
public ?PageAlertData $pageAlert = null,
26+
public ?array $_rawData = null,
2627
) {}
2728

2829
public static function from(array $form): self
@@ -36,6 +37,7 @@ public static function from(array $form): self
3637
layout: FormLayoutData::optional($form['layout'] ?? null),
3738
locales: $form['locales'] ?? null,
3839
pageAlert: PageAlertData::optional($form['pageAlert']),
40+
_rawData: $form['_rawData'] ?? null,
3941
);
4042
}
4143
}

src/Http/Controllers/Api/Commands/HandlesCommandForm.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ protected function getCommandForm(InstanceCommand|EntityCommand|DashboardCommand
3333
'locales' => $commandHandler->hasDataLocalizations()
3434
? $commandHandler->getDataLocalizations()
3535
: null,
36+
...app()->environment('testing') ? [
37+
'_rawData' => $formData,
38+
] : [],
3639
];
3740
}
3841
}

src/Http/Controllers/FormController.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ public function create(string $globalFilter, string $parentUri, EntityKey $entit
3737
$form->buildFormConfig();
3838
$formData = $form->newInstance();
3939

40+
if (app()->environment('testing')) {
41+
Inertia::share('_rawData', $formData);
42+
}
43+
4044
return Inertia::render('Form/Form', [
4145
'form' => FormData::from([
4246
...$this->buildFormData($form, $formData, $entityKey),
@@ -90,6 +94,10 @@ public function edit(string $globalFilter, string $parentUri, EntityKey $entityK
9094
->breadcrumb()
9195
->getParentShowCachedBreadcrumbLabel() ?: $entity->getLabelOrFail($entityKey->multiformKey());
9296

97+
if (app()->environment('testing')) {
98+
Inertia::share('_rawData', $formData);
99+
}
100+
93101
return Inertia::render('Form/Form', [
94102
'form' => FormData::from([
95103
...$this->buildFormData($form, $formData, $entityKey, $instanceId),

src/Http/Controllers/ShowController.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ public function show(string $globalFilter, string $parentUri, EntityKey $entityK
5757

5858
$this->addPreloadHeadersForShowEntityLists($payload);
5959

60+
if (app()->environment('testing')) {
61+
Inertia::share('_rawData', $showData);
62+
}
63+
6064
return Inertia::render('Show/Show', [
6165
'show' => $payload,
6266
'breadcrumb' => BreadcrumbData::from([

src/Http/Controllers/SingleShowController.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ public function show(string $globalFilter, EntityKey $entityKey)
5353

5454
$this->addPreloadHeadersForShowEntityLists($payload);
5555

56+
if (app()->environment('testing')) {
57+
Inertia::share('_rawData', $showData);
58+
}
59+
5660
return Inertia::render('Show/Show', [
5761
'show' => $payload,
5862
'breadcrumb' => BreadcrumbData::from([

0 commit comments

Comments
 (0)