+ */
+ public function definition(): array
+ {
+ return [
+ {{ definition }}
+ ];
+ }
+}
diff --git a/stubs/filters/Filters.stub b/stubs/filters/Filters.stub
new file mode 100644
index 0000000..0191974
--- /dev/null
+++ b/stubs/filters/Filters.stub
@@ -0,0 +1,28 @@
+string('email')->index();
- $table->string('token');
- $table->timestamp('created_at')->nullable();
- });
- }
-
- /**
- * Reverse the migrations.
- *
- * @return void
- */
- public function down()
- {
- Schema::dropIfExists('{{singularSnake}}_password_reset_tokens');
- }
-};
diff --git a/stubs/model/Model.stub b/stubs/model/Model.stub
new file mode 100644
index 0000000..b21a088
--- /dev/null
+++ b/stubs/model/Model.stub
@@ -0,0 +1,21 @@
+
+ */
+ protected $fillable = [
+ {{ fillable }}
+ ];
+
+ {{ body }}
+}
diff --git a/stubs/policy/Policy.stub b/stubs/policy/Policy.stub
new file mode 100644
index 0000000..2c22d18
--- /dev/null
+++ b/stubs/policy/Policy.stub
@@ -0,0 +1,17 @@
+
+ */
+ public function toArray(Request $request): array
+ {
+ return [
+ {{ attributes }}
+ ];
+ }
+}
diff --git a/stubs/routes/_admin.stub b/stubs/routes/_admin.stub
deleted file mode 100644
index c90aaca..0000000
--- a/stubs/routes/_admin.stub
+++ /dev/null
@@ -1,5 +0,0 @@
- /**
- * {{pluralTitle}}
- */
- Route::match(['PUT', 'PATCH'], '{{pluralKebab}}', [{{pluralStudly}}Controller::class, 'bulk'])->name('{{pluralKebab}}.bulk');
- Route::resource('{{pluralKebab}}', {{pluralStudly}}Controller::class);
diff --git a/stubs/routes/_adminSoftDeletes.stub b/stubs/routes/_adminSoftDeletes.stub
deleted file mode 100644
index e3f4fc3..0000000
--- a/stubs/routes/_adminSoftDeletes.stub
+++ /dev/null
@@ -1,8 +0,0 @@
- /**
- * {{pluralTitle}}
- */
- Route::match(['PUT', 'PATCH'], '{{pluralKebab}}', [{{pluralStudly}}Controller::class, 'bulk'])->name('{{pluralKebab}}.bulk');
- Route::get('{{pluralKebab}}/trash', [{{pluralStudly}}Controller::class, 'trash'])->name('{{pluralKebab}}.trash');
- Route::post('{{pluralKebab}}/{{{singularSnake}}}/restore', [{{pluralStudly}}Controller::class, 'restore'])->name('{{pluralKebab}}.restore')->withTrashed();
- Route::delete('{{pluralKebab}}/{{{singularSnake}}}/force-delete', [{{pluralStudly}}Controller::class, 'forceDelete'])->name('{{pluralKebab}}.force-delete')->withTrashed();
- Route::resource('{{pluralKebab}}', {{pluralStudly}}Controller::class);
diff --git a/stubs/routes/_api.stub b/stubs/routes/_api.stub
deleted file mode 100644
index f7b223c..0000000
--- a/stubs/routes/_api.stub
+++ /dev/null
@@ -1,5 +0,0 @@
- /**
- * {{pluralTitle}}
- */
- Route::get('{{pluralKebab}}', [{{pluralStudly}}Controller::class, 'index'])->name('{{pluralKebab}}.index');
- Route::get('{{pluralKebab}}/{id}', [{{pluralStudly}}Controller::class, 'show'])->name('{{pluralKebab}}.show');
diff --git a/stubs/routes/admin.stub b/stubs/routes/admin.stub
new file mode 100644
index 0000000..5a45aff
--- /dev/null
+++ b/stubs/routes/admin.stub
@@ -0,0 +1,20 @@
+prefix('{{ prefix }}')
+ ->name('{{ name }}')
+ ->group(function () {
+ // generators: routes
+ });
diff --git a/stubs/seeders/PermissionsSeeder.stub b/stubs/seeders/PermissionsSeeder.stub
deleted file mode 100644
index 164dd3a..0000000
--- a/stubs/seeders/PermissionsSeeder.stub
+++ /dev/null
@@ -1,26 +0,0 @@
-data as $model => $permissions) {
- foreach ($permissions as $name => $desc) {
- $permission = Permission::firstOrCreate(['name' => $name, 'guard_name' => 'web_admin']);
- $permission->update(['description' => $desc, 'model' => $model]);
- $permission->save();
- }
- }
- }
-}
diff --git a/stubs/seeders/_authPermissions.stub b/stubs/seeders/_authPermissions.stub
deleted file mode 100644
index a14d25d..0000000
--- a/stubs/seeders/_authPermissions.stub
+++ /dev/null
@@ -1 +0,0 @@
- 'approve_{{pluralSnake}}' => 'Approve {{pluralLower}}',
diff --git a/stubs/seeders/_permissions.stub b/stubs/seeders/_permissions.stub
deleted file mode 100644
index 457967d..0000000
--- a/stubs/seeders/_permissions.stub
+++ /dev/null
@@ -1,9 +0,0 @@
- /**
- * {{pluralTitle}} permissions
- */
- '{{pluralSnake}}' => [
- 'view_{{pluralSnake}}' => 'View {{pluralLower}}',
- 'edit_{{pluralSnake}}' => 'Edit {{pluralLower}}',
- 'delete_{{pluralSnake}}' => 'Delete {{pluralLower}}',
-// additional permissions
- ],
diff --git a/stubs/seeders/_permissionsSoftDeletes.stub b/stubs/seeders/_permissionsSoftDeletes.stub
deleted file mode 100644
index e516d1a..0000000
--- a/stubs/seeders/_permissionsSoftDeletes.stub
+++ /dev/null
@@ -1,10 +0,0 @@
- /**
- * {{pluralTitle}} permissions
- */
- '{{pluralSnake}}' => [
- 'view_{{pluralSnake}}' => 'View {{pluralLower}}',
- 'edit_{{pluralSnake}}' => 'Edit {{pluralLower}}',
- 'delete_{{pluralSnake}}' => 'Delete {{pluralLower}}',
- 'force_delete_{{pluralSnake}}' => 'Permanently delete {{pluralLower}}',
-// additional permissions
- ],
diff --git a/stubs/test/ControllerTest.stub b/stubs/test/ControllerTest.stub
new file mode 100644
index 0000000..b6cff18
--- /dev/null
+++ b/stubs/test/ControllerTest.stub
@@ -0,0 +1,37 @@
+artisan('permissions:sync');
+ {{ setUp }}
+ }
+
+ {{ tests }}
+
+ /**
+ * Log in as a user of the admin area with these permissions.
+ */
+ protected function actingAsAdmin(string ...$permissions): {{ user }}
+ {
+ $user = {{ user }}::factory()->create();
+
+ if ($permissions) {
+ $user->givePermissionTo($permissions);
+ }
+
+ $this->actingAs($user{{ guard }});
+
+ return $user;
+ }
+}
diff --git a/stubs/tests/ModelApiControllerTest.stub b/stubs/tests/ModelApiControllerTest.stub
deleted file mode 100644
index 3275fb4..0000000
--- a/stubs/tests/ModelApiControllerTest.stub
+++ /dev/null
@@ -1,78 +0,0 @@
-withoutExceptionHandling();
-
- $access_cookie = $this->getOAuthCookie(null);
-
- ${{pluralSnake}} = $this->getFactory({{singularStudly}}::class)->count(10)->create();
-
- $this->jsonApi('get', '/api/v1/{{pluralKebab}}', access_cookie: $access_cookie)
- ->assertSuccessful()
- ->assertJsonFragment([
- '{{nameField}}' => ${{pluralSnake}}->first()->{{nameField}},
- ]);
-
- }
-
- #[Test]
- public function it_can_show_api_{{pluralSnake}}_for_public_users(): void
- {
- $this->withoutExceptionHandling();
-
- $access_cookie = $this->getOAuthCookie(null);
-
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->create();
-
- $this->jsonApi('get', "/api/v1/{{pluralKebab}}/{${{singularSnake}}->{{keyName}}}", access_cookie: $access_cookie)
- ->assertSuccessful()
- ->assertJsonFragment([
- '{{nameField}}' => ${{singularSnake}}->{{nameField}},
- ]);
- }
-
- #[Test]
- public function it_can_list_api_{{pluralSnake}}_for_admin_users(): void
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsApiAdmin('view_{{pluralSnake}}');
-
- ${{pluralSnake}} = $this->getFactory({{singularStudly}}::class)->count(10)->create();
-
- $this->getJson('/api/v1/{{pluralKebab}}')
- ->assertSuccessful()
- ->assertJsonFragment([
- '{{nameField}}' => ${{pluralSnake}}->first()->{{nameField}},
- ]);
-
- }
-
- #[Test]
- public function it_can_show_api_{{pluralSnake}}_for_admin_users(): void
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsApiAdmin('view_{{pluralSnake}}');
-
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->create();
-
- $this->getJson("/api/v1/{{pluralKebab}}/{${{singularSnake}}->{{keyName}}}")
- ->assertSuccessful()
- ->assertJsonFragment([
- '{{nameField}}' => ${{singularSnake}}->{{nameField}},
- ]);
- }
-}
diff --git a/stubs/tests/ModelControllerTest.stub b/stubs/tests/ModelControllerTest.stub
deleted file mode 100644
index 47e58f8..0000000
--- a/stubs/tests/ModelControllerTest.stub
+++ /dev/null
@@ -1,358 +0,0 @@
-withoutExceptionHandling();
-
- $this->actingAsAdmin('view_{{pluralSnake}}');
-
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->create();
-
- $this->adminGet('/{{pluralKebab}}')
- ->assertStatus(200)
- ->assertSee(${{singularSnake}}->{{nameField}});
- }
-
- #[Test]
- public function it_can_filter_{{pluralSnake}}()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('view_{{pluralSnake}}');
-
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->create();
-
- $this->adminGet(add_query_arg([
- 'search' => ${{singularSnake}}->{{nameField}},
- 'date_field' => 'created_at',
- 'date_from' => now()->subDay()->toDateTimeString(),
- 'date_to' => now()->addDay()->toDateTimeString(),
- ], '/{{pluralKebab}}'))
- ->assertStatus(200)
- ->assertSee(${{singularSnake}}->{{nameField}});
- }
-
- #[Test]
- public function it_can_export_{{pluralSnake}}()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('view_{{pluralSnake}}');
-
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->create();
-
- Excel::fake();
-
- $this->adminGet(add_query_arg(['download' => 1], '/{{pluralKebab}}'))
- ->assertSuccessful();
-
- Excel::assertDownloaded('{{pluralKebab}}.xlsx', function ({{pluralStudly}}Export $export) {
- return $export->modelClass() == {{singularStudly}}::class;
- });
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_list_{{pluralSnake}}()
- {
- $this->actingAsAdmin();
-
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->create();
-
- $this->adminGet('/{{pluralKebab}}')
- ->assertStatus(403)
- ->assertDontSee(${{singularSnake}}->{{nameField}});
- }
-
- #[Test]
- public function it_can_show_the_create_new_{{singularSnake}}_page()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('edit_{{pluralSnake}}');
-
- $this->adminGet('/{{pluralKebab}}/create')
- ->assertStatus(200)
- ->assertSee('New {{singularTitle}}');
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_see_the_create_new_{{singularSnake}}_page()
- {
- $this->actingAsAdmin();
-
- $this->adminGet('/{{pluralKebab}}/create')
- ->assertStatus(403)
- ->assertDontSee('New {{singularTitle}}');
- }
-
- #[Test]
- public function it_can_create_a_new_{{singularSnake}}()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('edit_{{pluralSnake}}');
-
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->make();
-
- $this->adminPost('/{{pluralKebab}}', [
- // factory inputs
- ])
- ->assertRedirect()
- ->assertSessionMissing('errors');
-
- $this->assertDatabaseHas('{{tableName}}', [
- // factory db values
- ]);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_add_a_{{singularSnake}}()
- {
- $this->actingAsAdmin();
-
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->make();
-
- $this->adminPost('/{{pluralKebab}}', [
- // factory inputs
- ])
- ->assertSessionMissing('errors')
- ->assertStatus(403);
-
- $this->assertDatabaseMissing('{{tableName}}', [
- // factory db values
- ]);
- }
-
- #[Test]
- public function it_can_show_the_edit_{{singularSnake}}_page()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('edit_{{pluralSnake}}');
-
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->create();
-
- $this->adminGet("/{{pluralKebab}}/{${{singularSnake}}->{{keyName}}}/edit")
- ->assertStatus(200)
- ->assertSee(${{singularSnake}}->{{nameField}});
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_see_the_edit_{{singularSnake}}_page()
- {
- $this->actingAsAdmin();
-
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->create();
-
- $this->adminGet("/{{pluralKebab}}/{${{singularSnake}}->{{keyName}}}/edit")
- ->assertStatus(403)
- ->assertDontSee(${{singularSnake}}->{{nameField}});
- }
-
- #[Test]
- public function it_can_update_a_{{singularSnake}}()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('edit_{{pluralSnake}}');
-
- // old foreign key models
- ${{singularSnake}} = {{singularStudly}}::factory()->create([
- // correct factory values
- ]);
-
- // new foreign key models
- $this->adminPatch("/{{pluralKebab}}/{${{singularSnake}}->{{keyName}}}", [
- // different correct inputs
- ])
- ->assertRedirect()
- ->assertSessionMissing('errors');
-
- $this->assertDatabaseHas('{{tableName}}', [
- '{{keyName}}' => ${{singularSnake}}->{{keyName}},
- // different correct db values
- ]);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_update_a_{{singularSnake}}()
- {
- $this->actingAsAdmin();
-
- // old foreign key models
- ${{singularSnake}} = {{singularStudly}}::factory()->create([
- // correct factory values
- ]);
-
- // new foreign key models
- $this->adminPatch("/{{pluralKebab}}/{${{singularSnake}}->{{keyName}}}", [
- // different correct inputs
- ])
- ->assertSessionMissing('errors')
- ->assertStatus(403);
-
- $this->assertDatabaseHas('{{tableName}}', [
- '{{keyName}}' => ${{singularSnake}}->{{keyName}},
- // correct db values
- ]);
- }
-
- #[Test]
- public function it_can_show_a_{{singularSnake}}()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('view_{{pluralSnake}}');
-
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->create();
-
- $this->adminGet("/{{pluralKebab}}/{${{singularSnake}}->{{keyName}}}")
- ->assertStatus(200)
- ->assertSee(${{singularSnake}}->{{nameField}});
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_view_a_{{singularSnake}}()
- {
- $this->actingAsAdmin();
-
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->create();
-
- $this->adminGet("/{{pluralKebab}}/{${{singularSnake}}->{{keyName}}}")
- ->assertStatus(403)
- ->assertDontSee(${{singularSnake}}->{{nameField}});
- }
-
- #[Test]
- public function it_can_delete_a_{{singularSnake}}()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('delete_{{pluralSnake}}');
-
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->create();
-
- $this->assertDatabaseHas('{{tableName}}', [
- '{{keyName}}' => ${{singularSnake}}->{{keyName}},
- ]);
-
- $this->adminDelete("/{{pluralKebab}}/{${{singularSnake}}->{{keyName}}}")
- ->assertRedirect();
-
- $this->assertDatabaseMissing('{{tableName}}', [
- '{{keyName}}' => ${{singularSnake}}->{{keyName}},
- ]);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_delete_a_{{singularSnake}}()
- {
- $this->actingAsAdmin();
-
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->create();
-
- $this->assertDatabaseHas('{{tableName}}', [
- '{{keyName}}' => ${{singularSnake}}->{{keyName}},
- ]);
-
- $this->adminDelete("/{{pluralKebab}}/{${{singularSnake}}->{{keyName}}}")
- ->assertStatus(403);
-
- $this->assertDatabaseHas('{{tableName}}', [
- '{{keyName}}' => ${{singularSnake}}->{{keyName}},
- ]);
- }
-
- #[Test]
- public function it_can_bulk_delete_{{pluralSnake}}()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin(['view_{{pluralSnake}}', 'delete_{{pluralSnake}}']);
-
- ${{pluralSnake}} = $this->getFactory({{singularStudly}}::class)->count(3)->create();
-
- ${{pluralSnake}} = ${{pluralSnake}}->map(function (${{singularSnake}}) {
- return collect(${{singularSnake}}->toArray())
- ->only('{{keyName}}')
- ->all();
- });
-
- ${{pluralSnake}}->each(function (${{singularSnake}}) {
- $this->assertDatabaseHas('{{tableName}}', ${{singularSnake}});
- });
-
- ${{pluralSnake}}_to_delete = ${{pluralSnake}}->slice(1);
-
- $this->adminPatch('/{{pluralKebab}}', [
- '{{pluralSnake}}' => ${{pluralSnake}}_to_delete->pluck('{{keyName}}')->all(),
- 'action' => 'delete',
- ])
- ->assertRedirect()
- ->assertSessionMissing('errors');
-
- ${{pluralSnake}}_to_delete->each(function (${{singularSnake}}) {
- $this->assertDatabaseMissing('{{tableName}}', ${{singularSnake}});
- });
- }
-
- #[Test]
- public function it_does_not_allow_unauthorized_users_to_bulk_delete_{{pluralSnake}}()
- {
- $this->actingAsAdmin('view_{{pluralSnake}}');
-
- ${{pluralSnake}} = $this->getFactory({{singularStudly}}::class)->count(3)->create();
-
- ${{pluralSnake}} = ${{pluralSnake}}->map(function (${{singularSnake}}) {
- return collect(${{singularSnake}}->toArray())
- ->only('{{keyName}}')
- ->all();
- });
-
- ${{pluralSnake}}->each(function (${{singularSnake}}) {
- $this->assertDatabaseHas('{{tableName}}', ${{singularSnake}});
- });
-
- ${{pluralSnake}}_to_delete = ${{pluralSnake}}->slice(1);
-
- $this->adminPatch('/{{pluralKebab}}', [
- '{{pluralSnake}}' => ${{pluralSnake}}_to_delete->pluck('{{keyName}}')->all(),
- 'action' => 'delete',
- ])
- ->assertStatus(403)
- ->assertSessionMissing('errors');
-
- ${{pluralSnake}}_to_delete->each(function (${{singularSnake}}) {
- $this->assertDatabaseHas('{{tableName}}', ${{singularSnake}});
- });
- }
-
- #[Test]
- public function it_can_validate_{{singularSnake}}_inputs()
- {
- $this->actingAsAdmin('edit_{{pluralSnake}}');
-
- $this->adminPost('/{{pluralKebab}}', [
- // wrong inputs
- ])
- ->assertRedirect()
- ->assertSessionHasErrors([
- // errors
- ]);
-
- $this->assertDatabaseMissing('{{tableName}}', [
- // wrong db values
- ]);
- }
-}
diff --git a/stubs/tests/ModelSoftDeletesControllerTest.stub b/stubs/tests/ModelSoftDeletesControllerTest.stub
deleted file mode 100644
index 124d643..0000000
--- a/stubs/tests/ModelSoftDeletesControllerTest.stub
+++ /dev/null
@@ -1,485 +0,0 @@
-withoutExceptionHandling();
-
- $this->actingAsAdmin('view_{{pluralSnake}}');
-
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->create();
-
- $this->adminGet('/{{pluralKebab}}')
- ->assertStatus(200)
- ->assertSee(${{singularSnake}}->{{nameField}});
- }
-
- #[Test]
- public function it_can_filter_{{pluralSnake}}()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('view_{{pluralSnake}}');
-
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->create();
-
- $this->adminGet(add_query_arg([
- 'search' => ${{singularSnake}}->{{nameField}},
- 'date_field' => 'created_at',
- 'date_from' => now()->subDay()->toDateTimeString(),
- 'date_to' => now()->addDay()->toDateTimeString(),
- ], '/{{pluralKebab}}'))
- ->assertStatus(200)
- ->assertSee(${{singularSnake}}->{{nameField}});
- }
-
- #[Test]
- public function it_can_export_{{pluralSnake}}()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('view_{{pluralSnake}}');
-
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->create();
-
- Excel::fake();
-
- $this->adminGet(add_query_arg(['download' => 1], '/{{pluralKebab}}'))
- ->assertSuccessful();
-
- Excel::assertDownloaded('{{pluralKebab}}.xlsx', function ({{pluralStudly}}Export $export) {
- return $export->modelClass() == {{singularStudly}}::class;
- });
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_list_{{pluralSnake}}()
- {
- $this->actingAsAdmin();
-
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->create();
-
- $this->adminGet('/{{pluralKebab}}')
- ->assertStatus(403)
- ->assertDontSee(${{singularSnake}}->{{nameField}});
- }
-
- #[Test]
- public function it_can_show_the_create_new_{{singularSnake}}_page()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('edit_{{pluralSnake}}');
-
- $this->adminGet('/{{pluralKebab}}/create')
- ->assertStatus(200)
- ->assertSee('New {{singularTitle}}');
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_see_the_create_new_{{singularSnake}}_page()
- {
- $this->actingAsAdmin();
-
- $this->adminGet('/{{pluralKebab}}/create')
- ->assertStatus(403)
- ->assertDontSee('New {{singularTitle}}');
- }
-
- #[Test]
- public function it_can_create_a_new_{{singularSnake}}()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('edit_{{pluralSnake}}');
-
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->make();
-
- $this->adminPost('/{{pluralKebab}}', [
- // factory inputs
- ])
- ->assertRedirect()
- ->assertSessionMissing('errors');
-
- $this->assertDatabaseHas('{{tableName}}', [
- // factory db values
- ]);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_add_a_{{singularSnake}}()
- {
- $this->actingAsAdmin();
-
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->make();
-
- $this->adminPost('/{{pluralKebab}}', [
- // factory inputs
- ])
- ->assertSessionMissing('errors')
- ->assertStatus(403);
-
- $this->assertDatabaseMissing('{{tableName}}', [
- // factory db values
- ]);
- }
-
- #[Test]
- public function it_can_show_the_edit_{{singularSnake}}_page()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('edit_{{pluralSnake}}');
-
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->create();
-
- $this->adminGet("/{{pluralKebab}}/{${{singularSnake}}->{{keyName}}}/edit")
- ->assertStatus(200)
- ->assertSee(${{singularSnake}}->{{nameField}});
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_see_the_edit_{{singularSnake}}_page()
- {
- $this->actingAsAdmin();
-
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->create();
-
- $this->adminGet("/{{pluralKebab}}/{${{singularSnake}}->{{keyName}}}/edit")
- ->assertStatus(403)
- ->assertDontSee(${{singularSnake}}->{{nameField}});
- }
-
- #[Test]
- public function it_can_update_a_{{singularSnake}}()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('edit_{{pluralSnake}}');
-
- // old foreign key models
- ${{singularSnake}} = {{singularStudly}}::factory()->create([
- // correct factory values
- ]);
-
- // new foreign key models
- $this->adminPatch("/{{pluralKebab}}/{${{singularSnake}}->{{keyName}}}", [
- // different correct inputs
- ])
- ->assertRedirect()
- ->assertSessionMissing('errors');
-
- $this->assertDatabaseHas('{{tableName}}', [
- '{{keyName}}' => ${{singularSnake}}->{{keyName}},
- // different correct db values
- ]);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_update_a_{{singularSnake}}()
- {
- $this->actingAsAdmin();
-
- // old foreign key models
- ${{singularSnake}} = {{singularStudly}}::factory()->create([
- // correct factory values
- ]);
-
- // new foreign key models
- $this->adminPatch("/{{pluralKebab}}/{${{singularSnake}}->{{keyName}}}", [
- // different correct inputs
- ])
- ->assertSessionMissing('errors')
- ->assertStatus(403);
-
- $this->assertDatabaseHas('{{tableName}}', [
- '{{keyName}}' => ${{singularSnake}}->{{keyName}},
- // correct db values
- ]);
- }
-
- #[Test]
- public function it_can_show_a_{{singularSnake}}()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('view_{{pluralSnake}}');
-
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->create();
-
- $this->adminGet("/{{pluralKebab}}/{${{singularSnake}}->{{keyName}}}")
- ->assertStatus(200)
- ->assertSee(${{singularSnake}}->{{nameField}});
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_view_a_{{singularSnake}}()
- {
- $this->actingAsAdmin();
-
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->create();
-
- $this->adminGet("/{{pluralKebab}}/{${{singularSnake}}->{{keyName}}}")
- ->assertStatus(403)
- ->assertDontSee(${{singularSnake}}->{{nameField}});
- }
-
- #[Test]
- public function it_can_soft_delete_a_{{singularSnake}}()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('delete_{{pluralSnake}}');
-
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->create();
-
- $this->assertDatabaseHas('{{tableName}}', [
- '{{keyName}}' => ${{singularSnake}}->{{keyName}},
- ]);
-
- $this->adminDelete("/{{pluralKebab}}/{${{singularSnake}}->{{keyName}}}")
- ->assertRedirect();
-
- $this->assertSoftDeleted('{{tableName}}', [
- '{{keyName}}' => ${{singularSnake}}->{{keyName}},
- ]);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_soft_delete_a_{{singularSnake}}()
- {
- $this->actingAsAdmin();
-
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->create();
-
- $this->assertDatabaseHas('{{tableName}}', [
- '{{keyName}}' => ${{singularSnake}}->{{keyName}},
- ]);
-
- $this->adminDelete("/{{pluralKebab}}/{${{singularSnake}}->{{keyName}}}")
- ->assertStatus(403);
-
- $this->assertDatabaseHas('{{tableName}}', [
- '{{keyName}}' => ${{singularSnake}}->{{keyName}},
- ]);
- }
-
- #[Test]
- public function it_can_force_delete_a_{{singularSnake}}()
- {
- $this->withoutExceptionHandling();
-
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->create();
- ${{singularSnake}}->delete();
-
- // sanity check
- $this->assertSoftDeleted('{{tableName}}', [
- '{{keyName}}' => ${{singularSnake}}->{{keyName}},
- ]);
-
- $this->actingAsAdmin('force_delete_{{pluralSnake}}');
-
- $this->adminDelete("/{{pluralKebab}}/{${{singularSnake}}->{{keyName}}}/force-delete")
- ->assertRedirect();
-
- $this->assertDatabaseMissing('{{tableName}}', [
- '{{keyName}}' => ${{singularSnake}}->{{keyName}},
- ]);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_force_delete_a_{{singularSnake}}()
- {
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->create();
- ${{singularSnake}}->delete();
-
- // sanity check
- $this->assertSoftDeleted('{{tableName}}', [
- '{{keyName}}' => ${{singularSnake}}->{{keyName}},
- ]);
-
- $this->actingAsAdmin();
-
- $this->adminDelete("/{{pluralKebab}}/{${{singularSnake}}->{{keyName}}}/force-delete")
- ->assertStatus(403);
-
- $this->assertSoftDeleted('{{tableName}}', [
- '{{keyName}}' => ${{singularSnake}}->{{keyName}},
- ]);
- }
-
- #[Test]
- public function it_can_restore_a_soft_deleted_{{singularSnake}}()
- {
- $this->withoutExceptionHandling();
-
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->create();
- ${{singularSnake}}->delete();
-
- // sanity check
- $this->assertSoftDeleted('{{tableName}}', [
- '{{keyName}}' => ${{singularSnake}}->{{keyName}},
- ]);
-
- $this->actingAsAdmin('delete_{{pluralSnake}}');
-
- $this->adminPost("/{{pluralKebab}}/{${{singularSnake}}->{{keyName}}}/restore")
- ->assertRedirect();
-
- $this->assertDatabaseHas('{{tableName}}', [
- '{{keyName}}' => ${{singularSnake}}->{{keyName}},
- 'deleted_at' => null,
- ]);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_restore_a_{{singularSnake}}()
- {
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->create();
- ${{singularSnake}}->delete();
-
- // sanity check
- $this->assertSoftDeleted('{{tableName}}', [
- '{{keyName}}' => ${{singularSnake}}->{{keyName}},
- ]);
-
- $this->actingAsAdmin();
-
- $this->adminPost("/{{pluralKebab}}/{${{singularSnake}}->{{keyName}}}/restore")
- ->assertStatus(403);
-
- $this->assertSoftDeleted('{{tableName}}', [
- '{{keyName}}' => ${{singularSnake}}->{{keyName}},
- ]);
- }
-
- #[Test]
- public function it_can_list_soft_deleted_{{pluralSnake}}()
- {
- $this->withoutExceptionHandling();
-
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->create();
- ${{singularSnake}}->delete();
-
- // sanity check
- $this->assertSoftDeleted('{{tableName}}', [
- '{{keyName}}' => ${{singularSnake}}->{{keyName}},
- ]);
-
- $this->actingAsAdmin('delete_{{pluralSnake}}');
-
- $this->adminGet("/{{pluralKebab}}/trash")
- ->assertStatus(200)
- ->assertSee(${{singularSnake}}->{{nameField}});
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_list_soft_deleted_{{pluralSnake}}()
- {
- ${{singularSnake}} = $this->getFactory({{singularStudly}}::class)->create();
- ${{singularSnake}}->delete();
-
- // sanity check
- $this->assertSoftDeleted('{{tableName}}', [
- '{{keyName}}' => ${{singularSnake}}->{{keyName}},
- ]);
-
- $this->actingAsAdmin();
-
- $this->adminGet("/{{pluralKebab}}/trash")
- ->assertStatus(403)
- ->assertDontSee(${{singularSnake}}->{{nameField}});
- }
-
- #[Test]
- public function it_can_bulk_soft_delete_{{pluralSnake}}()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin(['view_{{pluralSnake}}', 'delete_{{pluralSnake}}']);
-
- ${{pluralSnake}} = $this->getFactory({{singularStudly}}::class)->count(3)->create();
-
- ${{pluralSnake}} = ${{pluralSnake}}->map(function (${{singularSnake}}) {
- return collect(${{singularSnake}}->toArray())
- ->only('{{keyName}}')
- ->all();
- });
-
- ${{pluralSnake}}->each(function (${{singularSnake}}) {
- $this->assertDatabaseHas('{{tableName}}', ${{singularSnake}});
- });
-
- ${{pluralSnake}}_to_delete = ${{pluralSnake}}->slice(1);
-
- $this->adminPatch('/{{pluralKebab}}', [
- '{{pluralSnake}}' => ${{pluralSnake}}_to_delete->pluck('{{keyName}}')->all(),
- 'action' => 'delete',
- ])
- ->assertRedirect()
- ->assertSessionMissing('errors');
-
- ${{pluralSnake}}_to_delete->each(function (${{singularSnake}}) {
- $this->assertSoftDeleted('{{tableName}}', ${{singularSnake}});
- });
- }
-
- #[Test]
- public function it_does_not_allow_unauthorized_users_to_bulk_soft_delete_{{pluralSnake}}()
- {
- $this->actingAsAdmin('view_{{pluralSnake}}');
-
- ${{pluralSnake}} = $this->getFactory({{singularStudly}}::class)->count(3)->create();
-
- ${{pluralSnake}} = ${{pluralSnake}}->map(function (${{singularSnake}}) {
- return collect(${{singularSnake}}->toArray())
- ->only('{{keyName}}')
- ->all();
- });
-
- ${{pluralSnake}}->each(function (${{singularSnake}}) {
- $this->assertDatabaseHas('{{tableName}}', ${{singularSnake}});
- });
-
- ${{pluralSnake}}_to_delete = ${{pluralSnake}}->slice(1);
-
- $this->adminPatch('/{{pluralKebab}}', [
- '{{pluralSnake}}' => ${{pluralSnake}}_to_delete->pluck('{{keyName}}')->all(),
- 'action' => 'delete',
- ])
- ->assertStatus(403)
- ->assertSessionMissing('errors');
-
- ${{pluralSnake}}_to_delete->each(function (${{singularSnake}}) {
- $this->assertDatabaseHas('{{tableName}}', ${{singularSnake}});
- });
- }
-
- #[Test]
- public function it_can_validate_{{singularSnake}}_inputs()
- {
- $this->actingAsAdmin('edit_{{pluralSnake}}');
-
- $this->adminPost('/{{pluralKebab}}', [
- // wrong inputs
- ])
- ->assertRedirect()
- ->assertSessionHasErrors([
- // errors
- ]);
-
- $this->assertDatabaseMissing('{{tableName}}', [
- // wrong db values
- ]);
- }
-}
diff --git a/stubs/views/blade/create.blade.stub b/stubs/views/blade/create.blade.stub
new file mode 100644
index 0000000..50c6307
--- /dev/null
+++ b/stubs/views/blade/create.blade.stub
@@ -0,0 +1,12 @@
+@extends('{{ layout }}')
+
+@section('title', __('{{ title }}'))
+
+@section('{{ section }}')
+
+ {{ breadcrumbs }}
+
{{ __('{{ title }}') }}
+
+
+ {{ $form }}
+@endsection
diff --git a/stubs/views/blade/edit.blade.stub b/stubs/views/blade/edit.blade.stub
new file mode 100644
index 0000000..81acec3
--- /dev/null
+++ b/stubs/views/blade/edit.blade.stub
@@ -0,0 +1,18 @@
+@extends('{{ layout }}')
+
+@section('title', __('{{ title }}'))
+
+@section('{{ section }}')
+
+
+ {{ breadcrumbs }}
+
{{ __('{{ title }}') }}
+
+
+
+ {{ __('View') }}
+
+
+
+ {{ $form }}
+@endsection
diff --git a/stubs/views/blade/index.blade.stub b/stubs/views/blade/index.blade.stub
new file mode 100644
index 0000000..c5045fb
--- /dev/null
+++ b/stubs/views/blade/index.blade.stub
@@ -0,0 +1,48 @@
+@extends('{{ layout }}')
+
+@section('title', __('{{ title }}'))
+
+@section('{{ section }}')
+
+
+ {{ breadcrumbs }}
+
{{ __('{{ title }}') }}
+
+
+ @can('create', {{ modelClass }}::class)
+
+ {{ __('{{ newLabel }}') }}
+
+ @endcan
+
+
+
+
+
+
+
+
+
+
+ {{ headers }}
+
+
+
+ @forelse(${{ plural }} as ${{ variable }})
+
+ {{ cells }}
+
+ @empty
+
+ {{ $filters->hasActiveFilters() ? __('No {{ pluralLower }} match these filters.') : __('No {{ pluralLower }} yet.') }}
+
+ @endforelse
+
+
+ @if(${{ plural }}->hasPages())
+
+
+
+ @endif
+
+@endsection
diff --git a/stubs/views/blade/show.blade.stub b/stubs/views/blade/show.blade.stub
new file mode 100644
index 0000000..6992b70
--- /dev/null
+++ b/stubs/views/blade/show.blade.stub
@@ -0,0 +1,23 @@
+@extends('{{ layout }}')
+
+@section('title', ${{ variable }}->{{ nameField }})
+
+@section('{{ section }}')
+
+
+ {{ breadcrumbs }}
+
{{ ${{ variable }}->{{ nameField }} }}
+
+
+
+ {{ actions }}
+
+
+
+ {{ trashedAlert }}
+
+
+ {{ entries }}
+
+ {{ media }}
+@endsection
diff --git a/stubs/views/model-soft-deletes/_actions.blade.stub b/stubs/views/model-soft-deletes/_actions.blade.stub
deleted file mode 100644
index 451ac44..0000000
--- a/stubs/views/model-soft-deletes/_actions.blade.stub
+++ /dev/null
@@ -1,48 +0,0 @@
-
diff --git a/stubs/views/model-soft-deletes/_filter.blade.stub b/stubs/views/model-soft-deletes/_filter.blade.stub
deleted file mode 100644
index 900a9a6..0000000
--- a/stubs/views/model-soft-deletes/_filter.blade.stub
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/stubs/views/model-soft-deletes/_list.blade.stub b/stubs/views/model-soft-deletes/_list.blade.stub
deleted file mode 100644
index 976784a..0000000
--- a/stubs/views/model-soft-deletes/_list.blade.stub
+++ /dev/null
@@ -1,48 +0,0 @@
-@foreach(${{pluralSnake}} as ${{singularSnake}})
-
-
-
- {!! ${{singularSnake}}->admin_link !!}
-
-
- // columns
-
-
-@endforeach
diff --git a/stubs/views/model-soft-deletes/index.blade.stub b/stubs/views/model-soft-deletes/index.blade.stub
deleted file mode 100644
index a01d6ae..0000000
--- a/stubs/views/model-soft-deletes/index.blade.stub
+++ /dev/null
@@ -1,36 +0,0 @@
-@extends('admin.{{pluralKebab}}.{{pluralKebab}}')
-
-@section('page-title')
- {{ if_route('admin.{{pluralKebab}}.trash') ? __('Deleted {{pluralTitle}}') : __('{{pluralTitle}}') }}
-@endsection
-
-@section('page-subheading')
- {{ $title }}
-@endsection
-
-@section('content')
- @if(${{pluralSnake}}->isNotEmpty() || App\Models\{{singularStudly}}::exists())
-
-
- @include('admin.{{pluralKebab}}._filter')
-
-
- @include('admin.{{pluralKebab}}._table', [
- 'no_bulk' => $trashed,
- 'no_checkbox' => $trashed,
- ])
-
- @else
-
- @endif
-@endsection
diff --git a/stubs/views/model/_actions.blade.stub b/stubs/views/model/_actions.blade.stub
deleted file mode 100644
index 82adda4..0000000
--- a/stubs/views/model/_actions.blade.stub
+++ /dev/null
@@ -1,41 +0,0 @@
-
diff --git a/stubs/views/model/_bulk.blade.stub b/stubs/views/model/_bulk.blade.stub
deleted file mode 100644
index 0e19612..0000000
--- a/stubs/views/model/_bulk.blade.stub
+++ /dev/null
@@ -1,9 +0,0 @@
-@php
- $actions = [];
-
- if (auth()->user()->can('delete_{{pluralSnake}}')) {
- $actions['delete'] = __('Delete');
- }
-@endphp
-
-
diff --git a/stubs/views/model/_details.blade.stub b/stubs/views/model/_details.blade.stub
deleted file mode 100644
index 8f98965..0000000
--- a/stubs/views/model/_details.blade.stub
+++ /dev/null
@@ -1,3 +0,0 @@
-
- // entries
-
diff --git a/stubs/views/model/_filter.blade.stub b/stubs/views/model/_filter.blade.stub
deleted file mode 100644
index 81a21af..0000000
--- a/stubs/views/model/_filter.blade.stub
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/stubs/views/model/_form.blade.stub b/stubs/views/model/_form.blade.stub
deleted file mode 100644
index 7669015..0000000
--- a/stubs/views/model/_form.blade.stub
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
- {{ __('Save') }}
-
-
-
- {{ __('Cancel') }}
-
-
-
-
-@include('admin.components.floating-submit')
diff --git a/stubs/views/model/_list.blade.stub b/stubs/views/model/_list.blade.stub
deleted file mode 100644
index 4b62e7a..0000000
--- a/stubs/views/model/_list.blade.stub
+++ /dev/null
@@ -1,32 +0,0 @@
-@foreach(${{pluralSnake}} as ${{singularSnake}})
-
-
-
- {!! ${{singularSnake}}->admin_link !!}
-
-
- // columns
-
-
-@endforeach
diff --git a/stubs/views/model/_table.blade.stub b/stubs/views/model/_table.blade.stub
deleted file mode 100644
index 9a8685c..0000000
--- a/stubs/views/model/_table.blade.stub
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
- @if(empty($no_bulk))
-
- @include('admin.{{pluralKebab}}._bulk')
-
- @endif
-
-
-
- // titles
-
-
-
- @if(${{pluralSnake}}->isEmpty())
-
- {{ __('No matching {{pluralLower}} found.') }}
-
- @else
- @include('admin.{{pluralKebab}}._list')
- @endif
-
-
- @if(empty($no_pagination))
-
- {{ ${{pluralSnake}}->links('forms::material-admin-26.pagination') }}
-
- @endif
-
-
diff --git a/stubs/views/model/create.blade.stub b/stubs/views/model/create.blade.stub
deleted file mode 100644
index 874f761..0000000
--- a/stubs/views/model/create.blade.stub
+++ /dev/null
@@ -1,9 +0,0 @@
-@extends('admin.{{pluralKebab}}.{{pluralKebab}}')
-
-@section('page-title', __('New {{singularTitle}}'))
-
-@section('content')
-
- @include('admin.{{pluralKebab}}._form')
-
-@endsection
diff --git a/stubs/views/model/edit.blade.stub b/stubs/views/model/edit.blade.stub
deleted file mode 100644
index a8f5514..0000000
--- a/stubs/views/model/edit.blade.stub
+++ /dev/null
@@ -1,9 +0,0 @@
-@extends('admin.{{pluralKebab}}.{{pluralKebab}}')
-
-@section('page-title', __('Edit {{singularTitle}}'))
-
-@section('content')
-
- @include('admin.{{pluralKebab}}._form')
-
-@endsection
diff --git a/stubs/views/model/index.blade.stub b/stubs/views/model/index.blade.stub
deleted file mode 100644
index 920d817..0000000
--- a/stubs/views/model/index.blade.stub
+++ /dev/null
@@ -1,29 +0,0 @@
-@extends('admin.{{pluralKebab}}.{{pluralKebab}}')
-
-@section('page-subheading')
- {{ $title }}
-@endsection
-
-@section('content')
- @if(${{pluralSnake}}->isNotEmpty() || App\Models\{{singularStudly}}::exists())
-
-
- @include('admin.{{pluralKebab}}._filter')
-
-
- @include('admin.{{pluralKebab}}._table')
-
- @else
-
- @endif
-@endsection
diff --git a/stubs/views/model/model.blade.stub b/stubs/views/model/model.blade.stub
deleted file mode 100644
index bce8094..0000000
--- a/stubs/views/model/model.blade.stub
+++ /dev/null
@@ -1,12 +0,0 @@
-@extends('layouts.admin')
-
-@section('title', '{{pluralTitle}}')
-@section('page-title', __('{{pluralTitle}}'))
-
-@section('top-search')
-
-@endsection
-
-@section('model-actions')
- @include('admin.{{pluralKebab}}._actions')
-@endsection
diff --git a/stubs/views/model/show.blade.stub b/stubs/views/model/show.blade.stub
deleted file mode 100644
index 3f88b34..0000000
--- a/stubs/views/model/show.blade.stub
+++ /dev/null
@@ -1,7 +0,0 @@
-@extends('admin.{{pluralKebab}}.{{pluralKebab}}')
-
-@section('page-title', __('View {{singularTitle}}'))
-
-@section('content')
- @include('admin.{{pluralKebab}}._details')
-@endsection
diff --git a/tests/Concerns/AssertsSnapshots.php b/tests/Concerns/AssertsSnapshots.php
new file mode 100644
index 0000000..cac6bc9
--- /dev/null
+++ b/tests/Concerns/AssertsSnapshots.php
@@ -0,0 +1,29 @@
+fail("The snapshot {$name} is missing: run the tests with GENERATORS_UPDATE_SNAPSHOTS=1.");
+ }
+
+ $this->assertEquals(file_get_contents($path), $actual, "The output differs from the snapshot {$name}.");
+ }
+}
diff --git a/tests/Feature/Commands/Auth/Controllers/GenerateAuthConfirmPasswordControllerCommandTest.php b/tests/Feature/Commands/Auth/Controllers/GenerateAuthConfirmPasswordControllerCommandTest.php
deleted file mode 100644
index 01bd32f..0000000
--- a/tests/Feature/Commands/Auth/Controllers/GenerateAuthConfirmPasswordControllerCommandTest.php
+++ /dev/null
@@ -1,40 +0,0 @@
-getTestStubContents('Controllers/Customer/Auth/ConfirmPasswordController.php');
-
- $this->artisan('generate:auth_confirm_password_controller', ['table' => 'customers'])
- ->expectsOutput($expected_content);
- }
-
- public function test_it_can_generate_auth_confirm_password_controller_file(): void
- {
- $expected_path = $this->app->path('Http/Controllers/Customer/Auth/ConfirmPasswordController.php');
- $expected_content = $this->getTestStubContents('Controllers/Customer/Auth/ConfirmPasswordController.php');
-
- $this->partialMock(Filesystem::class, function (MockInterface $mock) use ($expected_path, $expected_content) {
- $mock->shouldReceive('makeDirectory')
- ->andReturnTrue();
-
- $mock->shouldReceive('put')
- ->once()
- ->with($expected_path, $expected_content)
- ->andReturnTrue();
- });
-
- $this->artisan('generate:auth_confirm_password_controller', ['table' => 'customers', '--create' => true])
- ->assertSuccessful();
- }
-}
diff --git a/tests/Feature/Commands/Auth/Controllers/GenerateAuthControllerCommandTest.php b/tests/Feature/Commands/Auth/Controllers/GenerateAuthControllerCommandTest.php
deleted file mode 100644
index a9c7613..0000000
--- a/tests/Feature/Commands/Auth/Controllers/GenerateAuthControllerCommandTest.php
+++ /dev/null
@@ -1,40 +0,0 @@
-getTestStubContents('Controllers/CustomersController.php');
-
- $this->artisan('generate:auth_controller', ['table' => 'customers'])
- ->expectsOutput($expected_content);
- }
-
- public function test_it_can_generate_auth_controller_file(): void
- {
- $expected_path = $this->app->path('Http/Controllers/Admin/CustomersController.php');
- $expected_content = $this->getTestStubContents('Controllers/CustomersController.php');
-
- $this->partialMock(Filesystem::class, function (MockInterface $mock) use ($expected_path, $expected_content) {
- $mock->shouldReceive('makeDirectory')
- ->andReturnTrue();
-
- $mock->shouldReceive('put')
- ->once()
- ->with($expected_path, $expected_content)
- ->andReturnTrue();
- });
-
- $this->artisan('generate:auth_controller', ['table' => 'customers', '--create' => true])
- ->assertSuccessful();
- }
-}
diff --git a/tests/Feature/Commands/Auth/Controllers/GenerateAuthForgotPasswordControllerCommandTest.php b/tests/Feature/Commands/Auth/Controllers/GenerateAuthForgotPasswordControllerCommandTest.php
deleted file mode 100644
index 4a71afa..0000000
--- a/tests/Feature/Commands/Auth/Controllers/GenerateAuthForgotPasswordControllerCommandTest.php
+++ /dev/null
@@ -1,40 +0,0 @@
-getTestStubContents('Controllers/Customer/Auth/ForgotPasswordController.php');
-
- $this->artisan('generate:auth_forgot_password_controller', ['table' => 'customers'])
- ->expectsOutput($expected_content);
- }
-
- public function test_it_can_generate_auth_forgot_password_controller_file(): void
- {
- $expected_path = $this->app->path('Http/Controllers/Customer/Auth/ForgotPasswordController.php');
- $expected_content = $this->getTestStubContents('Controllers/Customer/Auth/ForgotPasswordController.php');
-
- $this->partialMock(Filesystem::class, function (MockInterface $mock) use ($expected_path, $expected_content) {
- $mock->shouldReceive('makeDirectory')
- ->andReturnTrue();
-
- $mock->shouldReceive('put')
- ->once()
- ->with($expected_path, $expected_content)
- ->andReturnTrue();
- });
-
- $this->artisan('generate:auth_forgot_password_controller', ['table' => 'customers', '--create' => true])
- ->assertSuccessful();
- }
-}
diff --git a/tests/Feature/Commands/Auth/Controllers/GenerateAuthHomeControllerCommandTest.php b/tests/Feature/Commands/Auth/Controllers/GenerateAuthHomeControllerCommandTest.php
deleted file mode 100644
index aac74ee..0000000
--- a/tests/Feature/Commands/Auth/Controllers/GenerateAuthHomeControllerCommandTest.php
+++ /dev/null
@@ -1,40 +0,0 @@
-getTestStubContents('Controllers/Customer/HomeController.php');
-
- $this->artisan('generate:auth_home_controller', ['table' => 'customers'])
- ->expectsOutput($expected_content);
- }
-
- public function test_it_can_generate_auth_home_controller_file(): void
- {
- $expected_path = $this->app->path('Http/Controllers/Customer/HomeController.php');
- $expected_content = $this->getTestStubContents('Controllers/Customer/HomeController.php');
-
- $this->partialMock(Filesystem::class, function (MockInterface $mock) use ($expected_path, $expected_content) {
- $mock->shouldReceive('makeDirectory')
- ->andReturnTrue();
-
- $mock->shouldReceive('put')
- ->once()
- ->with($expected_path, $expected_content)
- ->andReturnTrue();
- });
-
- $this->artisan('generate:auth_home_controller', ['table' => 'customers', '--create' => true])
- ->assertSuccessful();
- }
-}
diff --git a/tests/Feature/Commands/Auth/Controllers/GenerateAuthLoginControllerCommandTest.php b/tests/Feature/Commands/Auth/Controllers/GenerateAuthLoginControllerCommandTest.php
deleted file mode 100644
index 36f8c5d..0000000
--- a/tests/Feature/Commands/Auth/Controllers/GenerateAuthLoginControllerCommandTest.php
+++ /dev/null
@@ -1,40 +0,0 @@
-getTestStubContents('Controllers/Customer/Auth/LoginController.php');
-
- $this->artisan('generate:auth_login_controller', ['table' => 'customers'])
- ->expectsOutput($expected_content);
- }
-
- public function test_it_can_generate_auth_login_controller_file(): void
- {
- $expected_path = $this->app->path('Http/Controllers/Customer/Auth/LoginController.php');
- $expected_content = $this->getTestStubContents('Controllers/Customer/Auth/LoginController.php');
-
- $this->partialMock(Filesystem::class, function (MockInterface $mock) use ($expected_path, $expected_content) {
- $mock->shouldReceive('makeDirectory')
- ->andReturnTrue();
-
- $mock->shouldReceive('put')
- ->once()
- ->with($expected_path, $expected_content)
- ->andReturnTrue();
- });
-
- $this->artisan('generate:auth_login_controller', ['table' => 'customers', '--create' => true])
- ->assertSuccessful();
- }
-}
diff --git a/tests/Feature/Commands/Auth/Controllers/GenerateAuthRegisterControllerCommandTest.php b/tests/Feature/Commands/Auth/Controllers/GenerateAuthRegisterControllerCommandTest.php
deleted file mode 100644
index 0cfb26c..0000000
--- a/tests/Feature/Commands/Auth/Controllers/GenerateAuthRegisterControllerCommandTest.php
+++ /dev/null
@@ -1,40 +0,0 @@
-getTestStubContents('Controllers/Customer/Auth/RegisterController.php');
-
- $this->artisan('generate:auth_register_controller', ['table' => 'customers'])
- ->expectsOutput($expected_content);
- }
-
- public function test_it_can_generate_auth_register_controller_file(): void
- {
- $expected_path = $this->app->path('Http/Controllers/Customer/Auth/RegisterController.php');
- $expected_content = $this->getTestStubContents('Controllers/Customer/Auth/RegisterController.php');
-
- $this->partialMock(Filesystem::class, function (MockInterface $mock) use ($expected_path, $expected_content) {
- $mock->shouldReceive('makeDirectory')
- ->andReturnTrue();
-
- $mock->shouldReceive('put')
- ->once()
- ->with($expected_path, $expected_content)
- ->andReturnTrue();
- });
-
- $this->artisan('generate:auth_register_controller', ['table' => 'customers', '--create' => true])
- ->assertSuccessful();
- }
-}
diff --git a/tests/Feature/Commands/Auth/Controllers/GenerateAuthResetPasswordControllerCommandTest.php b/tests/Feature/Commands/Auth/Controllers/GenerateAuthResetPasswordControllerCommandTest.php
deleted file mode 100644
index 943feb0..0000000
--- a/tests/Feature/Commands/Auth/Controllers/GenerateAuthResetPasswordControllerCommandTest.php
+++ /dev/null
@@ -1,40 +0,0 @@
-getTestStubContents('Controllers/Customer/Auth/ResetPasswordController.php');
-
- $this->artisan('generate:auth_reset_password_controller', ['table' => 'customers'])
- ->expectsOutput($expected_content);
- }
-
- public function test_it_can_generate_auth_reset_password_controller_file(): void
- {
- $expected_path = $this->app->path('Http/Controllers/Customer/Auth/ResetPasswordController.php');
- $expected_content = $this->getTestStubContents('Controllers/Customer/Auth/ResetPasswordController.php');
-
- $this->partialMock(Filesystem::class, function (MockInterface $mock) use ($expected_path, $expected_content) {
- $mock->shouldReceive('makeDirectory')
- ->andReturnTrue();
-
- $mock->shouldReceive('put')
- ->once()
- ->with($expected_path, $expected_content)
- ->andReturnTrue();
- });
-
- $this->artisan('generate:auth_reset_password_controller', ['table' => 'customers', '--create' => true])
- ->assertSuccessful();
- }
-}
diff --git a/tests/Feature/Commands/Auth/Controllers/GenerateAuthUpdatePasswordControllerCommandTest.php b/tests/Feature/Commands/Auth/Controllers/GenerateAuthUpdatePasswordControllerCommandTest.php
deleted file mode 100644
index 880437f..0000000
--- a/tests/Feature/Commands/Auth/Controllers/GenerateAuthUpdatePasswordControllerCommandTest.php
+++ /dev/null
@@ -1,40 +0,0 @@
-getTestStubContents('Controllers/Customer/Auth/UpdatePasswordController.php');
-
- $this->artisan('generate:auth_update_password_controller', ['table' => 'customers'])
- ->expectsOutput($expected_content);
- }
-
- public function test_it_can_generate_auth_update_password_controller_file(): void
- {
- $expected_path = $this->app->path('Http/Controllers/Customer/Auth/UpdatePasswordController.php');
- $expected_content = $this->getTestStubContents('Controllers/Customer/Auth/UpdatePasswordController.php');
-
- $this->partialMock(Filesystem::class, function (MockInterface $mock) use ($expected_path, $expected_content) {
- $mock->shouldReceive('makeDirectory')
- ->andReturnTrue();
-
- $mock->shouldReceive('put')
- ->once()
- ->with($expected_path, $expected_content)
- ->andReturnTrue();
- });
-
- $this->artisan('generate:auth_update_password_controller', ['table' => 'customers', '--create' => true])
- ->assertSuccessful();
- }
-}
diff --git a/tests/Feature/Commands/Auth/Controllers/GenerateAuthVerificationControllerCommandTest.php b/tests/Feature/Commands/Auth/Controllers/GenerateAuthVerificationControllerCommandTest.php
deleted file mode 100644
index 394454d..0000000
--- a/tests/Feature/Commands/Auth/Controllers/GenerateAuthVerificationControllerCommandTest.php
+++ /dev/null
@@ -1,40 +0,0 @@
-getTestStubContents('Controllers/Customer/Auth/VerificationController.php');
-
- $this->artisan('generate:auth_verification_controller', ['table' => 'customers'])
- ->expectsOutput($expected_content);
- }
-
- public function test_it_can_generate_auth_verification_controller_file(): void
- {
- $expected_path = $this->app->path('Http/Controllers/Customer/Auth/VerificationController.php');
- $expected_content = $this->getTestStubContents('Controllers/Customer/Auth/VerificationController.php');
-
- $this->partialMock(Filesystem::class, function (MockInterface $mock) use ($expected_path, $expected_content) {
- $mock->shouldReceive('makeDirectory')
- ->andReturnTrue();
-
- $mock->shouldReceive('put')
- ->once()
- ->with($expected_path, $expected_content)
- ->andReturnTrue();
- });
-
- $this->artisan('generate:auth_verification_controller', ['table' => 'customers', '--create' => true])
- ->assertSuccessful();
- }
-}
diff --git a/tests/Feature/Commands/Auth/GenerateAuthConfigCommandTest.php b/tests/Feature/Commands/Auth/GenerateAuthConfigCommandTest.php
deleted file mode 100644
index ae7556e..0000000
--- a/tests/Feature/Commands/Auth/GenerateAuthConfigCommandTest.php
+++ /dev/null
@@ -1,70 +0,0 @@
-deleteFile($this->app->configPath('auth.php'));
- }
-
- protected function tearDown(): void
- {
- $this->deleteFile($this->app->configPath('auth.php'));
-
- parent::tearDown();
- }
-
- public function test_it_can_generate_auth_config_output(): void
- {
- $expected_content = '';
-
- $expected_content .= "// guards\n";
- $expected_content .= $this->getTestStubContents('config/auth/_guards.stub');
-
- $expected_content .= "// providers\n";
- $expected_content .= $this->getTestStubContents('config/auth/_providers.stub');
-
- $expected_content .= "// passwords\n";
- $expected_content .= $this->getTestStubContents('config/auth/_passwords.stub');
-
- $expected_content .= "// passport-guards\n";
- $expected_content .= $this->getTestStubContents('config/auth/_passport-guards.stub');
-
- $expected_content .= "// default-guard\n";
- $expected_content .= $this->getTestStubContents('config/auth/_default-guard.stub');
-
- $expected_content .= "// default-passwords\n";
- $expected_content .= $this->getTestStubContents('config/auth/_default-passwords.stub');
-
- $this->artisan('generate:auth_config', ['table' => 'customers'])
- ->expectsOutput($expected_content);
- }
-
- public function test_it_can_append_config_to_an_existing_auth_config_file(): void
- {
- $expected_path = $this->app->configPath('auth.php');
-
- $this->copyFile($this->getTestStubPath('config/auth/skeleton-auth.php'), $expected_path);
- $expected_content = $this->getTestStubContents('config/auth/models-auth.php');
-
- $this->artisan('generate:auth_config', ['table' => 'customers', '--create' => true])
- ->assertSuccessful();
-
- $this->artisan('generate:auth_config', ['table' => 'public_users', '--create' => true])
- ->assertSuccessful();
-
- $this->assertFileExists($expected_path);
-
- $actual_content = $this->getGeneratedFileContents($expected_path);
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Feature/Commands/Auth/GenerateAuthFactoryCommandTest.php b/tests/Feature/Commands/Auth/GenerateAuthFactoryCommandTest.php
deleted file mode 100644
index a29e937..0000000
--- a/tests/Feature/Commands/Auth/GenerateAuthFactoryCommandTest.php
+++ /dev/null
@@ -1,40 +0,0 @@
-getTestStubContents('factories/CustomerFactory.php');
-
- $this->artisan('generate:auth_factory', ['table' => 'customers'])
- ->expectsOutput($expected_content);
- }
-
- public function test_it_can_generate_auth_factory_file(): void
- {
- $expected_path = $this->app->databasePath('factories/CustomerFactory.php');
- $expected_content = $this->getTestStubContents('factories/CustomerFactory.php');
-
- $this->partialMock(Filesystem::class, function (MockInterface $mock) use ($expected_path, $expected_content) {
- $mock->shouldReceive('makeDirectory')
- ->andReturnTrue();
-
- $mock->shouldReceive('put')
- ->once()
- ->with($expected_path, $expected_content)
- ->andReturnTrue();
- });
-
- $this->artisan('generate:auth_factory', ['table' => 'customers', '--create' => true])
- ->assertSuccessful();
- }
-}
diff --git a/tests/Feature/Commands/Auth/GenerateAuthModelCommandTest.php b/tests/Feature/Commands/Auth/GenerateAuthModelCommandTest.php
deleted file mode 100644
index b926e20..0000000
--- a/tests/Feature/Commands/Auth/GenerateAuthModelCommandTest.php
+++ /dev/null
@@ -1,94 +0,0 @@
-deleteFile($this->app->path('Models/Customer.php'));
- $this->deleteFile($this->app->path('Models/PublicUser.php'));
-
- // setup skeleton service provider
- $this->copyFile(
- $this->getTestStubPath('Providers/SkeletonAppServiceProvider.php'),
- $this->app->path('Providers/AppServiceProvider.php')
- );
- }
-
- protected function tearDown(): void
- {
- $this->deleteFile($this->app->path('Models/Customer.php'));
- $this->deleteFile($this->app->path('Models/PublicUser.php'));
-
- // setup standard service provider
- $this->copyFile(
- $this->getTestStubPath('Providers/AppServiceProvider.php'),
- $this->app->path('Providers/AppServiceProvider.php')
- );
-
- parent::tearDown();
- }
-
- public function test_it_can_generate_auth_model_output(): void
- {
- $expected_content = $this->getTestStubContents('Models/Customer.php');
-
- $this->artisan('generate:auth_model', ['table' => 'customers'])
- ->expectsOutput($expected_content);
- }
-
- public function test_it_can_generate_a_new_auth_model_file(): void
- {
- $expected_path = $this->app->path('Models/Customer.php');
- $expected_content = $this->getTestStubContents('Models/Customer.php');
-
- $this->artisan('generate:auth_model', ['table' => 'customers', '--create' => true])
- ->assertSuccessful();
-
- $this->assertFileExists($expected_path);
-
- $actual_content = $this->getGeneratedFileContents($expected_path);
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_adds_auth_causer_types_and_subject_types_and_morph_maps(): void
- {
- $expected_path = $this->app->path('Models/PublicUser.php');
- $expected_content = $this->getTestStubContents('Models/PublicUser.php');
-
- $this->artisan('generate:auth_model', ['table' => 'public_users', '--create' => true, '--auth_name' => 'portal'])
- ->assertSuccessful();
-
- $this->assertFileExists($expected_path);
-
- $actual_content = $this->getGeneratedFileContents($expected_path);
-
- $this->assertEquals($expected_content, $actual_content);
-
- $expected_path = $this->app->path('Models/Customer.php');
- $expected_content = $this->getTestStubContents('Models/Customer.php');
-
- $this->artisan('generate:auth_model', ['table' => 'customers', '--create' => true])
- ->assertSuccessful();
-
- $this->assertFileExists($expected_path);
-
- $actual_content = $this->getGeneratedFileContents($expected_path);
- $this->assertEquals($expected_content, $actual_content);
-
- $expected_path = $this->app->path('Providers/AppServiceProvider.php');
- $expected_content = $this->getTestStubContents('Providers/AuthModelsAppServiceProvider.php');
- $actual_content = $this->getGeneratedFileContents($expected_path);
-
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Feature/Commands/Auth/GenerateAuthPasswordResetsCommandTest.php b/tests/Feature/Commands/Auth/GenerateAuthPasswordResetsCommandTest.php
deleted file mode 100644
index ef4495d..0000000
--- a/tests/Feature/Commands/Auth/GenerateAuthPasswordResetsCommandTest.php
+++ /dev/null
@@ -1,42 +0,0 @@
-getTestStubContents('migrations/create_customer_password_reset_tokens_table.php');
-
- $this->artisan('generate:auth_password_resets', ['table' => 'customers'])
- ->expectsOutput($expected_content);
- }
-
- public function test_it_can_generate_password_resets_migration_file(): void
- {
- $this->travelTo('2024-04-23 12:43:31');
-
- $expected_path = $this->app->databasePath('migrations/2024_04_23_124331_create_customer_password_reset_tokens_table.php');
- $expected_content = $this->getTestStubContents('migrations/create_customer_password_reset_tokens_table.php');
-
- $this->partialMock(Filesystem::class, function (MockInterface $mock) use ($expected_path, $expected_content) {
- $mock->shouldReceive('makeDirectory')
- ->andReturnTrue();
-
- $mock->shouldReceive('put')
- ->once()
- ->with($expected_path, $expected_content)
- ->andReturnTrue();
- });
-
- $this->artisan('generate:auth_password_resets', ['table' => 'customers', '--create' => true])
- ->assertSuccessful();
- }
-}
diff --git a/tests/Feature/Commands/Auth/GenerateAuthPermissionsCommandTest.php b/tests/Feature/Commands/Auth/GenerateAuthPermissionsCommandTest.php
deleted file mode 100644
index 4ca3fce..0000000
--- a/tests/Feature/Commands/Auth/GenerateAuthPermissionsCommandTest.php
+++ /dev/null
@@ -1,90 +0,0 @@
-deleteFile($this->app->databasePath('seeders/PermissionsSeeder.php'));
- }
-
- protected function tearDown(): void
- {
- $this->deleteFile($this->app->databasePath('seeders/PermissionsSeeder.php'));
-
- parent::tearDown();
- }
-
- public function test_it_can_generate_auth_permissions_output(): void
- {
- $expected_content = $this->getTestStubContents('seeders/_authPermissions.stub');
-
- $this->artisan('generate:auth_permissions', ['table' => 'customers'])
- ->expectsOutput($expected_content);
- }
-
- public function test_it_can_generate_a_new_auth_permissions_file(): void
- {
- $expected_path = $this->app->databasePath('seeders/PermissionsSeeder.php');
- $expected_content = $this->getTestStubContents('seeders/AuthPermissionsSeederNew.php');
-
- $this->artisan('generate:auth_permissions', ['table' => 'customers', '--create' => true])
- ->assertSuccessful();
-
- $this->artisan('generate:auth_permissions', ['table' => 'public_users', '--create' => true])
- ->assertSuccessful();
-
- $this->assertFileExists($expected_path);
-
- $actual_content = $this->getGeneratedFileContents($expected_path);
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_append_permissions_to_an_existing_auth_permissions_seeder(): void
- {
- $expected_path = $this->app->databasePath('seeders/PermissionsSeeder.php');
-
- $this->copyFile($this->getTestStubPath('seeders/PermissionsSeeder.php'), $expected_path);
- $expected_content = $this->getTestStubContents('seeders/AuthPermissionsSeederExisting.php');
-
- $this->artisan('generate:auth_permissions', ['table' => 'customers', '--create' => true])
- ->assertSuccessful();
-
- $this->artisan('generate:auth_permissions', ['table' => 'public_users', '--create' => true])
- ->assertSuccessful();
-
- $this->assertFileExists($expected_path);
-
- $actual_content = $this->getGeneratedFileContents($expected_path);
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_does_not_duplicate_permissions_when_the_generate_command_is_called_multiple_times(): void
- {
- $expected_path = $this->app->databasePath('seeders/PermissionsSeeder.php');
-
- $this->copyFile($this->getTestStubPath('seeders/PermissionsSeeder.php'), $expected_path);
- $expected_content = $this->getTestStubContents('seeders/AuthPermissionsSeederExisting.php');
-
- $this->artisan('generate:auth_permissions', ['table' => 'customers', '--create' => true])
- ->assertSuccessful();
-
- $this->artisan('generate:auth_permissions', ['table' => 'public_users', '--create' => true])
- ->assertSuccessful();
-
- $this->assertFileExists($expected_path);
-
- $actual_content = $this->getGeneratedFileContents($expected_path);
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Feature/Commands/Auth/GenerateAuthPolicyCommandTest.php b/tests/Feature/Commands/Auth/GenerateAuthPolicyCommandTest.php
deleted file mode 100644
index dc9801f..0000000
--- a/tests/Feature/Commands/Auth/GenerateAuthPolicyCommandTest.php
+++ /dev/null
@@ -1,40 +0,0 @@
-getTestStubContents('Policies/CustomerPolicy.php');
-
- $this->artisan('generate:auth_policy', ['table' => 'customers'])
- ->expectsOutput($expected_content);
- }
-
- public function test_it_can_generate_auth_policy_file(): void
- {
- $expected_path = $this->app->path('Policies/CustomerPolicy.php');
- $expected_content = $this->getTestStubContents('Policies/CustomerPolicy.php');
-
- $this->partialMock(Filesystem::class, function (MockInterface $mock) use ($expected_path, $expected_content) {
- $mock->shouldReceive('makeDirectory')
- ->andReturnTrue();
-
- $mock->shouldReceive('put')
- ->once()
- ->with($expected_path, $expected_content)
- ->andReturnTrue();
- });
-
- $this->artisan('generate:auth_policy', ['table' => 'customers', '--create' => true])
- ->assertSuccessful();
- }
-}
diff --git a/tests/Feature/Commands/Auth/GenerateAuthRequestCommandTest.php b/tests/Feature/Commands/Auth/GenerateAuthRequestCommandTest.php
deleted file mode 100644
index 90c610a..0000000
--- a/tests/Feature/Commands/Auth/GenerateAuthRequestCommandTest.php
+++ /dev/null
@@ -1,40 +0,0 @@
-getTestStubContents('Requests/CustomersRequest.php');
-
- $this->artisan('generate:auth_request', ['table' => 'customers'])
- ->expectsOutput($expected_content);
- }
-
- public function test_it_can_generate_auth_model_request_file(): void
- {
- $expected_path = $this->app->path('Http/Requests/CustomersRequest.php');
- $expected_content = $this->getTestStubContents('Requests/CustomersRequest.php');
-
- $this->partialMock(Filesystem::class, function (MockInterface $mock) use ($expected_path, $expected_content) {
- $mock->shouldReceive('makeDirectory')
- ->andReturnTrue();
-
- $mock->shouldReceive('put')
- ->once()
- ->with($expected_path, $expected_content)
- ->andReturnTrue();
- });
-
- $this->artisan('generate:auth_request', ['table' => 'customers', '--create' => true])
- ->assertSuccessful();
- }
-}
diff --git a/tests/Feature/Commands/BaseGenerateCommandTest.php b/tests/Feature/Commands/BaseGenerateCommandTest.php
deleted file mode 100644
index 1e9db76..0000000
--- a/tests/Feature/Commands/BaseGenerateCommandTest.php
+++ /dev/null
@@ -1,83 +0,0 @@
-expectException(RuntimeException::class);
- $this->expectExceptionMessage('Not enough arguments (missing: "table")');
-
- $this->artisan('generate:factory');
- }
-
- public function test_it_does_not_accept_multiple_table_names(): void
- {
- $this->expectException(MultipleTablesSuppliedException::class);
-
- $this->artisan('generate:factory', ['table' => 'categories,orders']);
- }
-
- public function test_it_checks_if_the_table_exists(): void
- {
- $this->expectException(TableDoesNotExistException::class);
-
- $this->artisan('generate:factory', ['table' => 'none_existing_table']);
- }
-
- public function test_it_checks_if_the_columns_exists(): void
- {
- $this->expectException(ColumnDoesNotExistException::class);
-
- $this->artisan('generate:factory', ['table' => 'products', '--columns' => 'name,none_existing_field']);
- }
-
- public function test_it_does_not_create_existing_files_without_the_force_option(): void
- {
- $expected_path = $this->app->databasePath('factories/CategoryFactory.php');
-
- $this->partialMock(Filesystem::class, function (MockInterface $mock) {
- $mock->shouldReceive('exists')
- ->once()
- ->andReturnTrue();
-
- $mock->shouldNotReceive('makeDirectory');
-
- $mock->shouldNotReceive('put');
- });
-
- $this->artisan('generate:factory', ['table' => 'categories', '--create' => true])
- ->expectsOutput($expected_path . ' already exists!');
- }
-
- public function test_it_over_writes_existing_files_with_the_force_option(): void
- {
- $this->partialMock(Filesystem::class, function (MockInterface $mock) {
- $mock->shouldReceive('exists')
- ->once()
- ->andReturnTrue();
-
- $mock->shouldReceive('makeDirectory')
- ->andReturnTrue();
-
- $mock->shouldReceive('put')
- ->once()
- ->andReturnTrue();
- });
-
- $this->artisan('generate:factory', ['table' => 'categories', '--create' => true, '--force' => true])
- ->assertSuccessful();
- }
-}
diff --git a/tests/Feature/Commands/GenerateAllCommandTest.php b/tests/Feature/Commands/GenerateAllCommandTest.php
deleted file mode 100644
index 0e97acc..0000000
--- a/tests/Feature/Commands/GenerateAllCommandTest.php
+++ /dev/null
@@ -1,102 +0,0 @@
-getCommands();
-
- foreach ($commands as $command) {
- // skip test and permissions
- if ($command == GenerateTestCommand::class || $command == GeneratePermissionsCommand::class) {
- $fake_command = $this->mock($command . '[createOutput]', function ($mock) {
- $mock->shouldAllowMockingProtectedMethods()
- ->shouldNotHaveReceived('createOutput');
- });
- } else {
- $fake_command = $this->mock($command . '[createOutput]', function ($mock) {
- $mock->shouldAllowMockingProtectedMethods()
- ->shouldReceive('createOutput')
- ->once()
- ->with('categories', []);
- });
- }
-
- $this->app->instance($command, $fake_command);
- }
-
- $this->artisan('generate:all', ['table' => 'categories', '--except' => 'test,permissions'])
- ->assertSuccessful();
- }
-
- public function test_it_can_generate_all_output(): void
- {
- $commands = $this->getCommands();
-
- foreach ($commands as $command) {
- $fake_command = $this->mock($command . '[createOutput]', function ($mock) {
- $mock->shouldAllowMockingProtectedMethods()
- ->shouldReceive('createOutput')
- ->once()
- ->with('categories', []);
- });
-
- $this->app->instance($command, $fake_command);
- }
-
- $this->artisan('generate:all', ['table' => 'categories'])
- ->assertSuccessful();
- }
-
- public function test_it_can_create_all_files(): void
- {
- $commands = $this->getCommands();
-
- foreach ($commands as $command) {
- $fake_command = $this->mock($command . '[createFiles]', function ($mock) {
- $mock->shouldAllowMockingProtectedMethods()
- ->shouldReceive('createFiles')
- ->once()
- ->with('categories', [], false, '');
- });
-
- $this->app->instance($command, $fake_command);
- }
-
- $this->artisan('generate:all', ['table' => 'categories', '--create' => true])
- ->assertSuccessful();
- }
-}
diff --git a/tests/Feature/Commands/GenerateApiCommandTest.php b/tests/Feature/Commands/GenerateApiCommandTest.php
deleted file mode 100644
index 5250ff7..0000000
--- a/tests/Feature/Commands/GenerateApiCommandTest.php
+++ /dev/null
@@ -1,59 +0,0 @@
-getCommands();
-
- foreach ($commands as $command) {
- $fake_command = $this->mock($command . '[createOutput]', function ($mock) {
- $mock->shouldAllowMockingProtectedMethods()
- ->shouldReceive('createOutput')
- ->once()
- ->with('categories', []);
- });
-
- $this->app->instance($command, $fake_command);
- }
-
- $this->artisan('generate:api', ['table' => 'categories'])
- ->assertSuccessful();
- }
-
- public function test_it_can_create_all_api_files(): void
- {
- $commands = $this->getCommands();
-
- foreach ($commands as $command) {
- $fake_command = $this->mock($command . '[createFiles]', function ($mock) {
- $mock->shouldAllowMockingProtectedMethods()
- ->shouldReceive('createFiles')
- ->once()
- ->with('categories', [], false, '');
- });
-
- $this->app->instance($command, $fake_command);
- }
-
- $this->artisan('generate:api', ['table' => 'categories', '--create' => true])
- ->assertSuccessful();
- }
-}
diff --git a/tests/Feature/Commands/GenerateApiControllerCommandTest.php b/tests/Feature/Commands/GenerateApiControllerCommandTest.php
deleted file mode 100644
index cc112fc..0000000
--- a/tests/Feature/Commands/GenerateApiControllerCommandTest.php
+++ /dev/null
@@ -1,80 +0,0 @@
-deleteDirectory($this->app->path('Http/Controllers/Api'));
-
- // setup skeleton route file
- $this->copyFile(
- $this->getTestStubPath('routes/skeletonApi.php'),
- $this->app->basePath('routes/api.php')
- );
- }
-
- protected function tearDown(): void
- {
- $this->deleteFile($this->app->path('routes/api.php'));
-
- $this->deleteDirectory($this->app->path('Http/Controllers/Api'));
-
- parent::tearDown();
- }
-
- public function test_it_can_generate_api_output(): void
- {
- $expected_content = $this->getTestStubContents('Controllers/Api/CategoriesController.php');
-
- $this->artisan('generate:api_controller', ['table' => 'categories'])
- ->expectsOutput($expected_content);
- }
-
- public function test_it_can_generate_api_controller_file(): void
- {
- $expected_path = $this->app->path('Http/Controllers/Api/CategoriesController.php');
- $expected_content = $this->getTestStubContents('Controllers/Api/CategoriesController.php');
-
- $this->partialMock(Filesystem::class, function (MockInterface $mock) use ($expected_path, $expected_content) {
- $mock->shouldReceive('makeDirectory')
- ->andReturnTrue();
-
- $mock->shouldReceive('put')
- ->once()
- ->with($expected_path, $expected_content)
- ->andReturnTrue();
- });
-
- $this->artisan('generate:api_controller', ['table' => 'categories', '--create' => true])
- ->assertSuccessful();
- }
-
- public function test_it_can_append_api_routes_to_an_existing_api_routes_file(): void
- {
- $expected_path = $this->app->basePath('routes/api.php');
- $expected_content = $this->getTestStubContents('routes/modelsApi.php');
-
- $this->artisan('generate:api_controller', ['table' => 'categories', '--create' => true])
- ->assertSuccessful();
-
- $this->artisan('generate:api_controller', ['table' => 'products', '--create' => true])
- ->assertSuccessful();
-
- $this->assertFileExists($expected_path);
-
- $actual_content = $this->getGeneratedFileContents($expected_path);
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Feature/Commands/GenerateApiTestCommandTest.php b/tests/Feature/Commands/GenerateApiTestCommandTest.php
deleted file mode 100644
index 73dd96a..0000000
--- a/tests/Feature/Commands/GenerateApiTestCommandTest.php
+++ /dev/null
@@ -1,40 +0,0 @@
-getTestStubContents('tests/Api/CategoriesControllerTest.stub');
-
- $this->artisan('generate:api_test', ['table' => 'categories'])
- ->expectsOutput($expected_content);
- }
-
- public function test_it_can_generate_test_file(): void
- {
- $expected_path = $this->app->basePath('tests/Feature/Controllers/Api/CategoriesControllerTest.php');
- $expected_content = $this->getTestStubContents('tests/Api/CategoriesControllerTest.stub');
-
- $this->partialMock(Filesystem::class, function (MockInterface $mock) use ($expected_path, $expected_content) {
- $mock->shouldReceive('makeDirectory')
- ->andReturnTrue();
-
- $mock->shouldReceive('put')
- ->once()
- ->with($expected_path, $expected_content)
- ->andReturnTrue();
- });
-
- $this->artisan('generate:api_test', ['table' => 'categories', '--create' => true])
- ->assertSuccessful();
- }
-}
diff --git a/tests/Feature/Commands/GenerateCommandsTest.php b/tests/Feature/Commands/GenerateCommandsTest.php
new file mode 100644
index 0000000..6b6b1ac
--- /dev/null
+++ b/tests/Feature/Commands/GenerateCommandsTest.php
@@ -0,0 +1,326 @@
+path = sys_get_temp_dir() . '/generators-' . uniqid();
+
+ (new Filesystem)->ensureDirectoryExists("{$this->path}/app/Providers");
+ (new Filesystem)->ensureDirectoryExists("{$this->path}/routes");
+
+ file_put_contents("{$this->path}/app/Providers/AppServiceProvider.php", <<<'PHP'
+ path}/routes/web.php", "app->setBasePath($this->path);
+ $this->app->useAppPath("{$this->path}/app");
+ $this->app->useDatabasePath("{$this->path}/database");
+ $this->app->useLangPath("{$this->path}/lang");
+ }
+
+ protected function tearDown(): void
+ {
+ $path = $this->path;
+
+ parent::tearDown();
+
+ (new Filesystem)->deleteDirectory($path);
+ }
+
+ #[Test]
+ public function it_prints_the_code_without_create(): void
+ {
+ $this->artisan('generate:model', ['table' => 'categories'])
+ ->expectsOutputToContain('// app/Models/Category.php')
+ ->expectsOutputToContain('class Category extends Model')
+ ->assertSuccessful();
+
+ $this->assertFileDoesNotExist("{$this->path}/app/Models/Category.php");
+ }
+
+ #[Test]
+ public function it_writes_the_files_with_create(): void
+ {
+ $this->artisan('generate:all', ['table' => 'products', '--create' => true])->assertSuccessful();
+
+ foreach ([
+ 'app/Models/Product.php',
+ 'database/factories/ProductFactory.php',
+ 'app/Policies/ProductPolicy.php',
+ 'app/Filters/ProductFilters.php',
+ 'app/BulkActions/ProductBulkActions.php',
+ 'app/Stencil/ProductForm.php',
+ 'app/Http/Controllers/Admin/ProductsController.php',
+ 'routes/admin.php',
+ 'resources/views/admin/products/index.blade.php',
+ 'resources/views/admin/products/show.blade.php',
+ 'tests/Feature/Admin/ProductsControllerTest.php',
+ 'lang/dv.json',
+ ] as $file) {
+ $this->assertFileExists("{$this->path}/{$file}");
+ }
+
+ $provider = file_get_contents("{$this->path}/app/Providers/AppServiceProvider.php");
+
+ $this->assertStringContainsString('use Javaabu\\Permissions\\Permissions;', $provider);
+ $this->assertStringContainsString("Permissions::resource('products', label: 'Products');", $provider);
+
+ $routes = file_get_contents("{$this->path}/routes/admin.php");
+
+ $this->assertStringContainsString("Route::resource('products', ProductsController::class)->withTrashed(['show']);", $routes);
+ $this->assertStringContainsString("Route::bulkActions('products', ProductBulkActions::class);", $routes);
+ $this->assertStringContainsString("require __DIR__ . '/admin.php';", file_get_contents("{$this->path}/routes/web.php"));
+
+ $this->assertEquals('ހޯދާ', json_decode(file_get_contents("{$this->path}/lang/dv.json"), true)['Search']);
+ }
+
+ #[Test]
+ public function it_adds_to_merged_files_once_and_keeps_existing_files(): void
+ {
+ $this->artisan('generate:all', ['table' => 'categories', '--create' => true])->assertSuccessful();
+
+ file_put_contents("{$this->path}/app/Models/Category.php", 'artisan('generate:all', ['table' => 'categories', '--create' => true])
+ ->expectsOutputToContain('exists')
+ ->assertSuccessful();
+
+ $this->assertEquals('path}/app/Models/Category.php"));
+ $this->assertEquals(1, substr_count(file_get_contents("{$this->path}/routes/admin.php"), "Route::resource('categories'"));
+ $this->assertEquals(1, substr_count(file_get_contents("{$this->path}/app/Providers/AppServiceProvider.php"), "Permissions::resource('categories'"));
+ }
+
+ #[Test]
+ public function it_overwrites_with_force(): void
+ {
+ $this->artisan('generate:model', ['table' => 'categories', '--create' => true])->assertSuccessful();
+
+ file_put_contents("{$this->path}/app/Models/Category.php", 'artisan('generate:model', ['table' => 'categories', '--create' => true, '--force' => true])->assertSuccessful();
+
+ $this->assertStringContainsString('class Category extends Model', file_get_contents("{$this->path}/app/Models/Category.php"));
+ }
+
+ #[Test]
+ public function it_skips_parts_with_except(): void
+ {
+ $this->artisan('generate:all', ['table' => 'categories', '--create' => true, '--except' => 'views,test,routes'])->assertSuccessful();
+
+ $this->assertFileExists("{$this->path}/app/Models/Category.php");
+ $this->assertFileDoesNotExist("{$this->path}/resources/views/admin/categories/index.blade.php");
+ $this->assertFileDoesNotExist("{$this->path}/tests/Feature/Admin/CategoriesControllerTest.php");
+ $this->assertFileDoesNotExist("{$this->path}/routes/admin.php");
+ }
+
+ #[Test]
+ public function it_generates_inertia_pages_with_the_views_option(): void
+ {
+ $this->artisan('generate:views', ['table' => 'categories', '--create' => true, '--views' => 'inertia'])->assertSuccessful();
+
+ $this->assertFileExists("{$this->path}/resources/js/pages/admin/categories/Index.tsx");
+ $this->assertFileExists("{$this->path}/resources/js/pages/admin/categories/types.ts");
+ $this->assertFileExists("{$this->path}/app/Http/Resources/Admin/CategoryResource.php");
+ }
+
+ #[Test]
+ public function it_chooses_the_fields_with_options(): void
+ {
+ $this->artisan('generate:form', ['table' => 'products', '--form' => 'name,price,tags'])
+ ->expectsOutputToContain("TextInput::make('name')")
+ ->expectsOutputToContain("Select::make('tags')")
+ ->doesntExpectOutputToContain("TextInput::make('stock')")
+ ->assertSuccessful();
+ }
+
+ #[Test]
+ public function it_asks_for_the_fields_when_interactive(): void
+ {
+ $this->artisan('generate:form', ['table' => 'categories', '--interactive' => true])
+ ->expectsQuestion('Which fields go in the index table?', ['name'])
+ ->expectsQuestion('Which fields go in the form?', ['name'])
+ ->expectsQuestion('Which fields go on the show page?', ['name', 'slug'])
+ ->expectsQuestion('Media collections (leave empty for none)', '')
+ ->expectsQuestion('Which views?', 'blade')
+ ->expectsOutputToContain("TextInput::make('name')")
+ ->doesntExpectOutputToContain("TextInput::make('slug')")
+ ->assertSuccessful();
+ }
+
+ #[Test]
+ public function it_writes_a_user_type_migration_when_the_table_does_not_exist(): void
+ {
+ $this->artisan('generate:auth', ['table' => 'vendors', '--migration' => true, '--create' => true])
+ ->expectsOutputToContain('php artisan migrate')
+ ->assertSuccessful();
+
+ $migrations = glob("{$this->path}/database/migrations/*_create_vendors_table.php");
+
+ $this->assertCount(1, $migrations);
+ $this->assertStringContainsString('UserSchema::columns($table);', file_get_contents($migrations[0]));
+ }
+
+ #[Test]
+ public function it_generates_a_user_type(): void
+ {
+ (new Filesystem)->ensureDirectoryExists("{$this->path}/config");
+ copy(dirname(__DIR__, 3) . '/vendor/orchestra/testbench-core/laravel/config/auth.php', "{$this->path}/config/auth.php");
+ $this->app->useConfigPath("{$this->path}/config");
+
+ $this->artisan('generate:auth', ['table' => 'public_users', '--type' => 'member', '--guard' => 'web_member', '--create' => true])
+ ->assertSuccessful();
+
+ $model = file_get_contents("{$this->path}/app/Models/PublicUser.php");
+
+ $this->assertStringContainsString('class PublicUser extends User', $model);
+ $this->assertStringContainsString('use Javaabu\\Auth\\Models\\User;', $model);
+ $this->assertStringNotContainsString('use SoftDeletes;', $model);
+
+ $config = file_get_contents("{$this->path}/config/auth.php");
+
+ $this->assertStringContainsString("'web_member' => [\n 'driver' => 'session',\n 'provider' => 'public_users',", $config);
+ $this->assertStringContainsString("'model' => PublicUser::class,", $config);
+ $this->assertStringContainsString("'members' => [\n 'provider' => 'public_users',", $config);
+
+ $provider = file_get_contents("{$this->path}/app/Providers/AppServiceProvider.php");
+
+ $this->assertStringContainsString("UserTypes::register('member', [", $provider);
+ $this->assertStringContainsString("'guard' => 'web_member',", $provider);
+
+ $this->assertStringContainsString("Route::authRoutes('member', ['prefix' => 'member']);", file_get_contents("{$this->path}/routes/web.php"));
+ $this->assertStringContainsString("Route::userAccountActions('member', ['prefix' => 'public-users']);", file_get_contents("{$this->path}/routes/admin.php"));
+
+ $form = file_get_contents("{$this->path}/app/Stencil/PublicUserForm.php");
+
+ $this->assertStringContainsString('->password()', $form);
+ $this->assertStringContainsString('->options(UserStatuses::class)', $form);
+ $this->assertStringNotContainsString("'remember_token'", $form);
+ $this->assertStringNotContainsString("'login_attempts'", $form);
+
+ // running it again changes nothing
+ $this->artisan('generate:auth', ['table' => 'public_users', '--type' => 'member', '--guard' => 'web_member', '--create' => true])->assertSuccessful();
+
+ $this->assertEquals(1, substr_count(file_get_contents("{$this->path}/config/auth.php"), "'web_member' =>"));
+ $this->assertEquals(1, substr_count(file_get_contents("{$this->path}/app/Providers/AppServiceProvider.php"), "UserTypes::register('member'"));
+ }
+
+ #[Test]
+ public function it_gives_an_admin_user_type_roles(): void
+ {
+ config()->set('generators.admin.user_model', 'App\\Models\\PublicUser');
+
+ $this->artisan('generate:auth', ['table' => 'public_users', '--type' => 'admin', '--except' => 'views,test,config,routes'])
+ ->expectsOutputToContain('use HasRoles;')
+ ->assertSuccessful();
+ }
+
+ #[Test]
+ public function it_prints_a_user_type_without_create(): void
+ {
+ $this->artisan('generate:auth', ['table' => 'customers', '--except' => 'views,test'])
+ ->expectsOutputToContain("UserTypes::register('customer', [")
+ ->expectsOutputToContain("Route::authRoutes('customer', ['prefix' => 'customer']);")
+ ->assertSuccessful();
+ }
+
+ #[Test]
+ public function it_explains_pivot_tables(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('product_tag is the pivot table of products and tags');
+
+ $this->artisan('generate:all', ['table' => 'product_tag']);
+ }
+
+ #[Test]
+ public function it_speaks_dhivehi(): void
+ {
+ app()->setLocale('dv');
+
+ $this->expectException(TableDoesNotExistException::class);
+ $this->expectExceptionMessage('"missing" ޓޭބަލެއް ނުފެނުނު.');
+
+ $this->artisan('generate:model', ['table' => 'missing']);
+ }
+
+ #[Test]
+ public function it_writes_the_admin_guard_into_the_generated_code(): void
+ {
+ config()->set('generators.admin.guard', 'web_admin');
+ config()->set('generators.admin.user_model', 'App\\Models\\Admin');
+
+ $this->artisan('generate:all', ['table' => 'categories', '--create' => true])->assertSuccessful();
+
+ $this->assertStringContainsString("protected ?string \$guard = 'web_admin';", file_get_contents("{$this->path}/app/Policies/CategoryPolicy.php"));
+ $this->assertStringContainsString("Permissions::resource('categories', label: 'Categories')->guards(['web_admin']);", file_get_contents("{$this->path}/app/Providers/AppServiceProvider.php"));
+ $this->assertStringContainsString("Route::bulkActions('categories', CategoryBulkActions::class, ['guard' => 'web_admin']);", file_get_contents("{$this->path}/routes/admin.php"));
+
+ $test = file_get_contents("{$this->path}/tests/Feature/Admin/CategoriesControllerTest.php");
+
+ $this->assertStringContainsString('use App\\Models\\Admin;', $test);
+ $this->assertStringContainsString("\$this->actingAs(\$user, 'web_admin');", $test);
+ }
+
+ #[Test]
+ public function it_rejects_several_tables(): void
+ {
+ $this->expectException(MultipleTablesSuppliedException::class);
+
+ $this->artisan('generate:model', ['table' => 'products,categories']);
+ }
+
+ #[Test]
+ public function it_rejects_a_missing_table(): void
+ {
+ $this->expectException(TableDoesNotExistException::class);
+
+ $this->artisan('generate:model', ['table' => 'missing']);
+ }
+
+ #[Test]
+ public function it_rejects_missing_columns(): void
+ {
+ $this->expectException(ColumnDoesNotExistException::class);
+
+ $this->artisan('generate:model', ['table' => 'products', '--columns' => 'name,missing']);
+ }
+
+ #[Test]
+ public function it_rejects_unknown_fields(): void
+ {
+ $this->expectException(ColumnDoesNotExistException::class);
+
+ $this->artisan('generate:form', ['table' => 'products', '--form' => 'name,missing']);
+ }
+}
diff --git a/tests/Feature/Commands/GenerateControllerCommandTest.php b/tests/Feature/Commands/GenerateControllerCommandTest.php
deleted file mode 100644
index 158691b..0000000
--- a/tests/Feature/Commands/GenerateControllerCommandTest.php
+++ /dev/null
@@ -1,40 +0,0 @@
-getTestStubContents('Controllers/CategoriesController.php');
-
- $this->artisan('generate:controller', ['table' => 'categories'])
- ->expectsOutput($expected_content);
- }
-
- public function test_it_can_generate_controller_file(): void
- {
- $expected_path = $this->app->path('Http/Controllers/Admin/CategoriesController.php');
- $expected_content = $this->getTestStubContents('Controllers/CategoriesController.php');
-
- $this->partialMock(Filesystem::class, function (MockInterface $mock) use ($expected_path, $expected_content) {
- $mock->shouldReceive('makeDirectory')
- ->andReturnTrue();
-
- $mock->shouldReceive('put')
- ->once()
- ->with($expected_path, $expected_content)
- ->andReturnTrue();
- });
-
- $this->artisan('generate:controller', ['table' => 'categories', '--create' => true])
- ->assertSuccessful();
- }
-}
diff --git a/tests/Feature/Commands/GenerateExportCommandTest.php b/tests/Feature/Commands/GenerateExportCommandTest.php
deleted file mode 100644
index 61c2598..0000000
--- a/tests/Feature/Commands/GenerateExportCommandTest.php
+++ /dev/null
@@ -1,40 +0,0 @@
-getTestStubContents('Exports/CategoriesExport.php');
-
- $this->artisan('generate:export', ['table' => 'categories'])
- ->expectsOutput($expected_content);
- }
-
- public function test_it_can_generate_export_file(): void
- {
- $expected_path = $this->app->path('Exports/CategoriesExport.php');
- $expected_content = $this->getTestStubContents('Exports/CategoriesExport.php');
-
- $this->partialMock(Filesystem::class, function (MockInterface $mock) use ($expected_path, $expected_content) {
- $mock->shouldReceive('makeDirectory')
- ->andReturnTrue();
-
- $mock->shouldReceive('put')
- ->once()
- ->with($expected_path, $expected_content)
- ->andReturnTrue();
- });
-
- $this->artisan('generate:export', ['table' => 'categories', '--create' => true])
- ->assertSuccessful();
- }
-}
diff --git a/tests/Feature/Commands/GenerateFactoryCommandTest.php b/tests/Feature/Commands/GenerateFactoryCommandTest.php
deleted file mode 100644
index 2861aa4..0000000
--- a/tests/Feature/Commands/GenerateFactoryCommandTest.php
+++ /dev/null
@@ -1,40 +0,0 @@
-getTestStubContents('factories/CategoryFactory.php');
-
- $this->artisan('generate:factory', ['table' => 'categories'])
- ->expectsOutput($expected_content);
- }
-
- public function test_it_can_generate_factory_file(): void
- {
- $expected_path = $this->app->databasePath('factories/CategoryFactory.php');
- $expected_content = $this->getTestStubContents('factories/CategoryFactory.php');
-
- $this->partialMock(Filesystem::class, function (MockInterface $mock) use ($expected_path, $expected_content) {
- $mock->shouldReceive('makeDirectory')
- ->andReturnTrue();
-
- $mock->shouldReceive('put')
- ->once()
- ->with($expected_path, $expected_content)
- ->andReturnTrue();
- });
-
- $this->artisan('generate:factory', ['table' => 'categories', '--create' => true])
- ->assertSuccessful();
- }
-}
diff --git a/tests/Feature/Commands/GenerateModelCommandTest.php b/tests/Feature/Commands/GenerateModelCommandTest.php
deleted file mode 100644
index eb51ee6..0000000
--- a/tests/Feature/Commands/GenerateModelCommandTest.php
+++ /dev/null
@@ -1,93 +0,0 @@
-deleteFile($this->app->path('Models/Product.php'));
- $this->deleteFile($this->app->path('Models/Category.php'));
-
- // setup skeleton service provider
- $this->copyFile(
- $this->getTestStubPath('Providers/SkeletonAppServiceProvider.php'),
- $this->app->path('Providers/AppServiceProvider.php')
- );
- }
-
- protected function tearDown(): void
- {
- $this->deleteFile($this->app->path('Models/Product.php'));
- $this->deleteFile($this->app->path('Models/Category.php'));
-
- // setup standard service provider
- $this->copyFile(
- $this->getTestStubPath('Providers/AppServiceProvider.php'),
- $this->app->path('Providers/AppServiceProvider.php')
- );
-
- parent::tearDown();
- }
-
- public function test_it_can_generate_model_output(): void
- {
- $expected_content = $this->getTestStubContents('Models/Product.php');
-
- $this->artisan('generate:model', ['table' => 'products'])
- ->expectsOutput($expected_content);
- }
-
- public function test_it_can_generate_a_new_model_file(): void
- {
- $expected_path = $this->app->path('Models/Product.php');
- $expected_content = $this->getTestStubContents('Models/Product.php');
-
- $this->artisan('generate:model', ['table' => 'products', '--create' => true])
- ->assertSuccessful();
-
- $this->assertFileExists($expected_path);
-
- $actual_content = $this->getGeneratedFileContents($expected_path);
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_adds_subject_types_and_morph_maps(): void
- {
- $expected_path = $this->app->path('Models/Category.php');
- $expected_content = $this->getTestStubContents('Models/Category.php');
-
- $this->artisan('generate:model', ['table' => 'categories', '--create' => true])
- ->assertSuccessful();
-
- $this->assertFileExists($expected_path);
-
- $actual_content = $this->getGeneratedFileContents($expected_path);
- $this->assertEquals($expected_content, $actual_content);
-
- $expected_path = $this->app->path('Models/Product.php');
- $expected_content = $this->getTestStubContents('Models/Product.php');
-
- $this->artisan('generate:model', ['table' => 'products', '--create' => true])
- ->assertSuccessful();
-
- $this->assertFileExists($expected_path);
-
- $actual_content = $this->getGeneratedFileContents($expected_path);
- $this->assertEquals($expected_content, $actual_content);
-
- $expected_path = $this->app->path('Providers/AppServiceProvider.php');
- $expected_content = $this->getTestStubContents('Providers/ModelsAppServiceProvider.php');
- $actual_content = $this->getGeneratedFileContents($expected_path);
-
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Feature/Commands/GeneratePermissionsCommandTest.php b/tests/Feature/Commands/GeneratePermissionsCommandTest.php
deleted file mode 100644
index b716750..0000000
--- a/tests/Feature/Commands/GeneratePermissionsCommandTest.php
+++ /dev/null
@@ -1,93 +0,0 @@
-deleteFile($this->app->databasePath('seeders/PermissionsSeeder.php'));
- }
-
- protected function tearDown(): void
- {
- $this->deleteFile($this->app->databasePath('seeders/PermissionsSeeder.php'));
-
- parent::tearDown();
- }
-
- public function test_it_can_generate_permissions_output(): void
- {
- $expected_content = $this->getTestStubContents('seeders/_permissions.stub');
-
- $this->artisan('generate:permissions', ['table' => 'categories'])
- ->expectsOutput($expected_content);
- }
-
- public function test_it_can_generate_a_new_permissions_file(): void
- {
- $expected_path = $this->app->databasePath('seeders/PermissionsSeeder.php');
- $expected_content = $this->getTestStubContents('seeders/PermissionsSeederNew.php');
-
- $this->artisan('generate:permissions', ['table' => 'products', '--create' => true])
- ->assertSuccessful();
-
- $this->artisan('generate:permissions', ['table' => 'categories', '--create' => true])
- ->assertSuccessful();
-
- $this->assertFileExists($expected_path);
-
- $actual_content = $this->getGeneratedFileContents($expected_path);
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_append_permissions_to_an_existing_permissions_seeder(): void
- {
- $expected_path = $this->app->databasePath('seeders/PermissionsSeeder.php');
-
- $this->copyFile($this->getTestStubPath('seeders/PermissionsSeeder.php'), $expected_path);
- $expected_content = $this->getTestStubContents('seeders/PermissionsSeederExisting.php');
-
- $this->artisan('generate:permissions', ['table' => 'products', '--create' => true])
- ->assertSuccessful();
-
- $this->artisan('generate:permissions', ['table' => 'categories', '--create' => true])
- ->assertSuccessful();
-
- $this->assertFileExists($expected_path);
-
- $actual_content = $this->getGeneratedFileContents($expected_path);
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_does_not_duplicate_permissions_when_the_generate_command_is_called_multiple_times(): void
- {
- $expected_path = $this->app->databasePath('seeders/PermissionsSeeder.php');
-
- $this->copyFile($this->getTestStubPath('seeders/PermissionsSeeder.php'), $expected_path);
- $expected_content = $this->getTestStubContents('seeders/PermissionsSeederExisting.php');
-
- $this->artisan('generate:permissions', ['table' => 'products', '--create' => true])
- ->assertSuccessful();
-
- $this->artisan('generate:permissions', ['table' => 'categories', '--create' => true])
- ->assertSuccessful();
-
- $this->artisan('generate:permissions', ['table' => 'categories', '--create' => true])
- ->assertSuccessful();
-
- $this->assertFileExists($expected_path);
-
- $actual_content = $this->getGeneratedFileContents($expected_path);
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Feature/Commands/GeneratePolicyCommandTest.php b/tests/Feature/Commands/GeneratePolicyCommandTest.php
deleted file mode 100644
index 9b24808..0000000
--- a/tests/Feature/Commands/GeneratePolicyCommandTest.php
+++ /dev/null
@@ -1,40 +0,0 @@
-getTestStubContents('Policies/CategoryPolicy.php');
-
- $this->artisan('generate:policy', ['table' => 'categories'])
- ->expectsOutput($expected_content);
- }
-
- public function test_it_can_generate_policy_file(): void
- {
- $expected_path = $this->app->path('Policies/CategoryPolicy.php');
- $expected_content = $this->getTestStubContents('Policies/CategoryPolicy.php');
-
- $this->partialMock(Filesystem::class, function (MockInterface $mock) use ($expected_path, $expected_content) {
- $mock->shouldReceive('makeDirectory')
- ->andReturnTrue();
-
- $mock->shouldReceive('put')
- ->once()
- ->with($expected_path, $expected_content)
- ->andReturnTrue();
- });
-
- $this->artisan('generate:policy', ['table' => 'categories', '--create' => true])
- ->assertSuccessful();
- }
-}
diff --git a/tests/Feature/Commands/GenerateRequestCommandTest.php b/tests/Feature/Commands/GenerateRequestCommandTest.php
deleted file mode 100644
index 72f0d56..0000000
--- a/tests/Feature/Commands/GenerateRequestCommandTest.php
+++ /dev/null
@@ -1,40 +0,0 @@
-getTestStubContents('Requests/CategoriesRequest.php');
-
- $this->artisan('generate:request', ['table' => 'categories'])
- ->expectsOutput($expected_content);
- }
-
- public function test_it_can_generate_request_file(): void
- {
- $expected_path = $this->app->path('Http/Requests/CategoriesRequest.php');
- $expected_content = $this->getTestStubContents('Requests/CategoriesRequest.php');
-
- $this->partialMock(Filesystem::class, function (MockInterface $mock) use ($expected_path, $expected_content) {
- $mock->shouldReceive('makeDirectory')
- ->andReturnTrue();
-
- $mock->shouldReceive('put')
- ->once()
- ->with($expected_path, $expected_content)
- ->andReturnTrue();
- });
-
- $this->artisan('generate:request', ['table' => 'categories', '--create' => true])
- ->assertSuccessful();
- }
-}
diff --git a/tests/Feature/Commands/GenerateRoutesCommandTest.php b/tests/Feature/Commands/GenerateRoutesCommandTest.php
deleted file mode 100644
index aef1e6c..0000000
--- a/tests/Feature/Commands/GenerateRoutesCommandTest.php
+++ /dev/null
@@ -1,55 +0,0 @@
-copyFile(
- $this->getTestStubPath('routes/skeletonAdmin.php'),
- $this->app->basePath('routes/admin.php')
- );
- }
-
- protected function tearDown(): void
- {
- $this->deleteFile($this->app->path('routes/admin.php'));
-
- parent::tearDown();
- }
-
- public function test_it_can_generate_routes_output(): void
- {
- $expected_content = $this->getTestStubContents('routes/_admin.stub');
-
- $this->artisan('generate:routes', ['table' => 'categories'])
- ->expectsOutput($expected_content);
- }
-
-
- public function test_it_can_append_routes_to_an_existing_routes_file(): void
- {
- $expected_path = $this->app->basePath('routes/admin.php');
- $expected_content = $this->getTestStubContents('routes/modelsAdmin.php');
-
- $this->artisan('generate:routes', ['table' => 'categories', '--create' => true])
- ->assertSuccessful();
-
- $this->artisan('generate:routes', ['table' => 'products', '--create' => true])
- ->assertSuccessful();
-
- $this->assertFileExists($expected_path);
-
- $actual_content = $this->getGeneratedFileContents($expected_path);
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Feature/Commands/GenerateTestCommandTest.php b/tests/Feature/Commands/GenerateTestCommandTest.php
deleted file mode 100644
index c15a099..0000000
--- a/tests/Feature/Commands/GenerateTestCommandTest.php
+++ /dev/null
@@ -1,40 +0,0 @@
-getTestStubContents('tests/CategoriesControllerTest.stub');
-
- $this->artisan('generate:test', ['table' => 'categories'])
- ->expectsOutput($expected_content);
- }
-
- public function test_it_can_generate_test_file(): void
- {
- $expected_path = $this->app->basePath('tests/Feature/Controllers/Admin/CategoriesControllerTest.php');
- $expected_content = $this->getTestStubContents('tests/CategoriesControllerTest.stub');
-
- $this->partialMock(Filesystem::class, function (MockInterface $mock) use ($expected_path, $expected_content) {
- $mock->shouldReceive('makeDirectory')
- ->andReturnTrue();
-
- $mock->shouldReceive('put')
- ->once()
- ->with($expected_path, $expected_content)
- ->andReturnTrue();
- });
-
- $this->artisan('generate:test', ['table' => 'categories', '--create' => true])
- ->assertSuccessful();
- }
-}
diff --git a/tests/Feature/Commands/GenerateViewsCommandTest.php b/tests/Feature/Commands/GenerateViewsCommandTest.php
deleted file mode 100644
index 8bfd5d3..0000000
--- a/tests/Feature/Commands/GenerateViewsCommandTest.php
+++ /dev/null
@@ -1,136 +0,0 @@
-deleteDirectory($this->app->resourcePath('views/admin'));
-
- // setup skeleton menu
- $this->makeDirectory($this->app->path('Menus/AdminSidebar.php'));
-
- $this->copyFile(
- $this->getTestStubPath('Menus/AdminSidebar.php'),
- $this->app->path('Menus/AdminSidebar.php')
- );
- }
-
- protected function tearDown(): void
- {
- // delete all views
- $this->deleteDirectory($this->app->resourcePath('views/admin'));
-
- // delete menus
- $this->deleteDirectory($this->app->path('Menus'));
-
- parent::tearDown();
- }
-
- public function test_it_can_generate_views_output(): void
- {
- $expected_content = '';
-
- $expected_content .= "// products.blade.php\n";
- $expected_content .= $this->getTestStubContents('views/products/products.blade.php');
-
- $expected_content .= "// _actions.blade.php\n";
- $expected_content .= $this->getTestStubContents('views/products/_actions.blade.php');
-
- $expected_content .= "// _form.blade.php\n";
- $expected_content .= $this->getTestStubContents('views/products/_form.blade.php');
-
- $expected_content .= "// create.blade.php\n";
- $expected_content .= $this->getTestStubContents('views/products/create.blade.php');
-
- $expected_content .= "// edit.blade.php\n";
- $expected_content .= $this->getTestStubContents('views/products/edit.blade.php');
-
- $expected_content .= "// _details.blade.php\n";
- $expected_content .= $this->getTestStubContents('views/products/_details.blade.php');
-
- $expected_content .= "// show.blade.php\n";
- $expected_content .= $this->getTestStubContents('views/products/show.blade.php');
-
- $expected_content .= "// _bulk.blade.php\n";
- $expected_content .= $this->getTestStubContents('views/products/_bulk.blade.php');
-
- $expected_content .= "// _list.blade.php\n";
- $expected_content .= $this->getTestStubContents('views/products/_list.blade.php');
-
- $expected_content .= "// _table.blade.php\n";
- $expected_content .= $this->getTestStubContents('views/products/_table.blade.php');
-
- $expected_content .= "// _filter.blade.php\n";
- $expected_content .= $this->getTestStubContents('views/products/_filter.blade.php');
-
- $expected_content .= "// index.blade.php\n";
- $expected_content .= $this->getTestStubContents('views/products/index.blade.php');
-
- $this->artisan('generate:views', ['table' => 'products'])
- ->expectsOutput($expected_content);
- }
-
- public function test_it_can_generate_new_view_files(): void
- {
- $views = [
- 'products.blade.php',
- '_actions.blade.php',
- '_form.blade.php',
- 'create.blade.php',
- 'edit.blade.php',
- '_details.blade.php',
- 'show.blade.php',
- '_bulk.blade.php',
- '_list.blade.php',
- '_table.blade.php',
- 'index.blade.php',
- ];
-
- $content = [];
-
- // load expected path and content
- foreach ($views as $view) {
- $content[$view] = [
- 'expected_path' => $this->app->resourcePath('views/admin/products/' . $view),
- 'expected_content' => $this->getTestStubContents('views/products/' . $view),
- ];
- }
-
- $this->artisan('generate:views', ['table' => 'products', '--create' => true])
- ->assertSuccessful();
-
- foreach ($content as $view => $test_data) {
- $this->assertFileExists($test_data['expected_path'], "$view not generated");
-
- $actual_content = $this->getGeneratedFileContents($test_data['expected_path']);
- $this->assertEquals($test_data['expected_content'], $actual_content, "$view content wrong");
- }
- }
-
- public function test_it_adds_sidebar_links(): void
- {
- $expected_path = $this->app->path('Menus/AdminSidebar.php');
- $expected_content = $this->getTestStubContents('Menus/ModelsAdminSidebar.php');
-
- $this->artisan('generate:views', ['table' => 'categories', '--create' => true])
- ->assertSuccessful();
-
- $this->artisan('generate:views', ['table' => 'products', '--create' => true])
- ->assertSuccessful();
-
- $this->assertFileExists($expected_path);
-
- $actual_content = $this->getGeneratedFileContents($expected_path);
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Feature/GeneratorSnapshotsTest.php b/tests/Feature/GeneratorSnapshotsTest.php
new file mode 100644
index 0000000..2f102ad
--- /dev/null
+++ b/tests/Feature/GeneratorSnapshotsTest.php
@@ -0,0 +1,92 @@
+, 2: array>}>
+ */
+ public static function resources(): array
+ {
+ $all = [
+ ModelGenerator::class,
+ FactoryGenerator::class,
+ PolicyGenerator::class,
+ PermissionsGenerator::class,
+ FiltersGenerator::class,
+ BulkActionsGenerator::class,
+ FormGenerator::class,
+ ControllerGenerator::class,
+ RoutesGenerator::class,
+ TestGenerator::class,
+ ];
+
+ return [
+ 'products, blade' => ['products', [], [...$all, BladeViewsGenerator::class, ExportGenerator::class]],
+ 'products, inertia' => ['products', ['views' => 'inertia'], [ControllerGenerator::class, ResourceGenerator::class, InertiaPagesGenerator::class, TestGenerator::class]],
+ 'categories, blade' => ['categories', [], [ModelGenerator::class, FiltersGenerator::class, BladeViewsGenerator::class]],
+ 'orders, enum class and a key to a slug' => ['orders', [], [ModelGenerator::class, FactoryGenerator::class, FormGenerator::class, TestGenerator::class]],
+ 'products nested in categories, blade' => ['products', ['parent' => 'categories', 'index' => 'name,price', 'form' => 'name,price,status,tags'], [ControllerGenerator::class, BulkActionsGenerator::class, RoutesGenerator::class, BladeViewsGenerator::class, TestGenerator::class]],
+ 'products nested in categories, inertia' => ['products', ['parent' => 'category_id', 'views' => 'inertia', 'index' => 'name,price', 'form' => 'name,price'], [ControllerGenerator::class, ResourceGenerator::class, InertiaPagesGenerator::class, TestGenerator::class]],
+ 'posts with media, blade' => ['posts', ['media' => 'featured_image:image,gallery:image:multiple,attachments:document:multiple'], [ModelGenerator::class, FormGenerator::class, BladeViewsGenerator::class]],
+ 'posts with media, inertia' => ['posts', ['media' => 'featured_image,attachments:document:multiple', 'views' => 'inertia'], [ResourceGenerator::class, InertiaPagesGenerator::class]],
+ 'tags, a pivot on the other side' => ['tags', [], [ModelGenerator::class, FormGenerator::class]],
+ ];
+ }
+
+ /**
+ * @param array $options
+ * @param array> $generators
+ */
+ #[Test]
+ #[DataProvider('resources')]
+ public function it_generates_the_code_in_the_snapshots(string $table, array $options, array $generators): void
+ {
+ if (config('database.default') !== 'sqlite') {
+ $this->markTestSkipped('The snapshots are of the SQLite schema; other drivers keep string lengths and comments.');
+ }
+
+ $resource = app(ResourceResolver::class)->resolve($table, ResourceOptions::fromArray($options));
+
+ $output = [];
+
+ foreach ($generators as $class) {
+ foreach ((new $class($resource))->files() as $file) {
+ $output[] = "// {$file->relativePath()}\n{$file->contents}";
+ }
+ }
+
+ $name = str_replace([', ', ' '], ['.', '-'], $this->dataName());
+
+ $this->assertMatchesSnapshot("{$name}.txt", implode("\n", $output));
+ }
+}
diff --git a/tests/Feature/TranslatableTest.php b/tests/Feature/TranslatableTest.php
new file mode 100644
index 0000000..ad68fe5
--- /dev/null
+++ b/tests/Feature/TranslatableTest.php
@@ -0,0 +1,107 @@
+resolve($table, ResourceOptions::fromArray($options));
+ }
+
+ #[Test]
+ public function it_makes_the_text_columns_translatable_when_the_package_is_installed(): void
+ {
+ Translations::fake(true);
+
+ $resource = $this->resolve('pages');
+
+ // unique columns such as slugs aren't translated
+ $this->assertEquals(['title', 'body'], $resource->translatable);
+
+ $model = (new ModelGenerator($resource))->renderModel();
+
+ $this->assertStringContainsString('class Page extends Model implements JsonTranslatable', $model);
+ $this->assertStringContainsString('use IsJsonTranslatable;', $model);
+ $this->assertStringContainsString("protected array \$translatable = ['title', 'body'];", $model);
+
+ $form = (new FormGenerator($resource))->renderForm();
+
+ $this->assertStringContainsString("TextInput::make('title')\n ->label(__('Title'))\n ->required()\n ->maxLength(255)\n ->translatable(),", $form);
+
+ $test = (new TestGenerator($resource))->renderTest();
+
+ $this->assertStringContainsString('use InteractsWithLanguages;', $test);
+ $this->assertStringContainsString("'title_dv' => 'ދިވެހި title',", $test);
+ }
+
+ #[Test]
+ public function it_writes_plain_models_without_the_package(): void
+ {
+ Translations::fake(false);
+
+ $resource = $this->resolve('pages');
+
+ $this->assertEquals([], $resource->translatable);
+ $this->assertArrayNotHasKey('translations', $resource->fields);
+ $this->assertArrayNotHasKey('lang', $resource->fields);
+
+ $this->assertStringNotContainsString('Translatable', (new ModelGenerator($resource))->renderModel());
+ $this->assertStringNotContainsString('translatable()', (new FormGenerator($resource))->renderForm());
+ $this->assertStringNotContainsString('InteractsWithLanguages', (new TestGenerator($resource))->renderTest());
+ }
+
+ #[Test]
+ public function it_skips_tables_without_the_translation_columns(): void
+ {
+ Translations::fake(true);
+
+ $this->assertEquals([], $this->resolve('posts')->translatable);
+ }
+
+ #[Test]
+ public function it_takes_the_translatable_columns_from_the_option(): void
+ {
+ Translations::fake(true);
+
+ $this->assertEquals(['title'], $this->resolve('pages', ['translatable' => 'title'])->translatable);
+ }
+
+ #[Test]
+ public function it_can_be_turned_off_in_config(): void
+ {
+ Translations::fake(true);
+
+ config()->set('generators.translatable', false);
+
+ $this->assertEquals([], $this->resolve('pages')->translatable);
+ }
+
+ #[Test]
+ public function it_explains_the_missing_columns(): void
+ {
+ Translations::fake(true);
+
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage("\$table->json('translations')->nullable();");
+
+ $this->resolve('posts', ['translatable' => 'title']);
+ }
+}
diff --git a/tests/TestSupport/Enums/OrderStatuses.php b/tests/Fixtures/Enums/OrderStatuses.php
similarity index 82%
rename from tests/TestSupport/Enums/OrderStatuses.php
rename to tests/Fixtures/Enums/OrderStatuses.php
index e11b58d..57053e8 100644
--- a/tests/TestSupport/Enums/OrderStatuses.php
+++ b/tests/Fixtures/Enums/OrderStatuses.php
@@ -1,6 +1,6 @@
timestamps();
});
}
+
/**
* Reverse the migrations.
*
diff --git a/tests/TestSupport/database/2024_04_28_144111_create_products_table.php b/tests/Fixtures/migrations/2024_04_28_144111_create_products_table.php
similarity index 99%
rename from tests/TestSupport/database/2024_04_28_144111_create_products_table.php
rename to tests/Fixtures/migrations/2024_04_28_144111_create_products_table.php
index b37d2de..94062fd 100644
--- a/tests/TestSupport/database/2024_04_28_144111_create_products_table.php
+++ b/tests/Fixtures/migrations/2024_04_28_144111_create_products_table.php
@@ -34,6 +34,7 @@ public function up()
$table->softDeletes();
});
}
+
/**
* Reverse the migrations.
*
diff --git a/tests/TestSupport/database/2024_04_28_144112_create_orders_table.php b/tests/Fixtures/migrations/2024_04_28_144112_create_orders_table.php
similarity index 85%
rename from tests/TestSupport/database/2024_04_28_144112_create_orders_table.php
rename to tests/Fixtures/migrations/2024_04_28_144112_create_orders_table.php
index a0fbfd9..5ffa114 100644
--- a/tests/TestSupport/database/2024_04_28_144112_create_orders_table.php
+++ b/tests/Fixtures/migrations/2024_04_28_144112_create_orders_table.php
@@ -3,7 +3,7 @@
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
-use Javaabu\Generators\Tests\TestSupport\Enums\OrderStatuses;
+use Javaabu\Generators\Tests\Fixtures\Enums\OrderStatuses;
return new class extends Migration
{
@@ -19,7 +19,7 @@ public function up()
$table->string('order_no', 4);
$table->foreignId('category_id')->constrained()->cascadeOnDelete();
$table->string('product_slug');
- $table->nativeEnum('status', OrderStatuses::class)->index();
+ $table->string('status')->comment('enum:' . OrderStatuses::class)->index();
$table->foreign('product_slug')
->references('slug')
@@ -29,6 +29,7 @@ public function up()
$table->timestamps();
});
}
+
/**
* Reverse the migrations.
*
diff --git a/tests/TestSupport/database/2024_04_28_144113_create_countries_table.php b/tests/Fixtures/migrations/2024_04_28_144113_create_countries_table.php
similarity index 99%
rename from tests/TestSupport/database/2024_04_28_144113_create_countries_table.php
rename to tests/Fixtures/migrations/2024_04_28_144113_create_countries_table.php
index 3db4a8f..bc287c5 100644
--- a/tests/TestSupport/database/2024_04_28_144113_create_countries_table.php
+++ b/tests/Fixtures/migrations/2024_04_28_144113_create_countries_table.php
@@ -20,6 +20,7 @@ public function up()
$table->timestamps();
});
}
+
/**
* Reverse the migrations.
*
diff --git a/tests/TestSupport/database/2024_04_28_144113_create_posts_table.php b/tests/Fixtures/migrations/2024_04_28_144113_create_posts_table.php
similarity index 99%
rename from tests/TestSupport/database/2024_04_28_144113_create_posts_table.php
rename to tests/Fixtures/migrations/2024_04_28_144113_create_posts_table.php
index c5efaa4..4c326ab 100644
--- a/tests/TestSupport/database/2024_04_28_144113_create_posts_table.php
+++ b/tests/Fixtures/migrations/2024_04_28_144113_create_posts_table.php
@@ -24,6 +24,7 @@ public function up()
$table->timestamps();
});
}
+
/**
* Reverse the migrations.
*
diff --git a/tests/TestSupport/database/2024_04_28_144114_create_payments_table.php b/tests/Fixtures/migrations/2024_04_28_144114_create_payments_table.php
similarity index 99%
rename from tests/TestSupport/database/2024_04_28_144114_create_payments_table.php
rename to tests/Fixtures/migrations/2024_04_28_144114_create_payments_table.php
index 3c25c9b..0967bb6 100644
--- a/tests/TestSupport/database/2024_04_28_144114_create_payments_table.php
+++ b/tests/Fixtures/migrations/2024_04_28_144114_create_payments_table.php
@@ -21,6 +21,7 @@ public function up()
$table->softDeletes();
});
}
+
/**
* Reverse the migrations.
*
diff --git a/tests/TestSupport/database/2024_04_28_144115_create_certificates_table.php b/tests/Fixtures/migrations/2024_04_28_144115_create_certificates_table.php
similarity index 99%
rename from tests/TestSupport/database/2024_04_28_144115_create_certificates_table.php
rename to tests/Fixtures/migrations/2024_04_28_144115_create_certificates_table.php
index ee88e7d..bfbfb6e 100644
--- a/tests/TestSupport/database/2024_04_28_144115_create_certificates_table.php
+++ b/tests/Fixtures/migrations/2024_04_28_144115_create_certificates_table.php
@@ -22,6 +22,7 @@ public function up()
$table->softDeletes();
});
}
+
/**
* Reverse the migrations.
*
diff --git a/tests/TestSupport/database/2024_04_28_144116_create_all_foreigns_table.php b/tests/Fixtures/migrations/2024_04_28_144116_create_all_foreigns_table.php
similarity index 99%
rename from tests/TestSupport/database/2024_04_28_144116_create_all_foreigns_table.php
rename to tests/Fixtures/migrations/2024_04_28_144116_create_all_foreigns_table.php
index 3892935..2e49f68 100644
--- a/tests/TestSupport/database/2024_04_28_144116_create_all_foreigns_table.php
+++ b/tests/Fixtures/migrations/2024_04_28_144116_create_all_foreigns_table.php
@@ -26,6 +26,7 @@ public function up()
$table->timestamps();
});
}
+
/**
* Reverse the migrations.
*
diff --git a/tests/TestSupport/database/2024_04_28_144117_create_customers_table.php b/tests/Fixtures/migrations/2024_04_28_144117_create_customers_table.php
similarity index 99%
rename from tests/TestSupport/database/2024_04_28_144117_create_customers_table.php
rename to tests/Fixtures/migrations/2024_04_28_144117_create_customers_table.php
index e978569..96b4e72 100644
--- a/tests/TestSupport/database/2024_04_28_144117_create_customers_table.php
+++ b/tests/Fixtures/migrations/2024_04_28_144117_create_customers_table.php
@@ -35,6 +35,7 @@ public function up()
$table->foreignId('category_id')->nullable()->constrained()->nullOnDelete();
});
}
+
/**
* Reverse the migrations.
*
diff --git a/tests/TestSupport/database/2024_04_28_144118_create_public_users_table.php b/tests/Fixtures/migrations/2024_04_28_144118_create_public_users_table.php
similarity index 99%
rename from tests/TestSupport/database/2024_04_28_144118_create_public_users_table.php
rename to tests/Fixtures/migrations/2024_04_28_144118_create_public_users_table.php
index 293d1f4..f3108b2 100644
--- a/tests/TestSupport/database/2024_04_28_144118_create_public_users_table.php
+++ b/tests/Fixtures/migrations/2024_04_28_144118_create_public_users_table.php
@@ -29,6 +29,7 @@ public function up()
$table->softDeletes();
});
}
+
/**
* Reverse the migrations.
*
diff --git a/tests/Fixtures/migrations/2024_04_28_144119_create_tags_table.php b/tests/Fixtures/migrations/2024_04_28_144119_create_tags_table.php
new file mode 100644
index 0000000..9837a60
--- /dev/null
+++ b/tests/Fixtures/migrations/2024_04_28_144119_create_tags_table.php
@@ -0,0 +1,29 @@
+id();
+ $table->string('name');
+ $table->timestamps();
+ });
+
+ Schema::create('product_tag', function (Blueprint $table) {
+ $table->foreignId('product_id')->constrained()->cascadeOnDelete();
+ $table->foreignId('tag_id')->constrained()->cascadeOnDelete();
+ $table->primary(['product_id', 'tag_id']);
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::dropIfExists('product_tag');
+ Schema::dropIfExists('tags');
+ }
+};
diff --git a/tests/Fixtures/migrations/2024_04_28_144120_create_pages_table.php b/tests/Fixtures/migrations/2024_04_28_144120_create_pages_table.php
new file mode 100644
index 0000000..a0ea094
--- /dev/null
+++ b/tests/Fixtures/migrations/2024_04_28_144120_create_pages_table.php
@@ -0,0 +1,26 @@
+id();
+ $table->string('title');
+ $table->string('slug')->unique();
+ $table->text('body')->nullable();
+ $table->json('translations')->nullable();
+ $table->string('lang')->nullable()->index();
+ $table->timestamps();
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::dropIfExists('pages');
+ }
+};
diff --git a/tests/TestSupport/database/2025_03_27_212600_create_single_value_enum_orders_table.php b/tests/Fixtures/migrations/2025_03_27_212600_create_single_value_enum_orders_table.php
similarity index 84%
rename from tests/TestSupport/database/2025_03_27_212600_create_single_value_enum_orders_table.php
rename to tests/Fixtures/migrations/2025_03_27_212600_create_single_value_enum_orders_table.php
index 06515ac..2b339c1 100644
--- a/tests/TestSupport/database/2025_03_27_212600_create_single_value_enum_orders_table.php
+++ b/tests/Fixtures/migrations/2025_03_27_212600_create_single_value_enum_orders_table.php
@@ -3,7 +3,7 @@
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
-use Javaabu\Generators\Tests\TestSupport\Enums\SingleCaseOrderStatuses;
+use Javaabu\Generators\Tests\Fixtures\Enums\SingleCaseOrderStatuses;
return new class extends Migration
{
@@ -19,7 +19,7 @@ public function up()
$table->string('order_no', 4);
$table->foreignId('category_id')->constrained()->cascadeOnDelete();
$table->string('product_slug');
- $table->nativeEnum('status', SingleCaseOrderStatuses::class)->index();
+ $table->string('status')->comment('enum:' . SingleCaseOrderStatuses::class)->index();
$table->foreign('product_slug')
->references('slug')
@@ -29,6 +29,7 @@ public function up()
$table->timestamps();
});
}
+
/**
* Reverse the migrations.
*
diff --git a/tests/TestCase.php b/tests/TestCase.php
index 77e564e..9e2678a 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -2,48 +2,90 @@
namespace Javaabu\Generators\Tests;
-use Javaabu\GeneratorHelpers\Testing\InteractsWithTestFiles;
-use Javaabu\GeneratorHelpers\Testing\InteractsWithTestStubs;
+use Illuminate\Foundation\Testing\RefreshDatabase;
use Javaabu\Generators\GeneratorsServiceProvider;
-use Javaabu\Generators\Tests\TestSupport\Providers\TestServiceProvider;
-use Javaabu\Schema\SchemaServiceProvider;
+use Javaabu\Generators\Tests\Fixtures\Enums\OrderStatuses;
+use Javaabu\Generators\Tests\Fixtures\Enums\SingleCaseOrderStatuses;
use Orchestra\Testbench\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
- use InteractsWithTestStubs;
- use InteractsWithTestFiles;
+ use RefreshDatabase;
- protected function setUp(): void
+ protected function getPackageProviders($app): array
{
- parent::setUp();
-
- $this->app['config']->set('app.key', 'base64:yWa/ByhLC/GUvfToOuaPD7zDwB64qkc/QkaQOrT5IpE=');
+ return [
+ GeneratorsServiceProvider::class,
+ ];
+ }
- $this->app['config']->set('session.serialization', 'php');
+ protected function defineEnvironment($app): void
+ {
+ $app['config']->set('app.key', 'base64:yWa/ByhLC/GUvfToOuaPD7zDwB64qkc/QkaQOrT5IpE=');
- $this->app['config']->set('database.default', 'mysql');
+ $driver = env('DB_CONNECTION', 'sqlite');
- $this->app['config']->set('database.connections.mysql', [
- 'driver' => 'mysql',
- 'database' => env('DB_DATABASE'),
- 'host' => env('DB_HOST', '127.0.0.1'),
- 'port' => env('DB_PORT', '3306'),
- 'username' => env('DB_USERNAME'),
- 'password' => env('DB_PASSWORD', ''),
- 'prefix' => '',
- ]);
+ $connections = [
+ 'sqlite' => [
+ 'driver' => 'sqlite',
+ 'database' => ':memory:',
+ 'prefix' => '',
+ 'foreign_key_constraints' => true,
+ ],
+ 'mysql' => [
+ 'driver' => 'mysql',
+ 'host' => env('DB_HOST', '127.0.0.1'),
+ 'port' => env('DB_PORT', '3306'),
+ 'database' => env('DB_DATABASE', 'generators'),
+ 'username' => env('DB_USERNAME', 'root'),
+ 'password' => env('DB_PASSWORD', ''),
+ 'charset' => 'utf8mb4',
+ 'collation' => 'utf8mb4_unicode_ci',
+ 'prefix' => '',
+ ],
+ 'mariadb' => [
+ 'driver' => 'mariadb',
+ 'host' => env('DB_HOST', '127.0.0.1'),
+ 'port' => env('DB_PORT', '3306'),
+ 'database' => env('DB_DATABASE', 'generators'),
+ 'username' => env('DB_USERNAME', 'root'),
+ 'password' => env('DB_PASSWORD', ''),
+ 'charset' => 'utf8mb4',
+ 'collation' => 'utf8mb4_unicode_ci',
+ 'prefix' => '',
+ ],
+ 'pgsql' => [
+ 'driver' => 'pgsql',
+ 'host' => env('DB_HOST', '127.0.0.1'),
+ 'port' => env('DB_PORT', '5432'),
+ 'database' => env('DB_DATABASE', 'generators'),
+ 'username' => env('DB_USERNAME', 'postgres'),
+ 'password' => env('DB_PASSWORD', ''),
+ 'charset' => 'utf8',
+ 'prefix' => '',
+ 'search_path' => 'public',
+ ],
+ ];
- $this->loadTestStubsFrom(__DIR__.'/stubs');
+ $app['config']->set('database.default', $driver);
+ $app['config']->set("database.connections.{$driver}", $connections[$driver]);
+ // SQLite keeps no column comments, so name the enum classes in config
+ $app['config']->set('generators.enums', [
+ 'orders.status' => OrderStatuses::class,
+ 'single_value_enum_orders.status' => SingleCaseOrderStatuses::class,
+ ]);
}
- protected function getPackageProviders($app)
+ /**
+ * @return array
+ */
+ protected function migrateFreshUsing(): array
{
return [
- SchemaServiceProvider::class,
- GeneratorsServiceProvider::class,
- TestServiceProvider::class
+ '--path' => __DIR__ . '/Fixtures/migrations',
+ '--realpath' => true,
+ '--drop-views' => true,
];
}
}
diff --git a/tests/TestSupport/Providers/TestServiceProvider.php b/tests/TestSupport/Providers/TestServiceProvider.php
deleted file mode 100644
index 2a1ff93..0000000
--- a/tests/TestSupport/Providers/TestServiceProvider.php
+++ /dev/null
@@ -1,24 +0,0 @@
-loadMigrationsFrom(__DIR__ . '/../database');
- }
-
- /**
- * Register the application services.
- */
- public function register()
- {
-
- }
-}
diff --git a/tests/Unit/BooleanFieldTest.php b/tests/Unit/BooleanFieldTest.php
deleted file mode 100644
index 4fe73f5..0000000
--- a/tests/Unit/BooleanFieldTest.php
+++ /dev/null
@@ -1,19 +0,0 @@
-assertEquals('on_sale = $request->input(\'on_sale\', false)', (new BooleanField('on_sale'))->renderAssignment());
- }
-
- public function test_it_can_generate_boolean_assignment_from_array(): void
- {
- $this->assertEquals('on_sale = $input[\'on_sale\'] ?? false', (new BooleanField('on_sale'))->renderAssignment('$input[', ']'));
- }
-}
diff --git a/tests/Unit/DecimalFieldTest.php b/tests/Unit/DecimalFieldTest.php
deleted file mode 100644
index 303b920..0000000
--- a/tests/Unit/DecimalFieldTest.php
+++ /dev/null
@@ -1,17 +0,0 @@
-assertEquals('0.01', (new DecimalField('price', 14, 2))->getStep());
- $this->assertEquals('1', (new DecimalField('price', 14, 0))->getStep());
- $this->assertEquals('0.001', (new DecimalField('price', 14, 3))->getStep());
- $this->assertEquals('0.1', (new DecimalField('price', 14, 1))->getStep());
- }
-}
diff --git a/tests/Unit/DocsTest.php b/tests/Unit/DocsTest.php
new file mode 100644
index 0000000..8e1b814
--- /dev/null
+++ b/tests/Unit/DocsTest.php
@@ -0,0 +1,50 @@
+root() . '/llms.txt');
+
+ preg_match_all('/\]\((docs\/[^)]+)\)/', $llms, $matches);
+
+ foreach ($matches[1] as $link) {
+ $this->assertFileExists($this->root() . '/' . $link, "llms.txt links to a missing page: {$link}");
+ }
+
+ $skip = ['docs/about-us.md', 'docs/changelog.md', 'docs/contributing-to-docs.md'];
+
+ foreach (glob($this->root() . '/docs/{,*/}*.md', GLOB_BRACE) as $page) {
+ $relative = substr($page, strlen($this->root()) + 1);
+
+ if (in_array($relative, $skip, true)) {
+ continue;
+ }
+
+ $this->assertContains($relative, $matches[1], "llms.txt doesn't link to {$relative}");
+ }
+ }
+
+ #[Test]
+ public function it_links_to_existing_pages_from_the_docs(): void
+ {
+ foreach (glob($this->root() . '/docs/{,*/}*.md', GLOB_BRACE) as $page) {
+ preg_match_all('/\]\(((?!https?:|#|\.\.\/\.\.\/)[^)#]+\.md)(#[^)]*)?\)/', file_get_contents($page), $matches);
+
+ foreach ($matches[1] as $link) {
+ $this->assertFileExists(dirname($page) . '/' . $link, basename($page) . " links to a missing page: {$link}");
+ }
+ }
+ }
+}
diff --git a/tests/Unit/FieldTest.php b/tests/Unit/FieldTest.php
deleted file mode 100644
index 83fe5ac..0000000
--- a/tests/Unit/FieldTest.php
+++ /dev/null
@@ -1,37 +0,0 @@
- 255];
- }
-}
-
-class FieldTest extends TestCase
-{
- public function test_it_can_render_field_assignment(): void
- {
- $this->assertEquals('slug = $request->input(\'slug\')', (new FakeStringField('slug'))->renderAssignment());
- }
-
- public function test_it_can_render_field_attributes(): void
- {
- $this->assertEquals('maxlength="255" required :inline="false" show-placeholder', (new FakeStringField('slug'))->renderFormComponentAttributes());
- }
-
- public function test_it_can_render_a_component(): void
- {
- $this->assertEquals('', (new FakeStringField('slug'))->renderFormComponent());
- }
-}
diff --git a/tests/Unit/ForeignKeyFieldTest.php b/tests/Unit/ForeignKeyFieldTest.php
deleted file mode 100644
index 65a5ca0..0000000
--- a/tests/Unit/ForeignKeyFieldTest.php
+++ /dev/null
@@ -1,55 +0,0 @@
-assertEquals('Category', (new ForeignKeyField('category_id', 'categories', 'id'))->getRelatedModelClass());
- $this->assertEquals('PostType', (new ForeignKeyField('post_type_id', 'post_types', 'id'))->getRelatedModelClass());
- $this->assertEquals('FormInputCategory', (new ForeignKeyField('form_input_category_id', 'form_input_categories', 'id'))->getRelatedModelClass());
- }
-
- public function test_it_can_generate_foreign_key_field_assignment_statement(): void
- {
- $this->assertEquals('category()->associate($request->input(\'category\'))', (new ForeignKeyField('category_id', 'categories', 'id'))->renderAssignment());
- }
-
- public function test_it_can_generate_the_correct_relation_name(): void
- {
- $this->assertEquals('category', (new ForeignKeyField('category_id', 'categories', 'id'))->getRelationName());
- $this->assertEquals('postType', (new ForeignKeyField('post_type_id', 'post_types', 'id'))->getRelationName());
- $this->assertEquals('formInputCategory', (new ForeignKeyField('form_input_category_id', 'form_input_categories', 'id'))->getRelationName());
- $this->assertEquals('nationality', (new ForeignKeyField('nationality', 'countries', 'code'))->getRelationName());
- $this->assertEquals('productSlug', (new ForeignKeyField('product_slug', 'products', 'slug'))->getRelationName());
- $this->assertEquals('id', (new ForeignKeyField('_id', 'countries', 'code'))->getRelationName());
- }
-
- public function test_it_can_generate_the_correct_input_name(): void
- {
- $this->assertEquals('category', (new ForeignKeyField('category_id', 'categories', 'id'))->getInputName());
- $this->assertEquals('post_type', (new ForeignKeyField('post_type_id', 'post_types', 'id'))->getInputName());
- $this->assertEquals('form_input_category', (new ForeignKeyField('form_input_category_id', 'form_input_categories', 'id'))->getInputName());
- $this->assertEquals('nationality', (new ForeignKeyField('nationality', 'countries', 'code'))->getInputName());
- $this->assertEquals('_id', (new ForeignKeyField('_id', 'countries', 'code'))->getInputName());
- }
-
- public function test_it_can_generate_the_correct_model_creation_statement(): void
- {
- $this->assertEquals('$new_category = $this->getFactory(Category::class)->create();', (new ForeignKeyField('category_id', 'categories', 'id'))->generateTestFactoryStatement('new_'));
- $this->assertEquals('$old_nationality = $this->getFactory(Country::class)->create();', (new ForeignKeyField('nationality', 'countries', 'code'))->generateTestFactoryStatement('old_'));
- }
-
- public function test_it_can_generate_the_correct_relation_statement(): void
- {
- $this->assertEquals('belongsTo(Category::class)', (new ForeignKeyField('category_id', 'categories', 'id'))->generateRelationStatement());
- $this->assertEquals('belongsTo(PostType::class)', (new ForeignKeyField('post_type_id', 'post_types', 'id'))->generateRelationStatement());
- $this->assertEquals('belongsTo(FormInputCategory::class)', (new ForeignKeyField('form_input_category_id', 'form_input_categories', 'id'))->generateRelationStatement());
- $this->assertEquals('belongsTo(Country::class, \'nationality\', \'code\')', (new ForeignKeyField('nationality', 'countries', 'code'))->generateRelationStatement());
- $this->assertEquals('belongsTo(Country::class, \'nationality\')', (new ForeignKeyField('nationality', 'countries', 'id'))->generateRelationStatement());
- }
-}
diff --git a/tests/Unit/Generators/ApiControllerGeneratorTest.php b/tests/Unit/Generators/ApiControllerGeneratorTest.php
deleted file mode 100644
index 94b7780..0000000
--- a/tests/Unit/Generators/ApiControllerGeneratorTest.php
+++ /dev/null
@@ -1,42 +0,0 @@
-getTestStubContents('Controllers/Api/CategoriesController.php');
- $actual_content = $api_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_an_api_controller_with_foreign_keys(): void
- {
- $api_generator = new ApiControllerGenerator('products');
-
- $expected_content = $this->getTestStubContents('Controllers/Api/ProductsController.php');
- $actual_content = $api_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_api_routes(): void
- {
- $api_generator = new ApiControllerGenerator('categories');
-
- $expected_content = $this->getTestStubContents('routes/_api.stub');
- $actual_content = $api_generator->renderRoutes();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Unit/Generators/ApiTestGeneratorTest.php b/tests/Unit/Generators/ApiTestGeneratorTest.php
deleted file mode 100644
index fdd6aff..0000000
--- a/tests/Unit/Generators/ApiTestGeneratorTest.php
+++ /dev/null
@@ -1,22 +0,0 @@
-getTestStubContents('tests/Api/CategoriesControllerTest.stub');
- $actual_content = $api_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Unit/Generators/Auth/AuthConfigGeneratorTest.php b/tests/Unit/Generators/Auth/AuthConfigGeneratorTest.php
deleted file mode 100644
index 0d1e787..0000000
--- a/tests/Unit/Generators/Auth/AuthConfigGeneratorTest.php
+++ /dev/null
@@ -1,88 +0,0 @@
-getTestStubContents('config/auth/_guards.stub');
- $actual_content = $config_generator->renderGuards();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_auth_providers_config(): void
- {
- $config_generator = new AuthConfigGenerator('customers');
-
- $expected_content = $this->getTestStubContents('config/auth/_providers.stub');
- $actual_content = $config_generator->renderProviders();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_auth_passwords_config(): void
- {
- $config_generator = new AuthConfigGenerator('customers');
-
- $expected_content = $this->getTestStubContents('config/auth/_passwords.stub');
- $actual_content = $config_generator->renderPasswords();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_auth_passport_guards_config(): void
- {
- $config_generator = new AuthConfigGenerator('customers');
-
- $expected_content = $this->getTestStubContents('config/auth/_passport-guards.stub');
- $actual_content = $config_generator->renderPassportGuards();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_auth_default_guard_config(): void
- {
- $config_generator = new AuthConfigGenerator('customers');
-
- $expected_content = $this->getTestStubContents('config/auth/_default-guard.stub');
- $actual_content = $config_generator->renderDefaultGuard();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_auth_default_passwords_config(): void
- {
- $config_generator = new AuthConfigGenerator('customers');
-
- $expected_content = $this->getTestStubContents('config/auth/_default-passwords.stub');
- $actual_content = $config_generator->renderDefaultPasswords();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_determine_whether_default_guard_should_be_generated(): void
- {
- $config_generator = new AuthConfigGenerator('customers');
-
- $this->assertTrue($config_generator->shouldRenderDefaultGuard());
-
- Config::set('auth.defaults.guard', 'web_admin');
-
- $this->assertTrue($config_generator->shouldRenderDefaultGuard());
-
- Config::set('auth.defaults.guard', 'web_customer');
-
- $this->assertFalse($config_generator->shouldRenderDefaultGuard());
- }
-}
diff --git a/tests/Unit/Generators/Auth/AuthFactoryGeneratorTest.php b/tests/Unit/Generators/Auth/AuthFactoryGeneratorTest.php
deleted file mode 100644
index b8adf4a..0000000
--- a/tests/Unit/Generators/Auth/AuthFactoryGeneratorTest.php
+++ /dev/null
@@ -1,22 +0,0 @@
-getTestStubContents('factories/CustomerFactory.php');
- $actual_content = $factory_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Unit/Generators/Auth/AuthModelGeneratorTest.php b/tests/Unit/Generators/Auth/AuthModelGeneratorTest.php
deleted file mode 100644
index 14bc3db..0000000
--- a/tests/Unit/Generators/Auth/AuthModelGeneratorTest.php
+++ /dev/null
@@ -1,32 +0,0 @@
-getTestStubContents('Models/Customer.php');
- $actual_content = $model_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_an_auth_model_without_foreign_keys(): void
- {
- $model_generator = new AuthModelGenerator('public_users', auth_name: 'portal');
-
- $expected_content = $this->getTestStubContents('Models/PublicUser.php');
- $actual_content = $model_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Unit/Generators/Auth/AuthPasswordResetsGeneratorTest.php b/tests/Unit/Generators/Auth/AuthPasswordResetsGeneratorTest.php
deleted file mode 100644
index 4604fbf..0000000
--- a/tests/Unit/Generators/Auth/AuthPasswordResetsGeneratorTest.php
+++ /dev/null
@@ -1,31 +0,0 @@
-travelTo('2024-04-23 12:43:31');
-
- $migration_generator = new AuthPasswordResetsGenerator('customers');
-
- $this->assertEquals('2024_04_23_124331_create_customer_password_reset_tokens_table', $migration_generator->getPasswordResetsMigrationName());
- }
-
- public function test_it_can_generate_a_password_resets_migration(): void
- {
- $migration_generator = new AuthPasswordResetsGenerator('customers');
-
- $expected_content = $this->getTestStubContents('migrations/create_customer_password_reset_tokens_table.php');
- $actual_content = $migration_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Unit/Generators/Auth/AuthPermissionsGeneratorTest.php b/tests/Unit/Generators/Auth/AuthPermissionsGeneratorTest.php
deleted file mode 100644
index b11d20f..0000000
--- a/tests/Unit/Generators/Auth/AuthPermissionsGeneratorTest.php
+++ /dev/null
@@ -1,22 +0,0 @@
-getTestStubContents('seeders/_authPermissions.stub');
- $actual_content = $permissions_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Unit/Generators/Auth/AuthPolicyGeneratorTest.php b/tests/Unit/Generators/Auth/AuthPolicyGeneratorTest.php
deleted file mode 100644
index 01c40a5..0000000
--- a/tests/Unit/Generators/Auth/AuthPolicyGeneratorTest.php
+++ /dev/null
@@ -1,22 +0,0 @@
-getTestStubContents('Policies/CustomerPolicy.php');
- $actual_content = $policy_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Unit/Generators/Auth/AuthRequestGeneratorTest.php b/tests/Unit/Generators/Auth/AuthRequestGeneratorTest.php
deleted file mode 100644
index d3fc492..0000000
--- a/tests/Unit/Generators/Auth/AuthRequestGeneratorTest.php
+++ /dev/null
@@ -1,43 +0,0 @@
-assertEquals('$this->baseRules()', $request_generator->renderBaseRulesStatement());
-
- $request_generator = new AuthRequestGenerator('public_users');
-
- $this->assertEquals('$this->baseRules(false, false)', $request_generator->renderBaseRulesStatement());
- }
-
- public function test_it_can_generate_an_auth_model_request_with_one_unique_rule(): void
- {
- $request_generator = new AuthRequestGenerator('customers');
-
- $expected_content = $this->getTestStubContents('Requests/CustomersRequest.php');
- $actual_content = $request_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_an_auth_model_request_with_no_unique_rules(): void
- {
- $request_generator = new AuthRequestGenerator('public_users', auth_name: 'portal');
-
- $expected_content = $this->getTestStubContents('Requests/PublicUsersRequest.php');
- $actual_content = $request_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Unit/Generators/Auth/BaseAuthGeneratorTest.php b/tests/Unit/Generators/Auth/BaseAuthGeneratorTest.php
deleted file mode 100644
index dbb9e85..0000000
--- a/tests/Unit/Generators/Auth/BaseAuthGeneratorTest.php
+++ /dev/null
@@ -1,195 +0,0 @@
-deleteFile($this->app->databasePath('seeders/DefaultUsersSeeder.php'));
- }
-
- protected function tearDown(): void
- {
- $this->deleteFile($this->app->databasePath('seeders/DefaultUsersSeeder.php'));
-
- parent::tearDown();
- }
-
- public function test_it_can_determine_if_the_table_has_additional_attributes(): void
- {
- $generator = new MockAuthBaseGenerator('customers');
-
- $this->assertTrue($generator->hasAdditionalAttributes());
-
- $generator = new MockAuthBaseGenerator('public_users');
-
- $this->assertFalse($generator->hasAdditionalAttributes());
- }
-
- public function test_it_can_determine_if_email_is_required(): void
- {
- $generator = new MockAuthBaseGenerator('customers');
-
- $this->assertTrue($generator->requiresEmail());
-
- $generator = new MockAuthBaseGenerator('public_users');
-
- $this->assertFalse($generator->requiresEmail());
- }
-
- public function test_it_can_determine_if_password_is_required(): void
- {
- $generator = new MockAuthBaseGenerator('customers');
-
- $this->assertTrue($generator->requiresPassword());
-
- $generator = new MockAuthBaseGenerator('public_users');
-
- $this->assertFalse($generator->requiresPassword());
- }
-
- public function test_it_can_extract_default_password_from_default_users_seeder(): void
- {
- $seeder_path = $this->app->databasePath('seeders/DefaultUsersSeeder.php');
-
- $this->copyFile($this->getTestStubPath('seeders/DefaultUsersSeeder.php'), $seeder_path);
-
- $generator = new MockAuthBaseGenerator('customers');
-
- $this->assertEquals('RandomPassword12345', $generator->getDefaultPassword());
- }
-
- public function test_it_can_generate_default_password_from_table_name(): void
- {
- $generator = new MockAuthBaseGenerator('customers');
-
- $this->assertEquals('Customer@123456', $generator->getDefaultPassword());
-
- }
-
- public function test_it_can_generate_auth_name_from_table_name(): void
- {
- $generator = new MockAuthBaseGenerator('customers');
-
- $this->assertEquals('customer', $generator->getAuthName());
- $this->assertEquals('Customer', $generator->getNamespace());
- $this->assertEquals('web_customer', $generator->getWebGuardName());
- $this->assertEquals('api_customer', $generator->getApiGuardName());
-
- }
-
- public function test_it_can_generate_custom_auth_name(): void
- {
- $generator = new MockAuthBaseGenerator('customers', auth_name: 'Portal');
-
- $this->assertEquals('portal', $generator->getAuthName());
- $this->assertEquals('Portal', $generator->getNamespace());
- $this->assertEquals('web_customer', $generator->getWebGuardName());
- $this->assertEquals('api_customer', $generator->getApiGuardName());
-
- }
-
- public function test_it_can_remove_auth_columns_even_if_no_columns_are_provided(): void
- {
- $generator = new MockAuthBaseGenerator('customers');
-
- $skip_columns = [
- 'name',
- 'email',
- 'email_verified_at',
- 'password',
- 'remember_token',
- 'last_login_at',
- 'login_attempts',
- 'require_password_update',
- 'status',
- 'new_email',
- 'created_at',
- 'updated_at',
- 'deleted_at'
- ];
-
- foreach ($skip_columns as $key) {
- $this->assertArrayNotHasKey($key, $generator->getFields());
- }
-
- $extra_columns = [
- 'designation',
- 'address',
- 'on_sale',
- 'expire_at',
- ];
-
- foreach ($extra_columns as $key) {
- $this->assertArrayHasKey($key, $generator->getFields());
- }
- }
-
- public function test_it_can_remove_auth_columns_from_the_columns_that_are_provided(): void
- {
- $extra_columns = [
- 'address',
- 'on_sale',
- ];
-
- $generator = new MockAuthBaseGenerator('customers', $extra_columns);
-
- $skip_columns = [
- 'designation',
- 'expire_at',
- ];
-
- foreach ($skip_columns as $key) {
- $this->assertArrayNotHasKey($key, $generator->getFields());
- }
-
- foreach ($extra_columns as $key) {
- $this->assertArrayHasKey($key, $generator->getFields());
- }
- }
-
- public function test_it_can_determine_what_is_an_auth_column(): void
- {
- $generator = new MockAuthBaseGenerator('customers');
-
- $this->assertTrue($generator->isAuthColumn('name'));
- $this->assertFalse($generator->isAuthColumn('designation'));
-
- Config::set('generators.auth_skip_columns', ['email']);
-
- $this->assertFalse($generator->isAuthColumn('name'));
- }
-
-
-}
diff --git a/tests/Unit/Generators/Auth/Controllers/AuthConfirmPasswordControllerGeneratorTest.php b/tests/Unit/Generators/Auth/Controllers/AuthConfirmPasswordControllerGeneratorTest.php
deleted file mode 100644
index c7ae1e7..0000000
--- a/tests/Unit/Generators/Auth/Controllers/AuthConfirmPasswordControllerGeneratorTest.php
+++ /dev/null
@@ -1,22 +0,0 @@
-getTestStubContents('Controllers/Customer/Auth/ConfirmPasswordController.php');
- $actual_content = $controller_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Unit/Generators/Auth/Controllers/AuthControllerGeneratorTest.php b/tests/Unit/Generators/Auth/Controllers/AuthControllerGeneratorTest.php
deleted file mode 100644
index 2697a6f..0000000
--- a/tests/Unit/Generators/Auth/Controllers/AuthControllerGeneratorTest.php
+++ /dev/null
@@ -1,22 +0,0 @@
-getTestStubContents('Controllers/CustomersController.php');
- $actual_content = $controller_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Unit/Generators/Auth/Controllers/AuthForgotPasswordControllerGeneratorTest.php b/tests/Unit/Generators/Auth/Controllers/AuthForgotPasswordControllerGeneratorTest.php
deleted file mode 100644
index c75252d..0000000
--- a/tests/Unit/Generators/Auth/Controllers/AuthForgotPasswordControllerGeneratorTest.php
+++ /dev/null
@@ -1,22 +0,0 @@
-getTestStubContents('Controllers/Customer/Auth/ForgotPasswordController.php');
- $actual_content = $controller_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Unit/Generators/Auth/Controllers/AuthHomeControllerGeneratorTest.php b/tests/Unit/Generators/Auth/Controllers/AuthHomeControllerGeneratorTest.php
deleted file mode 100644
index 2a08e69..0000000
--- a/tests/Unit/Generators/Auth/Controllers/AuthHomeControllerGeneratorTest.php
+++ /dev/null
@@ -1,22 +0,0 @@
-getTestStubContents('Controllers/Customer/HomeController.php');
- $actual_content = $controller_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Unit/Generators/Auth/Controllers/AuthLoginControllerGeneratorTest.php b/tests/Unit/Generators/Auth/Controllers/AuthLoginControllerGeneratorTest.php
deleted file mode 100644
index 3254f31..0000000
--- a/tests/Unit/Generators/Auth/Controllers/AuthLoginControllerGeneratorTest.php
+++ /dev/null
@@ -1,22 +0,0 @@
-getTestStubContents('Controllers/Customer/Auth/LoginController.php');
- $actual_content = $controller_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Unit/Generators/Auth/Controllers/AuthRegisterControllerGeneratorTest.php b/tests/Unit/Generators/Auth/Controllers/AuthRegisterControllerGeneratorTest.php
deleted file mode 100644
index 0099981..0000000
--- a/tests/Unit/Generators/Auth/Controllers/AuthRegisterControllerGeneratorTest.php
+++ /dev/null
@@ -1,42 +0,0 @@
-getTestStubContents('Controllers/Customer/Auth/RegisterController.php');
- $actual_content = $controller_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_register_assignments(): void
- {
- $controller_generator = new AuthRegisterControllerGenerator('customers');
-
- $expected_content = '$customer->category()->associate($data[\'category\']);';
- $actual_content = $controller_generator->renderRegisterAssignment('category_id');
-
- $this->assertEquals($expected_content, $actual_content);
-
- $expected_content = '$customer->on_sale = $data[\'on_sale\'] ?? false;';
- $actual_content = $controller_generator->renderRegisterAssignment('on_sale');
-
- $this->assertEquals($expected_content, $actual_content);
-
- $expected_content = '$customer->designation = $data[\'designation\'];';
- $actual_content = $controller_generator->renderRegisterAssignment('designation');
-
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Unit/Generators/Auth/Controllers/AuthResetPasswordControllerGeneratorTest.php b/tests/Unit/Generators/Auth/Controllers/AuthResetPasswordControllerGeneratorTest.php
deleted file mode 100644
index b89525f..0000000
--- a/tests/Unit/Generators/Auth/Controllers/AuthResetPasswordControllerGeneratorTest.php
+++ /dev/null
@@ -1,22 +0,0 @@
-getTestStubContents('Controllers/Customer/Auth/ResetPasswordController.php');
- $actual_content = $controller_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Unit/Generators/Auth/Controllers/AuthUpdatePasswordControllerGeneratorTest.php b/tests/Unit/Generators/Auth/Controllers/AuthUpdatePasswordControllerGeneratorTest.php
deleted file mode 100644
index 634f6ee..0000000
--- a/tests/Unit/Generators/Auth/Controllers/AuthUpdatePasswordControllerGeneratorTest.php
+++ /dev/null
@@ -1,22 +0,0 @@
-getTestStubContents('Controllers/Customer/Auth/UpdatePasswordController.php');
- $actual_content = $controller_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Unit/Generators/Auth/Controllers/AuthVerificationControllerGeneratorTest.php b/tests/Unit/Generators/Auth/Controllers/AuthVerificationControllerGeneratorTest.php
deleted file mode 100644
index ea89a3a..0000000
--- a/tests/Unit/Generators/Auth/Controllers/AuthVerificationControllerGeneratorTest.php
+++ /dev/null
@@ -1,22 +0,0 @@
-getTestStubContents('Controllers/Customer/Auth/VerificationController.php');
- $actual_content = $controller_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Unit/Generators/BaseGeneratorTest.php b/tests/Unit/Generators/BaseGeneratorTest.php
deleted file mode 100644
index c96d0ad..0000000
--- a/tests/Unit/Generators/BaseGeneratorTest.php
+++ /dev/null
@@ -1,174 +0,0 @@
- 'Javaabu\\Generators\\GeneratorsServiceProvider',
- '--tag' => 'generators-stubs',
- ]);
-
- // setup skeleton service provider
- $this->copyFile(
- $this->getTestStubPath('Exports/ModelExport.stub'),
- $this->app->basePath('stubs/vendor/generators/Exports/ModelExport.stub')
- );
- }
-
- protected function tearDown(): void
- {
- $this->deleteDirectory($this->app->basePath('stubs'));
-
- parent::tearDown();
- }
-
- public function test_it_can_generate_from_custom_stubs(): void
- {
- $export_generator = new ExportGenerator('products');
-
- $expected_content = $this->getTestStubContents('Exports/ProductsExport.php');
- $actual_content = $export_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_determine_if_the_model_has_any_fillable_fields(): void
- {
- $generator = new MockBaseGenerator('products');
- $this->assertTrue($generator->hasAnyFillable());
-
- $generator = new MockBaseGenerator('all_foreigns');
- $this->assertFalse($generator->hasAnyFillable());
- }
-
- public function test_it_can_determine_the_name_field(): void
- {
- $generator = new MockBaseGenerator('products');
- $this->assertEquals('name', $generator->getNameField());
-
- $generator = new MockBaseGenerator('orders');
- $this->assertEquals('order_no', $generator->getNameField());
-
- $generator = new MockBaseGenerator('posts');
- $this->assertEquals('title', $generator->getNameField());
-
- $generator = new MockBaseGenerator('payments');
- $this->assertEquals('id', $generator->getNameField());
-
- $generator = new MockBaseGenerator('certificates');
- $this->assertEquals('title', $generator->getNameField());
- }
-
- public function test_it_can_determine_the_type_from_attribute(): void
- {
- $generator = new MockBaseGenerator('products');
-
- $this->assertInstanceOf(StringField::class, $generator->getField('name'));
- $this->assertInstanceOf(StringField::class, $generator->getField('address'));
- $this->assertInstanceOf(TextField::class, $generator->getField('description'));
- $this->assertInstanceOf(DecimalField::class, $generator->getField('price'));
- $this->assertInstanceOf(IntegerField::class, $generator->getField('stock'));
- $this->assertInstanceOf(BooleanField::class, $generator->getField('on_sale'));
- $this->assertInstanceOf(JsonField::class, $generator->getField('features'));
- $this->assertInstanceOf(EnumField::class, $generator->getField('status'));
- $this->assertInstanceOf(ForeignKeyField::class, $generator->getField('category_id'));
- $this->assertInstanceOf(DateTimeField::class, $generator->getField('published_at'));
- $this->assertInstanceOf(DateField::class, $generator->getField('released_on'));
- $this->assertInstanceOf(TimeField::class, $generator->getField('sale_time'));
- $this->assertInstanceOf(YearField::class, $generator->getField('manufactured_year'));
- }
-
- public function test_it_can_determine_the_label_from_the_attribute(): void
- {
- $generator = new MockBaseGenerator('products');
-
- $this->assertEquals('Name', $generator->getField('name')->getLabel());
- $this->assertEquals('Address', $generator->getField('address')->getLabel());
- $this->assertEquals('Description', $generator->getField('description')->getLabel());
- $this->assertEquals('Price', $generator->getField('price')->getLabel());
- $this->assertEquals('Stock', $generator->getField('stock')->getLabel());
- $this->assertEquals('On Sale', $generator->getField('on_sale')->getLabel());
- $this->assertEquals('Features', $generator->getField('features')->getLabel());
- $this->assertEquals('Status', $generator->getField('status')->getLabel());
- $this->assertEquals('Category', $generator->getField('category_id')->getLabel());
- $this->assertEquals('Published At', $generator->getField('published_at')->getLabel());
- $this->assertEquals('Released On', $generator->getField('released_on')->getLabel());
- $this->assertEquals('Sale Time', $generator->getField('sale_time')->getLabel());
- $this->assertEquals('Manufactured Year', $generator->getField('manufactured_year')->getLabel());
- }
-
- public function test_it_can_identify_the_fillable_attributes(): void
- {
- $generator = new MockBaseGenerator('products');
-
- $this->assertEquals([
- 'name',
- 'address',
- 'slug',
- 'description',
- 'price',
- 'stock',
- 'on_sale',
- 'features',
- 'published_at',
- 'expire_at',
- 'released_on',
- 'sale_time',
- 'status',
- 'manufactured_year',
- ], $generator->getFillableAttributes());
- }
-
- public function test_it_can_identify_the_searchable_attributes(): void
- {
- $generator = new MockBaseGenerator('products');
-
- $this->assertEquals([
- 'name',
- 'address',
- 'slug',
- ], $generator->getSearchableAttributes());
- }
-
- public function test_it_can_identify_the_foreign_key_attributes(): void
- {
- $generator = new MockBaseGenerator('products');
-
- $this->assertEquals([
- 'category_id',
- ], $generator->getForeignKeyAttributes());
- }
-}
diff --git a/tests/Unit/Generators/Concerns/GeneratesAuthControllerTest.php b/tests/Unit/Generators/Concerns/GeneratesAuthControllerTest.php
deleted file mode 100644
index 80bed98..0000000
--- a/tests/Unit/Generators/Concerns/GeneratesAuthControllerTest.php
+++ /dev/null
@@ -1,26 +0,0 @@
-assertEquals('ConfirmPasswordController', $controller_generator->getControllerName());
- }
-
- public function test_it_can_generate_auth_controller_path(): void
- {
- $controller_generator = new AuthConfirmPasswordControllerGenerator('customers');
-
- $this->assertEquals('Http/Controllers/Customer/Auth', $controller_generator->getControllerPath());
- }
-}
diff --git a/tests/Unit/Generators/ControllerGeneratorTest.php b/tests/Unit/Generators/ControllerGeneratorTest.php
deleted file mode 100644
index d1ed7f6..0000000
--- a/tests/Unit/Generators/ControllerGeneratorTest.php
+++ /dev/null
@@ -1,82 +0,0 @@
-getTestStubContents('Controllers/_categoryForeignKey.stub');
- $actual_content = $controller_generator->renderControllerForeignKey('category_id', $controller_generator->getField('category_id'));
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_boolean_assignment(): void
- {
- $controller_generator = new ControllerGenerator('products');
-
- $expected_content = $this->getTestStubContents('Controllers/_onSaleBoolean.stub');
- $actual_content = $controller_generator->renderControllerBoolean('on_sale', $controller_generator->getField('on_sale'));
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_an_orderby(): void
- {
- $controller_generator = new ControllerGenerator('products');
-
- $expected_content = $this->getTestStubContents('Controllers/_orderBy.stub');
- $actual_content = $controller_generator->renderControllerOrderBy('created_at');
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_a_controller_without_foreign_keys(): void
- {
- $controller_generator = new ControllerGenerator('categories');
-
- $expected_content = $this->getTestStubContents('Controllers/CategoriesController.php');
- $actual_content = $controller_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_a_controller_with_foreign_keys(): void
- {
- $controller_generator = new ControllerGenerator('products');
-
- $expected_content = $this->getTestStubContents('Controllers/ProductsController.php');
- $actual_content = $controller_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_a_controller_with_multiple_foreign_keys(): void
- {
- $controller_generator = new ControllerGenerator('orders');
-
- $expected_content = $this->getTestStubContents('Controllers/OrdersController.php');
- $actual_content = $controller_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_controller_eager_loads(): void
- {
- $controller_generator = new ControllerGenerator('orders');
-
- $expected_content = $this->getTestStubContents('Controllers/_ordersEagerLoads.stub');
- $actual_content = $controller_generator->renderControllerEagerLoads(["'category'", "'productSlug'"]);
-
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Unit/Generators/ExportGeneratorTest.php b/tests/Unit/Generators/ExportGeneratorTest.php
deleted file mode 100644
index 355debc..0000000
--- a/tests/Unit/Generators/ExportGeneratorTest.php
+++ /dev/null
@@ -1,32 +0,0 @@
-getTestStubContents('Exports/CategoriesExport.php');
- $actual_content = $export_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_an_export_with_relations(): void
- {
- $export_generator = new ExportGenerator('all_foreigns');
-
- $expected_content = $this->getTestStubContents('Exports/AllForeignsExport.php');
- $actual_content = $export_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Unit/Generators/FactoryGeneratorTest.php b/tests/Unit/Generators/FactoryGeneratorTest.php
deleted file mode 100644
index 01262bf..0000000
--- a/tests/Unit/Generators/FactoryGeneratorTest.php
+++ /dev/null
@@ -1,205 +0,0 @@
-assertNull($factory_generator->getFakerMethodFromColumnName('colorful'));
- $this->assertEquals('email', $factory_generator->getFakerMethodFromColumnName('email'));
- $this->assertEquals('firstName', $factory_generator->getFakerMethodFromColumnName('first_name'));
- }
-
- public function test_it_can_determine_the_faker_statement_for_faker_attributes(): void
- {
- $factory_generator = new FactoryGenerator('products');
-
- $this->assertEquals('fake()->address()', $factory_generator->getFakerStatement('address'));
- }
-
- public function test_it_can_determine_the_faker_statement_for_nullable_attributes(): void
- {
- $factory_generator = new FactoryGenerator('products');
-
- $this->assertEquals('fake()->optional()->sentences(3, true)', $factory_generator->getFakerStatement('description'));
- }
-
- public function test_it_can_determine_the_faker_statement_for_decimals(): void
- {
- $factory_generator = new FactoryGenerator('products');
-
- $this->assertEquals('fake()->randomFloat(2, 0, 999999999999)', $factory_generator->getFakerStatement('price'));
- }
-
- public function test_it_can_determine_the_faker_statement_for_ints(): void
- {
- $factory_generator = new FactoryGenerator('products');
-
- $this->assertEquals('fake()->numberBetween(0, 4294967295)', $factory_generator->getFakerStatement('stock'));
- }
-
- public function test_it_can_determine_the_faker_statement_for_texts(): void
- {
- $factory_generator = new FactoryGenerator('products');
-
- $this->assertEquals('fake()->optional()->sentences(3, true)', $factory_generator->getFakerStatement('description'));
- }
-
- public function test_it_can_determine_the_faker_statement_for_unique_fields(): void
- {
- $factory_generator = new FactoryGenerator('products');
-
- $this->assertEquals('fake()->unique()->slug()', $factory_generator->getFakerStatement('slug'));
- }
-
- public function test_it_can_determine_the_faker_statement_for_strings_shorter_than_5_characters(): void
- {
- $factory_generator = new FactoryGenerator('orders');
-
- $this->assertEquals('fake()->regexify(\'[a-z]{4}\')', $factory_generator->getFakerStatement('order_no'));
- }
-
- public function test_it_can_determine_the_faker_statement_for_strings(): void
- {
- $factory_generator = new FactoryGenerator('products');
-
- $this->assertEquals('fake()->text(fake()->numberBetween(5, 255))', $factory_generator->getFakerStatement('name'));
- }
-
- public function test_it_can_determine_the_faker_statement_for_booleans(): void
- {
- $factory_generator = new FactoryGenerator('products');
-
- $this->assertEquals('fake()->boolean()', $factory_generator->getFakerStatement('on_sale'));
- }
-
-
- public function test_it_can_determine_the_faker_statement_for_date_times(): void
- {
- $factory_generator = new FactoryGenerator('products');
-
- $this->assertEquals('fake()->dateTime()?->format(\'Y-m-d H:i\')', $factory_generator->getFakerStatement('published_at'));
- }
-
- public function test_it_can_determine_the_faker_statement_for_times(): void
- {
- $factory_generator = new FactoryGenerator('products');
-
- $this->assertEquals('fake()->time()', $factory_generator->getFakerStatement('sale_time'));
- }
-
- public function test_it_can_determine_the_faker_statement_for_timestamps(): void
- {
- $factory_generator = new FactoryGenerator('products');
-
- $this->assertEquals('fake()->dateTime()?->format(\'Y-m-d H:i\')', $factory_generator->getFakerStatement('expire_at'));
- }
-
- public function test_it_can_determine_the_faker_statement_for_dates(): void
- {
- $factory_generator = new FactoryGenerator('products');
-
- $this->assertEquals('fake()->date()', $factory_generator->getFakerStatement('released_on'));
- }
-
- public function test_it_can_determine_the_faker_statement_for_years(): void
- {
- $factory_generator = new FactoryGenerator('products');
-
- $this->assertEquals('fake()->year(2100)', $factory_generator->getFakerStatement('manufactured_year'));
- }
-
- public function test_it_can_determine_the_faker_statement_for_foreign_keys(): void
- {
- $factory_generator = new FactoryGenerator('products');
-
- $this->assertEquals('fake()'."->optional()->passThrough(random_id_or_generate(\App\Models\Category::class, 'id', generate: true))", $factory_generator->getFakerStatement('category_id'));
- }
-
- public function test_it_can_determine_the_faker_statement_for_unique_foreign_keys(): void
- {
- $factory_generator = new FactoryGenerator('payments');
-
- $this->assertEquals('fake()'."->unique()->passThrough(random_id_or_generate(\App\Models\Order::class, 'id', generate: true, unique: true))", $factory_generator->getFakerStatement('order_id'));
- }
-
- public function test_it_can_determine_the_faker_statement_for_json_fields(): void
- {
- $factory_generator = new FactoryGenerator('products');
-
- $this->assertEquals('fake()->words()', $factory_generator->getFakerStatement('features'));
- }
-
- public function test_it_can_determine_the_faker_statement_for_enum_fields(): void
- {
- $factory_generator = new FactoryGenerator('products');
-
- $this->assertEquals('fake()->randomElement('."['draft', 'published']".')', $factory_generator->getFakerStatement('status'));
- }
-
- public function test_it_can_determine_the_faker_statement_for_enum_fields_with_enum_class(): void
- {
- $factory_generator = new FactoryGenerator('orders');
-
- $this->assertEquals('fake()->randomElement(array_column(\\Javaabu\\Generators\\Tests\\TestSupport\\Enums\\OrderStatuses::cases(), \'value\'))', $factory_generator->getFakerStatement('status'));
- }
-
- public function test_it_can_generate_a_factory_with_foreign_keys(): void
- {
- $factory_generator = new FactoryGenerator('products');
-
- $expected_content = $this->getTestStubContents('factories/ProductFactory.php');
- $actual_content = $factory_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_a_factory_without_foreign_keys(): void
- {
- $factory_generator = new FactoryGenerator('categories');
-
- $expected_content = $this->getTestStubContents('factories/CategoryFactory.php');
- $actual_content = $factory_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_a_factory_with_multiple_foreign_keys(): void
- {
- $factory_generator = new FactoryGenerator('orders');
-
- $expected_content = $this->getTestStubContents('factories/OrderFactory.php');
- $actual_content = $factory_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_a_factory_with_unique_foreign_keys(): void
- {
- $factory_generator = new FactoryGenerator('payments');
-
- $expected_content = $this->getTestStubContents('factories/PaymentFactory.php');
- $actual_content = $factory_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_a_factory_with_optional_unique_values(): void
- {
- $factory_generator = new FactoryGenerator('countries');
-
- $expected_content = $this->getTestStubContents('factories/CountryFactory.php');
- $actual_content = $factory_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Unit/Generators/ModelGeneratorTest.php b/tests/Unit/Generators/ModelGeneratorTest.php
deleted file mode 100644
index 602d411..0000000
--- a/tests/Unit/Generators/ModelGeneratorTest.php
+++ /dev/null
@@ -1,72 +0,0 @@
-getTestStubContents('Models/_categoryForeignKey.stub');
- $actual_content = $model_generator->renderModelForeignKey('category_id', $model_generator->getField('category_id'));
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_date_mutator(): void
- {
- $model_generator = new ModelGenerator('products');
-
- $expected_content = $this->getTestStubContents('Models/_dateMutator.stub');
- $actual_content = $model_generator->renderModelDateMutator('published_at', $model_generator->getField('published_at'));
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_nullable_date_mutator(): void
- {
- $model_generator = new ModelGenerator('posts');
-
- $expected_content = $this->getTestStubContents('Models/_nullableDateMutator.stub');
- $actual_content = $model_generator->renderModelDateMutator('published_at', $model_generator->getField('published_at'));
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_a_model_with_foreign_keys(): void
- {
- $model_generator = new ModelGenerator('products');
-
- $expected_content = $this->getTestStubContents('Models/Product.php');
- $actual_content = $model_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_a_model_without_foreign_keys(): void
- {
- $model_generator = new ModelGenerator('categories');
-
- $expected_content = $this->getTestStubContents('Models/Category.php');
- $actual_content = $model_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_a_model_with_multiple_foreign_keys(): void
- {
- $model_generator = new ModelGenerator('orders');
-
- $expected_content = $this->getTestStubContents('Models/Order.php');
- $actual_content = $model_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Unit/Generators/PermissionsGeneratorTest.php b/tests/Unit/Generators/PermissionsGeneratorTest.php
deleted file mode 100644
index 673f89c..0000000
--- a/tests/Unit/Generators/PermissionsGeneratorTest.php
+++ /dev/null
@@ -1,32 +0,0 @@
-getTestStubContents('seeders/_permissionsSoftDeletes.stub');
- $actual_content = $permissions_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_a_permissions_for_a_none_soft_delete(): void
- {
- $permissions_generator = new PermissionsGenerator('categories');
-
- $expected_content = $this->getTestStubContents('seeders/_permissions.stub');
- $actual_content = $permissions_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Unit/Generators/PolicyGeneratorTest.php b/tests/Unit/Generators/PolicyGeneratorTest.php
deleted file mode 100644
index 8033b0f..0000000
--- a/tests/Unit/Generators/PolicyGeneratorTest.php
+++ /dev/null
@@ -1,32 +0,0 @@
-getTestStubContents('Policies/ProductPolicy.php');
- $actual_content = $policy_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_a_policy_for_a_none_soft_delete(): void
- {
- $policy_generator = new PolicyGenerator('categories');
-
- $expected_content = $this->getTestStubContents('Policies/CategoryPolicy.php');
- $actual_content = $policy_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Unit/Generators/RequestGeneratorTest.php b/tests/Unit/Generators/RequestGeneratorTest.php
deleted file mode 100644
index 4a80c9c..0000000
--- a/tests/Unit/Generators/RequestGeneratorTest.php
+++ /dev/null
@@ -1,172 +0,0 @@
-assertContains('nullable', $request_generator->getRequestValidationRules('description'));
- }
-
- public function test_it_can_determine_the_validation_rules_for_decimals(): void
- {
- $request_generator = new RequestGenerator('products');
-
- $this->assertEquals([
- 'decimal:0,2',
- 'min:0',
- 'max:999999999999'
- ], $request_generator->getRequestValidationRules('price'));
- }
-
- public function test_it_can_determine_the_validation_rules_for_ints(): void
- {
- $request_generator = new RequestGenerator('products');
-
- $this->assertEquals([
- 'integer',
- 'min:0',
- 'max:4294967295'
- ], $request_generator->getRequestValidationRules('stock'));
- }
-
- public function test_it_can_determine_the_validation_rules_for_texts(): void
- {
- $request_generator = new RequestGenerator('products');
-
- $this->assertEquals([
- 'nullable',
- 'string'
- ], $request_generator->getRequestValidationRules('description'));
- }
-
- public function test_it_can_determine_the_validation_rules_for_strings(): void
- {
- $request_generator = new RequestGenerator('products');
-
- $this->assertEquals([
- 'string',
- 'max:255'
- ], $request_generator->getRequestValidationRules('name'));
- }
-
- public function test_it_can_determine_the_validation_rules_for_booleans(): void
- {
- $request_generator = new RequestGenerator('products');
-
- $this->assertEquals([
- 'boolean'
- ], $request_generator->getRequestValidationRules('on_sale'));
- }
-
-
- public function test_it_can_determine_the_validation_rules_for_date_times(): void
- {
- $request_generator = new RequestGenerator('products');
-
- $this->assertEquals([
- 'date',
- ], $request_generator->getRequestValidationRules('published_at'));
- }
-
- public function test_it_can_determine_the_validation_rules_for_times(): void
- {
- $request_generator = new RequestGenerator('products');
-
- $this->assertEquals([
- 'date',
- ], $request_generator->getRequestValidationRules('sale_time'));
- }
-
- public function test_it_can_determine_the_validation_rules_for_timestamps(): void
- {
- $request_generator = new RequestGenerator('products');
-
- $this->assertEquals([
- 'date',
- ], $request_generator->getRequestValidationRules('expire_at'));
- }
-
- public function test_it_can_determine_the_validation_rules_for_dates(): void
- {
- $request_generator = new RequestGenerator('products');
-
- $this->assertEquals([
- 'date',
- ], $request_generator->getRequestValidationRules('released_on'));
- }
-
- public function test_it_can_determine_the_validation_rules_for_years(): void
- {
- $request_generator = new RequestGenerator('products');
-
- $this->assertEquals([
- 'integer',
- 'min:1900',
- 'max:2100'
- ], $request_generator->getRequestValidationRules('manufactured_year'));
- }
-
- public function test_it_can_determine_the_validation_rules_for_foreign_keys(): void
- {
- $request_generator = new RequestGenerator('products');
-
- $this->assertEquals([
- 'nullable',
- 'exists:categories,id',
- ], $request_generator->getRequestValidationRules('category_id'));
- }
-
- public function test_it_can_determine_the_validation_rules_for_json_fields(): void
- {
- $request_generator = new RequestGenerator('products');
-
- $this->assertEquals(['array'], $request_generator->getRequestValidationRules('features'));
- }
-
- public function test_it_can_determine_the_validation_rules_for_enum_fields(): void
- {
- $request_generator = new RequestGenerator('products');
-
- $this->assertEquals(['in:draft,published'], $request_generator->getRequestValidationRules('status'));
- }
-
- public function test_it_can_generate_a_request_with_one_unique_rule(): void
- {
- $request_generator = new RequestGenerator('products');
-
- $expected_content = $this->getTestStubContents('Requests/ProductsRequest.php');
- $actual_content = $request_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_a_request_with_multiple_unique_rules(): void
- {
- $request_generator = new RequestGenerator('categories');
-
- $expected_content = $this->getTestStubContents('Requests/CategoriesRequest.php');
- $actual_content = $request_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_a_request_with_no_unique_rules(): void
- {
- $request_generator = new RequestGenerator('orders');
-
- $expected_content = $this->getTestStubContents('Requests/OrdersRequest.php');
- $actual_content = $request_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Unit/Generators/RoutesGeneratorTest.php b/tests/Unit/Generators/RoutesGeneratorTest.php
deleted file mode 100644
index 48ae1bd..0000000
--- a/tests/Unit/Generators/RoutesGeneratorTest.php
+++ /dev/null
@@ -1,32 +0,0 @@
-getTestStubContents('routes/_adminSoftDeletes.stub');
- $actual_content = $routes_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_a_routes_for_a_none_soft_delete(): void
- {
- $routes_generator = new RoutesGenerator('categories');
-
- $expected_content = $this->getTestStubContents('routes/_admin.stub');
- $actual_content = $routes_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Unit/Generators/TestGeneratorTest.php b/tests/Unit/Generators/TestGeneratorTest.php
deleted file mode 100644
index efdbbbd..0000000
--- a/tests/Unit/Generators/TestGeneratorTest.php
+++ /dev/null
@@ -1,142 +0,0 @@
-getTestStubContents('tests/_errors.stub');
- $actual_content = $test_generator->renderErrors();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_render_wrong_inputs(): void
- {
- $test_generator = new TestGenerator('products');
-
- $expected_content = $this->getTestStubContents('tests/_wrongInputs.stub');
- $actual_content = $test_generator->renderWrongInputs();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_render_wrong_db_values(): void
- {
- $test_generator = new TestGenerator('products');
-
- $expected_content = $this->getTestStubContents('tests/_wrongDbValues.stub');
- $actual_content = $test_generator->renderWrongDbValues();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_render_correct_inputs(): void
- {
- $test_generator = new TestGenerator('products');
-
- $expected_content = $this->getTestStubContents('tests/_correctInputs.stub');
- $actual_content = $test_generator->renderCorrectInputs();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_render_correct_db_values(): void
- {
- $test_generator = new TestGenerator('products');
-
- $expected_content = $this->getTestStubContents('tests/_correctDbValues.stub');
- $actual_content = $test_generator->renderCorrectDbValues();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_render_different_correct_inputs(): void
- {
- $test_generator = new TestGenerator('products');
-
- $expected_content = $this->getTestStubContents('tests/_differentCorrectInputs.stub');
- $actual_content = $test_generator->renderDifferentCorrectInputs();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_render_different_correct_db_values(): void
- {
- $test_generator = new TestGenerator('products');
-
- $expected_content = $this->getTestStubContents('tests/_differentCorrectDbValues.stub');
- $actual_content = $test_generator->renderDifferentCorrectDbValues();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_render_factory_inputs(): void
- {
- $test_generator = new TestGenerator('products');
-
- $expected_content = $this->getTestStubContents('tests/_factoryInputs.stub');
- $actual_content = $test_generator->renderFactoryInputs();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_render_factory_db_values(): void
- {
- $test_generator = new TestGenerator('products');
-
- $expected_content = $this->getTestStubContents('tests/_factoryDbValues.stub');
- $actual_content = $test_generator->renderFactoryDbValues();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_a_test_with_foreign_keys(): void
- {
- $test_generator = new TestGenerator('products');
-
- $expected_content = $this->getTestStubContents('tests/ProductsControllerTest.stub');
- $actual_content = $test_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_a_test_without_foreign_keys(): void
- {
- $test_generator = new TestGenerator('categories');
-
- $expected_content = $this->getTestStubContents('tests/CategoriesControllerTest.stub');
- $actual_content = $test_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_a_test_with_multiple_foreign_keys(): void
- {
- $test_generator = new TestGenerator('orders');
-
- $expected_content = $this->getTestStubContents('tests/OrdersControllerTest.stub');
- $actual_content = $test_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_a_test_where_the_enum_has_only_one_case(): void
- {
- $test_generator = new TestGenerator('single_value_enum_orders');
-
- $expected_content = $this->getTestStubContents('tests/SingleValueEnumOrdersControllerTest.stub');
- $actual_content = $test_generator->render();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Unit/Generators/ViewsGeneratorTest.php b/tests/Unit/Generators/ViewsGeneratorTest.php
deleted file mode 100644
index e017433..0000000
--- a/tests/Unit/Generators/ViewsGeneratorTest.php
+++ /dev/null
@@ -1,366 +0,0 @@
-assertEquals('', $views_generator->getFormComponentBlade('description'));
- }
-
- public function test_it_can_generate_the_component_for_decimals(): void
- {
- $views_generator = new ViewsGenerator('products');
-
- $this->assertEquals('', $views_generator->getFormComponentBlade('price'));
- }
-
- public function test_it_can_generate_the_component_for_ints(): void
- {
- $views_generator = new ViewsGenerator('products');
-
- $this->assertEquals('', $views_generator->getFormComponentBlade('stock'));
- }
-
- public function test_it_can_generate_the_component_for_texts(): void
- {
- $views_generator = new ViewsGenerator('products');
-
- $this->assertEquals('', $views_generator->getFormComponentBlade('description'));
- }
-
- public function test_it_can_generate_the_component_for_strings(): void
- {
- $views_generator = new ViewsGenerator('products');
-
- $this->assertEquals('', $views_generator->getFormComponentBlade('name'));
- }
-
-
- public function test_it_can_generate_the_component_for_booleans(): void
- {
- $views_generator = new ViewsGenerator('products');
-
- $this->assertEquals('', $views_generator->getFormComponentBlade('on_sale'));
- }
-
-
- public function test_it_can_generate_the_component_for_date_times(): void
- {
- $views_generator = new ViewsGenerator('products');
-
- $this->assertEquals('', $views_generator->getFormComponentBlade('published_at'));
- }
-
- public function test_it_can_generate_the_component_for_times(): void
- {
- $views_generator = new ViewsGenerator('products');
-
- $this->assertEquals('', $views_generator->getFormComponentBlade('sale_time'));
- }
-
- public function test_it_can_generate_the_component_for_timestamps(): void
- {
- $views_generator = new ViewsGenerator('products');
-
- $this->assertEquals('', $views_generator->getFormComponentBlade('expire_at'));
- }
-
- public function test_it_can_generate_the_component_for_dates(): void
- {
- $views_generator = new ViewsGenerator('products');
-
- $this->assertEquals('', $views_generator->getFormComponentBlade('released_on'));
- }
-
- public function test_it_can_generate_the_component_for_years(): void
- {
- $views_generator = new ViewsGenerator('products');
-
- $this->assertEquals('', $views_generator->getFormComponentBlade('manufactured_year'));
- }
-
- public function test_it_can_generate_the_component_for_foreign_keys(): void
- {
- $views_generator = new ViewsGenerator('products');
-
- $this->assertEquals('', $views_generator->getFormComponentBlade('category_id'));
- }
-
- public function test_it_can_generate_the_component_for_json_fields(): void
- {
- $views_generator = new ViewsGenerator('products');
-
- $this->assertEquals('', $views_generator->getFormComponentBlade('features'));
- }
-
- public function test_it_can_generate_the_component_for_enum_fields(): void
- {
- $views_generator = new ViewsGenerator('products');
-
- $this->assertEquals('', $views_generator->getFormComponentBlade('status'));
- }
-
- public function test_it_can_generate_the_component_for_enum_fields_with_enum_class_that_dont_have_get_labels(): void
- {
- $views_generator = new ViewsGenerator('payments');
-
- $this->assertEquals('', $views_generator->getFormComponentBlade('status'));
- }
-
- public function test_it_can_generate_the_component_for_enum_fields_with_enum_class(): void
- {
- $views_generator = new ViewsGenerator('orders');
-
- $this->assertEquals('', $views_generator->getFormComponentBlade('status'));
- }
-
- public function test_it_can_generate_an_infolist_with_foreign_keys(): void
- {
- $views_generator = new ViewsGenerator('products');
-
- $expected_content = $this->getTestStubContents('views/products/_details.blade.php');
- $actual_content = $views_generator->renderInfolist();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_an_infolist_with_multiple_foreign_keys(): void
- {
- $views_generator = new ViewsGenerator('orders');
-
- $expected_content = $this->getTestStubContents('views/orders/_details.blade.php');
- $actual_content = $views_generator->renderInfolist();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_a_form_with_foreign_keys(): void
- {
- $views_generator = new ViewsGenerator('products');
-
- $expected_content = $this->getTestStubContents('views/products/_form.blade.php');
- $actual_content = $views_generator->renderForm();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_a_form_without_foreign_keys(): void
- {
- $views_generator = new ViewsGenerator('categories');
-
- $expected_content = $this->getTestStubContents('views/categories/_form.blade.php');
- $actual_content = $views_generator->renderForm();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_a_form_with_multiple_foreign_keys(): void
- {
- $views_generator = new ViewsGenerator('orders');
-
- $expected_content = $this->getTestStubContents('views/orders/_form.blade.php');
- $actual_content = $views_generator->renderForm();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_model_layout(): void
- {
- $views_generator = new ViewsGenerator('products');
-
- $expected_content = $this->getTestStubContents('views/products/products.blade.php');
- $actual_content = $views_generator->renderLayout();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_model_create_view(): void
- {
- $views_generator = new ViewsGenerator('products');
-
- $expected_content = $this->getTestStubContents('views/products/create.blade.php');
- $actual_content = $views_generator->renderCreateView();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_model_edit_view(): void
- {
- $views_generator = new ViewsGenerator('products');
-
- $expected_content = $this->getTestStubContents('views/products/edit.blade.php');
- $actual_content = $views_generator->renderEditView();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_model_show_view(): void
- {
- $views_generator = new ViewsGenerator('products');
-
- $expected_content = $this->getTestStubContents('views/products/show.blade.php');
- $actual_content = $views_generator->renderShowView();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_actions_view_for_soft_delete_models(): void
- {
- $views_generator = new ViewsGenerator('products');
-
- $expected_content = $this->getTestStubContents('views/products/_actions.blade.php');
- $actual_content = $views_generator->renderActions();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_actions_view_for_none_soft_delete_models(): void
- {
- $views_generator = new ViewsGenerator('categories');
-
- $expected_content = $this->getTestStubContents('views/categories/_actions.blade.php');
- $actual_content = $views_generator->renderActions();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_bulk_actions_view(): void
- {
- $views_generator = new ViewsGenerator('products');
-
- $expected_content = $this->getTestStubContents('views/products/_bulk.blade.php');
- $actual_content = $views_generator->renderBulkActions();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_filters_view_for_soft_delete_models(): void
- {
- $views_generator = new ViewsGenerator('products');
-
- $expected_content = $this->getTestStubContents('views/products/_filter.blade.php');
- $actual_content = $views_generator->renderFilters();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_filters_view_for_none_soft_delete_models(): void
- {
- $views_generator = new ViewsGenerator('categories');
-
- $expected_content = $this->getTestStubContents('views/categories/_filter.blade.php');
- $actual_content = $views_generator->renderFilters();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_render_model_columns_and_skips_the_admin_link_name(): void
- {
- $views_generator = new ViewsGenerator('products');
-
- $expected_content = $this->getTestStubContents('views/products/_columns.blade.stub');
- $actual_content = $views_generator->renderTableColumns();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_list_view_for_multiple_foreign_keys_model(): void
- {
- $views_generator = new ViewsGenerator('orders');
-
- $expected_content = $this->getTestStubContents('views/orders/_list.blade.php');
- $actual_content = $views_generator->renderTableRows();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_list_view_for_soft_delete_models(): void
- {
- $views_generator = new ViewsGenerator('products');
-
- $expected_content = $this->getTestStubContents('views/products/_list.blade.php');
- $actual_content = $views_generator->renderTableRows();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_list_view_for_none_soft_delete_models(): void
- {
- $views_generator = new ViewsGenerator('categories');
-
- $expected_content = $this->getTestStubContents('views/categories/_list.blade.php');
- $actual_content = $views_generator->renderTableRows();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_render_column_titles_and_skips_the_admin_link_name(): void
- {
- $views_generator = new ViewsGenerator('products');
-
- $expected_content = $this->getTestStubContents('views/products/_titles.blade.stub');
- $actual_content = $views_generator->renderTableTitles();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_table_view_for_soft_delete_models(): void
- {
- $views_generator = new ViewsGenerator('products');
-
- $expected_content = $this->getTestStubContents('views/products/_table.blade.php');
- $actual_content = $views_generator->renderTable();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_table_view_for_none_soft_delete_models(): void
- {
- $views_generator = new ViewsGenerator('categories');
-
- $expected_content = $this->getTestStubContents('views/categories/_table.blade.php');
- $actual_content = $views_generator->renderTable();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_index_view_for_soft_delete_models(): void
- {
- $views_generator = new ViewsGenerator('products');
-
- $expected_content = $this->getTestStubContents('views/products/index.blade.php');
- $actual_content = $views_generator->renderIndexView();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_generate_index_view_for_none_soft_delete_models(): void
- {
- $views_generator = new ViewsGenerator('categories');
-
- $expected_content = $this->getTestStubContents('views/categories/index.blade.php');
- $actual_content = $views_generator->renderIndexView();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_render_sidebar_links(): void
- {
- $views_generator = new ViewsGenerator('categories');
-
- $expected_content = $this->getTestStubContents('Menus/_categoriesSidebar.stub');
- $actual_content = $views_generator->renderSidebarLinks();
-
- $this->assertEquals($expected_content, $actual_content);
- }
-}
diff --git a/tests/Unit/IconProviderTest.php b/tests/Unit/IconProviderTest.php
deleted file mode 100644
index 382aa8a..0000000
--- a/tests/Unit/IconProviderTest.php
+++ /dev/null
@@ -1,32 +0,0 @@
-assertEquals('file', $icons->findIconFor('file'));
- $this->assertEquals('email', $icons->findIconFor('messages'));
- $this->assertEquals('shopping-cart', $icons->findIconFor('product'));
- $this->assertEquals('email', $icons->findIconFor('emails'));
- $this->assertEquals('cloud-upload', $icons->findIconFor('cloud_file_uploads'));
- }
-
- public function test_it_can_find_appropriate_fontawesome_icons(): void
- {
- $icons = new FontAwesomeProvider();
-
- $this->assertEquals('file', $icons->findIconFor('file'));
- $this->assertEquals('envelope', $icons->findIconFor('messages'));
- $this->assertEquals('shopping-cart', $icons->findIconFor('product'));
- $this->assertEquals('crow', $icons->findIconFor('crows'));
- $this->assertEquals('cloud-upload', $icons->findIconFor('cloud_file_uploads'));
- }
-}
diff --git a/tests/Unit/Schema/RelationResolverTest.php b/tests/Unit/Schema/RelationResolverTest.php
new file mode 100644
index 0000000..c871f4a
--- /dev/null
+++ b/tests/Unit/Schema/RelationResolverTest.php
@@ -0,0 +1,95 @@
+ $relations
+ * @return array
+ */
+ protected function definitions(array $relations): array
+ {
+ $definitions = [];
+
+ foreach ($relations as $relation) {
+ $definitions[$relation->name] = $relation->definition();
+ }
+
+ return $definitions;
+ }
+
+ protected function table(string $name)
+ {
+ return app(SchemaInspector::class)->table($name);
+ }
+
+ #[Test]
+ public function it_finds_belongs_to_relations(): void
+ {
+ $relations = app(RelationResolver::class)->belongsTo($this->table('orders'));
+
+ $this->assertEquals([
+ 'category' => 'belongsTo(Category::class)',
+ 'product' => "belongsTo(Product::class, 'product_slug', 'slug')",
+ ], $this->definitions($relations));
+ }
+
+ #[Test]
+ public function it_finds_has_many_relations_from_other_tables(): void
+ {
+ $definitions = $this->definitions(app(RelationResolver::class)->hasMany($this->table('categories')));
+
+ $this->assertEquals('hasMany(Product::class)', $definitions['products']);
+ $this->assertEquals('hasMany(Order::class)', $definitions['orders']);
+ $this->assertArrayNotHasKey('productTags', $definitions);
+ }
+
+ #[Test]
+ public function it_finds_has_many_relations_on_other_keys(): void
+ {
+ $definitions = $this->definitions(app(RelationResolver::class)->hasMany($this->table('products')));
+
+ $this->assertEquals("hasMany(Order::class, 'product_slug', 'slug')", $definitions['orders']);
+ }
+
+ #[Test]
+ public function it_finds_many_to_many_relations_through_pivot_tables(): void
+ {
+ $resolver = app(RelationResolver::class);
+
+ $this->assertEquals(['tags' => 'belongsToMany(Tag::class)'], $this->definitions($resolver->belongsToMany($this->table('products'))));
+ $this->assertEquals(['products' => 'belongsToMany(Product::class)'], $this->definitions($resolver->belongsToMany($this->table('tags'))));
+ }
+
+ #[Test]
+ public function it_tells_pivot_tables_apart(): void
+ {
+ $resolver = app(RelationResolver::class);
+
+ $this->assertTrue($resolver->isPivotTable($this->table('product_tag')));
+
+ // two keys, but one is to products.slug, not a key
+ $this->assertFalse($resolver->isPivotTable($this->table('all_foreigns')));
+
+ $this->assertFalse($resolver->isPivotTable($this->table('orders')));
+ $this->assertFalse($resolver->isPivotTable($this->table('products')));
+ }
+
+ #[Test]
+ public function it_skips_ignored_tables(): void
+ {
+ config()->set('generators.ignored_tables', ['orders']);
+
+ $definitions = $this->definitions(app(RelationResolver::class)->hasMany($this->table('categories')));
+
+ $this->assertArrayNotHasKey('orders', $definitions);
+ $this->assertArrayHasKey('products', $definitions);
+ }
+}
diff --git a/tests/Unit/Schema/SchemaInspectorTest.php b/tests/Unit/Schema/SchemaInspectorTest.php
new file mode 100644
index 0000000..01ee361
--- /dev/null
+++ b/tests/Unit/Schema/SchemaInspectorTest.php
@@ -0,0 +1,150 @@
+inspector()->tableNames();
+
+ $this->assertContains('products', $tables);
+ $this->assertContains('product_tag', $tables);
+ $this->assertTrue($this->inspector()->hasTable('categories'));
+ $this->assertFalse($this->inspector()->hasTable('missing'));
+ }
+
+ #[Test]
+ public function it_throws_for_a_missing_table(): void
+ {
+ $this->expectException(TableDoesNotExistException::class);
+
+ $this->inspector()->table('missing');
+ }
+
+ #[Test]
+ public function it_normalizes_the_column_types(): void
+ {
+ $table = $this->inspector()->table('products');
+
+ $types = array_map(fn ($column) => $column->type, $table->columns);
+
+ $this->assertContains($types['id'], ['bigint', 'int']);
+ $this->assertEquals('string', $types['name']);
+ $this->assertEquals('text', $types['description']);
+ $this->assertEquals('decimal', $types['price']);
+ $this->assertEquals('int', $types['stock']);
+ $this->assertEquals('boolean', $types['on_sale']);
+ $this->assertEquals('date', $types['released_on']);
+ $this->assertEquals('time', $types['sale_time']);
+ $this->assertEquals('enum', $types['status']);
+ $this->assertContains($types['published_at'], ['datetime', 'timestamp']);
+ $this->assertContains($types['expire_at'], ['datetime', 'timestamp']);
+
+ // SQLite stores JSON as text
+ $this->assertContains($types['features'], ['json', 'text']);
+
+ // MySQL keeps year columns, the others store integers
+ $this->assertContains($types['manufactured_year'], ['year', 'int']);
+ }
+
+ #[Test]
+ public function it_reads_enum_options_on_every_driver(): void
+ {
+ $status = $this->inspector()->table('products')->column('status');
+
+ $this->assertEquals('enum', $status->type);
+ $this->assertEquals(['draft', 'published'], $status->options);
+ }
+
+ #[Test]
+ public function it_reads_nullability_uniqueness_and_defaults(): void
+ {
+ $table = $this->inspector()->table('products');
+
+ $this->assertTrue($table->column('description')->nullable);
+ $this->assertFalse($table->column('name')->nullable);
+ $this->assertTrue($table->column('slug')->unique);
+ $this->assertFalse($table->column('name')->unique);
+ $this->assertNotNull($table->column('on_sale')->default);
+ $this->assertTrue($table->column('id')->auto_increment);
+ }
+
+ #[Test]
+ public function it_reads_the_decimal_precision(): void
+ {
+ $price = $this->inspector()->table('products')->column('price');
+
+ // SQLite keeps no precision: 10,2 is assumed
+ $sqlite = $this->inspector()->driver() === 'sqlite';
+
+ $this->assertEquals($sqlite ? 10 : 14, $price->precision);
+ $this->assertEquals(2, $price->scale);
+ }
+
+ #[Test]
+ public function it_reads_string_lengths_where_the_driver_keeps_them(): void
+ {
+ $order_no = $this->inspector()->table('orders')->column('order_no');
+
+ $expected = $this->inspector()->driver() === 'sqlite' ? 255 : 4;
+
+ $this->assertEquals($expected, $order_no->length);
+ }
+
+ #[Test]
+ public function it_reads_enum_classes_from_column_comments_where_the_driver_keeps_them(): void
+ {
+ if ($this->inspector()->driver() === 'sqlite') {
+ $this->markTestSkipped('SQLite keeps no column comments.');
+ }
+
+ $status = $this->inspector()->table('payments')->column('status');
+
+ $this->assertEquals('App\\Enums\\PaymentStatuses', $status->enumClassFromComment());
+ }
+
+ #[Test]
+ public function it_reads_the_keys_timestamps_and_soft_deletes(): void
+ {
+ $products = $this->inspector()->table('products');
+ $payments = $this->inspector()->table('payments');
+ $pivot = $this->inspector()->table('product_tag');
+
+ $this->assertEquals('id', $products->keyName());
+ $this->assertTrue($products->hasTimestamps());
+ $this->assertTrue($products->hasSoftDeletes());
+
+ $this->assertFalse($payments->hasTimestamps());
+ $this->assertTrue($payments->hasSoftDeletes());
+
+ $this->assertNull($pivot->keyName());
+ $this->assertEquals(['product_id', 'tag_id'], $pivot->primary_key);
+ }
+
+ #[Test]
+ public function it_reads_foreign_keys(): void
+ {
+ $orders = $this->inspector()->table('orders');
+
+ $category = $orders->foreignKeyFor('category_id');
+ $product = $orders->foreignKeyFor('product_slug');
+
+ $this->assertEquals('categories', $category->foreign_table);
+ $this->assertEquals('id', $category->foreign_column);
+ $this->assertEquals('products', $product->foreign_table);
+ $this->assertEquals('slug', $product->foreign_column);
+ $this->assertNull($orders->foreignKeyFor('order_no'));
+ }
+}
diff --git a/tests/Unit/SchemaResolverMySqlTest.php b/tests/Unit/SchemaResolverMySqlTest.php
deleted file mode 100644
index db7e61e..0000000
--- a/tests/Unit/SchemaResolverMySqlTest.php
+++ /dev/null
@@ -1,341 +0,0 @@
-assertEquals('id', $resolver->resolve()->getKeyName());
- }
-
- public function test_it_can_resolve_soft_deletes(): void
- {
- $resolver = new SchemaResolverMySql('products');
- $this->assertTrue($resolver->resolve()->hasSoftDeletes());
-
- $resolver = new SchemaResolverMySql('categories');
- $this->assertFalse($resolver->resolve()->hasSoftDeletes());
- }
-
- public function test_it_can_resolve_timestamps(): void
- {
- $resolver = new SchemaResolverMySql('products');
- $this->assertTrue($resolver->resolve()->hasTimestamps());
-
- $resolver = new SchemaResolverMySql('payments');
- $this->assertFalse($resolver->resolve()->hasTimestamps());
- }
-
- public function test_it_can_resolve_a_subset_of_fields(): void
- {
- $resolver = new SchemaResolverMySql('orders', ['order_no', 'category_id']);
- $fields = $resolver->resolve()->getFields();
-
- $this->assertArrayHasKey('order_no', $fields);
- $this->assertArrayHasKey('category_id', $fields);
- $this->assertArrayNotHasKey('product_slug', $fields);
- $this->assertCount(2, $fields);
- }
-
- public function test_it_does_not_resolve_always_ignored_fields(): void
- {
- $resolver = new SchemaResolverMySql('products');
- $fields = $resolver->resolve()->getFields();
-
- $this->assertArrayNotHasKey('created_at', $fields);
- $this->assertArrayNotHasKey('updated_at', $fields);
- $this->assertArrayNotHasKey('deleted_at', $fields);
- }
-
- public function test_it_does_not_resolve_auto_increments(): void
- {
- $resolver = new SchemaResolverMySql('products');
- $fields = $resolver->resolve()->getFields();
-
- $this->assertArrayNotHasKey('id', $fields);
- }
-
- public function test_it_can_resolve_text_fields(): void
- {
- $resolver = new SchemaResolverMySql('posts');
- $fields = $resolver->resolve()->getFields();
-
- $this->assertArrayHasKey('excerpt', $fields);
-
- $field = $fields['excerpt'];
- $this->assertInstanceOf(TextField::class, $field);
- $this->assertEquals('excerpt', $field->getName());
- $this->assertEquals(65535, $field->getMax());
- $this->assertFalse($field->isNullable());
- $this->assertFalse($field->hasDefault());
- $this->assertFalse($field->isUnique());
- }
-
- public function test_it_can_resolve_medium_text_fields(): void
- {
- $resolver = new SchemaResolverMySql('posts');
- $fields = $resolver->resolve()->getFields();
-
- $this->assertArrayHasKey('medium_content', $fields);
-
- $field = $fields['medium_content'];
- $this->assertInstanceOf(TextField::class, $field);
- $this->assertEquals('medium_content', $field->getName());
- $this->assertEquals(16777215, $field->getMax());
- $this->assertTrue($field->isNullable());
- $this->assertFalse($field->hasDefault());
- $this->assertFalse($field->isUnique());
- }
-
- public function test_it_can_resolve_tiny_text_fields(): void
- {
- $resolver = new SchemaResolverMySql('posts');
- $fields = $resolver->resolve()->getFields();
-
- $this->assertArrayHasKey('tiny_content', $fields);
-
- $field = $fields['tiny_content'];
- $this->assertInstanceOf(TextField::class, $field);
- $this->assertEquals('tiny_content', $field->getName());
- $this->assertEquals(255, $field->getMax());
- $this->assertTrue($field->isNullable());
- $this->assertFalse($field->hasDefault());
- $this->assertFalse($field->isUnique());
- }
-
- public function test_it_can_resolve_long_text_fields(): void
- {
- $resolver = new SchemaResolverMySql('posts');
- $fields = $resolver->resolve()->getFields();
-
- $this->assertArrayHasKey('content', $fields);
-
- $field = $fields['content'];
- $this->assertInstanceOf(TextField::class, $field);
- $this->assertEquals('content', $field->getName());
- $this->assertEquals(4294967295, $field->getMax());
- $this->assertTrue($field->isNullable());
- $this->assertFalse($field->hasDefault());
- $this->assertFalse($field->isUnique());
- }
-
- public function test_it_can_resolve_foreign_key_fields(): void
- {
- $field = $this->resolveField('category_id');
-
- $this->assertInstanceOf(ForeignKeyField::class, $field);
- $this->assertEquals('category_id', $field->getName());
- $this->assertEquals('categories', $field->getRelatedTable());
- $this->assertEquals('id', $field->getRelatedKeyName());
- $this->assertEquals('Category', $field->getRelatedModelClass());
- $this->assertTrue($field->isNullable());
- $this->assertFalse($field->hasDefault());
- $this->assertFalse($field->isUnique());
- $this->assertNull($field->generateCast());
- }
-
- public function test_it_can_resolve_enums_from_comments(): void
- {
- $field = $this->resolveField('status', 'payments');
-
- $this->assertInstanceOf(EnumField::class, $field);
- $this->assertEquals('status', $field->getName());
- $this->assertTrue($field->isNullable());
- $this->assertFalse($field->hasDefault());
- $this->assertFalse($field->isUnique());
- $this->assertEquals('PaymentStatuses::class', $field->generateCast());
- }
-
- public function test_it_can_resolve_boolean_fields(): void
- {
- $field = $this->resolveField('on_sale');
-
- $this->assertInstanceOf(BooleanField::class, $field);
- $this->assertEquals('on_sale', $field->getName());
- $this->assertFalse($field->isNullable());
- $this->assertFalse($field->getDefault());
- $this->assertTrue($field->hasDefault());
- $this->assertFalse($field->isUnique());
- $this->assertEquals('boolean', $field->generateCast());
- }
-
- public function test_it_can_resolve_unique_fields(): void
- {
- $field = $this->resolveField('slug');
-
- $this->assertInstanceOf(StringField::class, $field);
- $this->assertEquals('slug', $field->getName());
- $this->assertEquals(255, $field->getMax());
- $this->assertFalse($field->isNullable());
- $this->assertFalse($field->hasDefault());
- $this->assertTrue($field->isUnique());
- }
-
- public function test_it_can_resolve_string_fields(): void
- {
- $field = $this->resolveField('name');
-
- $this->assertInstanceOf(StringField::class, $field);
- $this->assertEquals('name', $field->getName());
- $this->assertEquals(255, $field->getMax());
- $this->assertFalse($field->isNullable());
- $this->assertFalse($field->hasDefault());
- $this->assertFalse($field->isUnique());
- $this->assertEquals('string', $field->generateCast());
- }
-
- public function test_it_can_resolve_integer_fields(): void
- {
- $field = $this->resolveField('stock');
-
- $this->assertInstanceOf(IntegerField::class, $field);
- $this->assertEquals('stock', $field->getName());
- $this->assertTrue($field->isUnsigned());
- $this->assertEquals(0, $field->getMin());
- $this->assertEquals(4294967295, $field->getMax());
- $this->assertFalse($field->isNullable());
- $this->assertFalse($field->hasDefault());
- $this->assertFalse($field->isUnique());
- $this->assertEquals('integer', $field->generateCast());
- }
-
- public function test_it_can_resolve_decimal_fields(): void
- {
- $field = $this->resolveField('price');
-
- $this->assertInstanceOf(DecimalField::class, $field);
- $this->assertEquals('price', $field->getName());
- $this->assertTrue($field->isUnsigned());
- $this->assertEquals(14, $field->getTotalDigits());
- $this->assertEquals(2, $field->getPlaces());
- $this->assertEquals(0, $field->getMin());
- $this->assertEquals(999999999999, $field->getMax());
- $this->assertFalse($field->isNullable());
- $this->assertFalse($field->hasDefault());
- $this->assertFalse($field->isUnique());
- $this->assertEquals('decimal:2', $field->generateCast());
- }
-
- public function test_it_can_resolve_enum_fields(): void
- {
- $field = $this->resolveField('status');
-
- $this->assertInstanceOf(EnumField::class, $field);
- $this->assertEquals('status', $field->getName());
- $this->assertEquals(['draft', 'published'], $field->getOptions());
- $this->assertFalse($field->isNullable());
- $this->assertFalse($field->hasDefault());
- $this->assertFalse($field->isUnique());
- $this->assertNull($field->generateCast());
- }
-
- public function test_it_can_resolve_year_fields(): void
- {
- $field = $this->resolveField('manufactured_year');
-
- $this->assertInstanceOf(YearField::class, $field);
- $this->assertEquals('manufactured_year', $field->getName());
- $this->assertEquals(1900, $field->getMin());
- $this->assertEquals(2100, $field->getMax());
- $this->assertFalse($field->isNullable());
- $this->assertFalse($field->hasDefault());
- $this->assertFalse($field->isUnique());
- $this->assertEquals('integer', $field->generateCast());
- }
-
- public function test_it_can_resolve_date_time_fields(): void
- {
- $field = $this->resolveField('published_at');
-
- $this->assertInstanceOf(DateTimeField::class, $field);
- $this->assertEquals('published_at', $field->getName());
- $this->assertNull($field->getMin());
- $this->assertNull($field->getMax());
- $this->assertFalse($field->isNullable());
- $this->assertFalse($field->hasDefault());
- $this->assertFalse($field->isUnique());
- $this->assertEquals('datetime', $field->generateCast());
-
- $field = $this->resolveField('expire_at');
-
- $this->assertInstanceOf(DateTimeField::class, $field);
- $this->assertEquals('expire_at', $field->getName());
- $this->assertNull($field->getMin());
- $this->assertNull($field->getMax());
- $this->assertFalse($field->isNullable());
- $this->assertFalse($field->hasDefault());
- $this->assertFalse($field->isUnique());
- $this->assertEquals('datetime', $field->generateCast());
- }
-
- public function test_it_can_resolve_date_fields(): void
- {
- $field = $this->resolveField('released_on');
-
- $this->assertInstanceOf(DateField::class, $field);
- $this->assertEquals('released_on', $field->getName());
- $this->assertNull($field->getMin());
- $this->assertNull($field->getMax());
- $this->assertFalse($field->isNullable());
- $this->assertFalse($field->hasDefault());
- $this->assertFalse($field->isUnique());
- $this->assertEquals('date', $field->generateCast());
- }
-
- public function test_it_can_resolve_time_fields(): void
- {
- $field = $this->resolveField('sale_time');
-
- $this->assertInstanceOf(TimeField::class, $field);
- $this->assertEquals('sale_time', $field->getName());
- $this->assertNull($field->getMin());
- $this->assertNull($field->getMax());
- $this->assertFalse($field->isNullable());
- $this->assertFalse($field->hasDefault());
- $this->assertFalse($field->isUnique());
- $this->assertEquals('datetime', $field->generateCast());
- }
-
- public function test_it_can_resolve_json_fields(): void
- {
- $field = $this->resolveField('features');
-
- $this->assertInstanceOf(JsonField::class, $field);
- $this->assertEquals('features', $field->getName());
- $this->assertNull($field->getMin());
- $this->assertNull($field->getMax());
- $this->assertFalse($field->isNullable());
- $this->assertFalse($field->hasDefault());
- $this->assertFalse($field->isUnique());
- $this->assertEquals('array', $field->generateCast());
- }
-
- protected function resolveField($field, $table = 'products'): Field
- {
- $resolver = new SchemaResolverMySql($table, [$field]);
- $fields = $resolver->resolve()->getFields();
-
- return $fields[$field];
- }
-}
diff --git a/tests/Unit/StringCaserTest.php b/tests/Unit/StringCaserTest.php
deleted file mode 100644
index 299ce4b..0000000
--- a/tests/Unit/StringCaserTest.php
+++ /dev/null
@@ -1,89 +0,0 @@
-assertEquals('formInputFields', StringCaser::pluralCamel($name));
- $this->assertEquals('form-input-fields', StringCaser::pluralKebab($name));
- $this->assertEquals('form_input_fields', StringCaser::pluralSnake($name));
- $this->assertEquals('FormInputFields', StringCaser::pluralStudly($name));
- $this->assertEquals('Form Input Fields', StringCaser::pluralTitle($name));
- $this->assertEquals('form input fields', StringCaser::pluralLower($name));
-
- $this->assertEquals('formInputField', StringCaser::singularCamel($name));
- $this->assertEquals('form-input-field', StringCaser::singularKebab($name));
- $this->assertEquals('form_input_field', StringCaser::singularSnake($name));
- $this->assertEquals('FormInputField', StringCaser::singularStudly($name));
- $this->assertEquals('Form Input Field', StringCaser::singularTitle($name));
- $this->assertEquals('form input field', StringCaser::singularLower($name));
-
- $this->assertEquals('formInputField', StringCaser::camel($name));
- $this->assertEquals('form-input-field', StringCaser::kebab($name));
- $this->assertEquals('form_input_field', StringCaser::snake($name));
- $this->assertEquals('FormInputField', StringCaser::studly($name));
- $this->assertEquals('Form Input Field', StringCaser::title($name));
- $this->assertEquals('form input field', StringCaser::lower($name));
- }
-
- public function test_it_can_generate_correct_string_cases_in_snake_format(): void
- {
- $name = 'Form_input_Field';
-
- $this->assertEquals('formInputFields', StringCaser::pluralCamel($name));
- $this->assertEquals('form-input-fields', StringCaser::pluralKebab($name));
- $this->assertEquals('form_input_fields', StringCaser::pluralSnake($name));
- $this->assertEquals('FormInputFields', StringCaser::pluralStudly($name));
- $this->assertEquals('Form Input Fields', StringCaser::pluralTitle($name));
- $this->assertEquals('form input fields', StringCaser::pluralLower($name));
-
- $this->assertEquals('formInputField', StringCaser::singularCamel($name));
- $this->assertEquals('form-input-field', StringCaser::singularKebab($name));
- $this->assertEquals('form_input_field', StringCaser::singularSnake($name));
- $this->assertEquals('FormInputField', StringCaser::singularStudly($name));
- $this->assertEquals('Form Input Field', StringCaser::singularTitle($name));
- $this->assertEquals('form input field', StringCaser::singularLower($name));
-
- $this->assertEquals('formInputField', StringCaser::camel($name));
- $this->assertEquals('form-input-field', StringCaser::kebab($name));
- $this->assertEquals('form_input_field', StringCaser::snake($name));
- $this->assertEquals('FormInputField', StringCaser::studly($name));
- $this->assertEquals('Form Input Field', StringCaser::title($name));
- $this->assertEquals('form input field', StringCaser::lower($name));
- }
-
- public function test_it_can_generate_correct_string_cases_in_kebab_format(): void
- {
- $name = 'form-input-field';
-
- $this->assertEquals('formInputFields', StringCaser::pluralCamel($name));
- $this->assertEquals('form-input-fields', StringCaser::pluralKebab($name));
- $this->assertEquals('form_input_fields', StringCaser::pluralSnake($name));
- $this->assertEquals('FormInputFields', StringCaser::pluralStudly($name));
- $this->assertEquals('Form Input Fields', StringCaser::pluralTitle($name));
- $this->assertEquals('form input fields', StringCaser::pluralLower($name));
-
- $this->assertEquals('formInputField', StringCaser::singularCamel($name));
- $this->assertEquals('form-input-field', StringCaser::singularKebab($name));
- $this->assertEquals('form_input_field', StringCaser::singularSnake($name));
- $this->assertEquals('FormInputField', StringCaser::singularStudly($name));
- $this->assertEquals('Form Input Field', StringCaser::singularTitle($name));
- $this->assertEquals('form input field', StringCaser::singularLower($name));
-
- $this->assertEquals('formInputField', StringCaser::camel($name));
- $this->assertEquals('form-input-field', StringCaser::kebab($name));
- $this->assertEquals('form_input_field', StringCaser::snake($name));
- $this->assertEquals('FormInputField', StringCaser::studly($name));
- $this->assertEquals('Form Input Field', StringCaser::title($name));
- $this->assertEquals('form input field', StringCaser::lower($name));
- }
-
-
-}
diff --git a/tests/Unit/StubRendererTest.php b/tests/Unit/StubRendererTest.php
deleted file mode 100644
index 374d436..0000000
--- a/tests/Unit/StubRendererTest.php
+++ /dev/null
@@ -1,100 +0,0 @@
-getRenderer();
-
- $expected_content = $this->getTestStubContents('SingleNameCases.php');
- $actual_content = $renderer->replaceFileNames($this->getTestStubPath('NameCases.stub'), 'category');
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_replace_multiple_names(): void
- {
- $renderer = $this->getRenderer();
-
- $expected_content = $this->getTestStubContents('MultipleNameCases.php');
- $actual_content = $renderer->replaceFileNames($this->getTestStubPath('NameCases.stub'), 'form_input_field');
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_replace_multiple_names_with_suffix(): void
- {
- $renderer = $this->getRenderer();
-
- $expected_content = $this->getTestStubContents('MultipleNameCases.php');
- $actual_content = $renderer->replaceFileNames($this->getTestStubPath('NameCasesSuffix.stub'), 'form_input_field', 'Model');
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_replace_multiple_names_in_studly_format(): void
- {
- $renderer = $this->getRenderer();
-
- $expected_content = $this->getTestStubContents('MultipleNameCases.php');
- $actual_content = $renderer->replaceFileNames($this->getTestStubPath('NameCases.stub'), 'FormInputField');
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_replace_multiple_names_in_camel_format(): void
- {
- $renderer = $this->getRenderer();
-
- $expected_content = $this->getTestStubContents('MultipleNameCases.php');
- $actual_content = $renderer->replaceFileNames($this->getTestStubPath('NameCases.stub'), 'formInputField');
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_append_to_a_stub_without_removing_marker(): void
- {
- $renderer = $this->getRenderer();
-
- $expected_content = $this->getTestStubContents('AfterAppend.php');
- $actual_content = $renderer->appendToFile($this->getTestStubPath('BeforeAppend.php'), " 'product',\n", "// append here - DONOT REMOVE\n");
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_append_to_a_stub_with_removing_marker(): void
- {
- $renderer = $this->getRenderer();
-
- $expected_content = $this->getTestStubContents('AfterAppendMarkerRemoved.php');
- $actual_content = $renderer->appendToFile($this->getTestStubPath('BeforeAppend.php'), "'product',\n", "// append here - DONOT REMOVE\n", false);
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- public function test_it_can_add_indentation_to_multiple_lines(): void
- {
- $renderer = $this->getRenderer();
-
- $expected_content = $this->getTestStubContents('Controllers/_onSaleBooleanIndented.stub');
- $actual_content = $renderer->addIndentation($this->getTestStubContents('Controllers/_onSaleBoolean.stub'), 1);
-
- $this->assertEquals($expected_content, $actual_content);
-
- $expected_content = $this->getTestStubContents('Controllers/_onSaleBoolean.stub');
- $actual_content = $renderer->addIndentation($this->getTestStubContents('Controllers/_onSaleBoolean.stub'), 0);
-
- $this->assertEquals($expected_content, $actual_content);
- }
-
- protected function getRenderer(): StubRenderer
- {
- return $this->app->make(StubRenderer::class);
- }
-
-}
diff --git a/tests/Unit/Support/ImportsTest.php b/tests/Unit/Support/ImportsTest.php
new file mode 100644
index 0000000..86cc687
--- /dev/null
+++ b/tests/Unit/Support/ImportsTest.php
@@ -0,0 +1,36 @@
+assertEquals('Model', $imports->add('Illuminate\\Database\\Eloquent\\Model'));
+ $this->assertEquals('Category', $imports->add('App\\Models\\Category'));
+ $this->assertEquals('BelongsTo', $imports->add('\\Illuminate\\Database\\Eloquent\\Relations\\BelongsTo'));
+
+ // classes of the namespace itself need no import
+ $this->assertEquals(
+ "use Illuminate\\Database\\Eloquent\\Model;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;",
+ $imports->render()
+ );
+ }
+
+ #[Test]
+ public function it_uses_the_qualified_name_for_a_clashing_basename(): void
+ {
+ $imports = new Imports;
+
+ $this->assertEquals('Form', $imports->add('Javaabu\\Stencil\\Form'));
+ $this->assertEquals('\\App\\Stencil\\Form', $imports->add('App\\Stencil\\Form'));
+ $this->assertTrue($imports->has('Javaabu\\Stencil\\Form'));
+ }
+}
diff --git a/tests/Unit/Support/NamingTest.php b/tests/Unit/Support/NamingTest.php
new file mode 100644
index 0000000..172dffc
--- /dev/null
+++ b/tests/Unit/Support/NamingTest.php
@@ -0,0 +1,61 @@
+assertEquals('category', Naming::belongsTo('category_id', 'categories'));
+ $this->assertEquals('blogPost', Naming::belongsTo('blog_post_id', 'blog_posts'));
+ $this->assertEquals('product', Naming::belongsTo('product_slug', 'products'));
+ $this->assertEquals('owner', Naming::belongsTo('owner', 'users'));
+ }
+
+ #[Test]
+ public function it_parses_media_collections(): void
+ {
+ $collections = MediaCollection::parse('featured_image, gallery:image:multiple,documents:document:multiple,video:video');
+
+ $this->assertCount(4, $collections);
+ $this->assertEquals(['featured_image', 'image', false], [$collections[0]->name, $collections[0]->type, $collections[0]->multiple]);
+ $this->assertEquals(['gallery', 'image', true], [$collections[1]->name, $collections[1]->type, $collections[1]->multiple]);
+ $this->assertEquals(['documents', 'document', true], [$collections[2]->name, $collections[2]->type, $collections[2]->multiple]);
+ $this->assertEquals('Featured image', $collections[0]->label());
+ }
+
+ #[Test]
+ public function it_rejects_unknown_media_types(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+
+ MediaCollection::parse('cover:picture');
+ }
+
+ #[Test]
+ public function it_reads_resource_options(): void
+ {
+ $options = ResourceOptions::fromArray([
+ 'columns' => 'name, price',
+ 'index' => 'name',
+ 'form' => null,
+ 'media' => 'cover',
+ 'translatable' => 'title,body',
+ ]);
+
+ $this->assertEquals(['name', 'price'], $options->columns);
+ $this->assertEquals(['name'], $options->index);
+ $this->assertNull($options->form);
+ $this->assertCount(1, $options->media);
+ $this->assertEquals(['title', 'body'], $options->translatable);
+ $this->assertTrue(ResourceOptions::fromArray(['translatable' => 'auto'])->translatable);
+ }
+}
diff --git a/tests/Unit/Support/PhpFileTest.php b/tests/Unit/Support/PhpFileTest.php
new file mode 100644
index 0000000..c9f3201
--- /dev/null
+++ b/tests/Unit/Support/PhpFileTest.php
@@ -0,0 +1,78 @@
+provider, 'Javaabu\\Permissions\\Permissions');
+ $code = PhpFile::addImport($code, 'Javaabu\\Permissions\\Permissions');
+ $code = PhpFile::addImport($code, 'App\\Models\\Product');
+
+ $this->assertStringContainsString("use App\\Models\\Product;\nuse Illuminate\\Support\\ServiceProvider;\nuse Javaabu\\Permissions\\Permissions;\n", $code);
+ $this->assertEquals(1, substr_count($code, 'use Javaabu\\Permissions\\Permissions;'));
+ }
+
+ #[Test]
+ public function it_appends_to_a_method_once(): void
+ {
+ $code = PhpFile::appendToMethod($this->provider, 'boot', "Permissions::resource('products');");
+ $code = PhpFile::appendToMethod($code, 'boot', "Permissions::resource('products');");
+ $code = PhpFile::appendToMethod($code, 'boot', "Permissions::resource('tags');");
+
+ $this->assertStringContainsString(
+ " //\n\n Permissions::resource('products');\n\n Permissions::resource('tags');\n }",
+ $code
+ );
+ }
+
+ #[Test]
+ public function it_fills_an_empty_method(): void
+ {
+ $code = PhpFile::appendToMethod("assertStringContainsString(" {\n one();\n }", $code);
+ }
+
+ #[Test]
+ public function it_throws_when_the_method_is_missing(): void
+ {
+ $this->expectException(RuntimeException::class);
+
+ PhpFile::appendToMethod($this->provider, 'register', 'one();');
+ }
+
+ #[Test]
+ public function it_inserts_before_a_marker_with_its_indentation(): void
+ {
+ $code = "group(function () {\n // marker\n});";
+
+ $code = PhpFile::insertBeforeMarker($code, '// marker', "one();\ntwo();");
+ $code = PhpFile::insertBeforeMarker($code, '// marker', "one();\ntwo();");
+
+ $this->assertEquals("group(function () {\n one();\n two();\n\n // marker\n});", $code);
+ }
+}
diff --git a/tests/Unit/Support/StubTest.php b/tests/Unit/Support/StubTest.php
new file mode 100644
index 0000000..70a2036
--- /dev/null
+++ b/tests/Unit/Support/StubTest.php
@@ -0,0 +1,39 @@
+assertEquals("class A\n{\n one();\n\n two();\n}", Stub::fill($template, ['body' => "one();\n\ntwo();"]));
+ }
+
+ #[Test]
+ public function it_removes_the_line_of_an_empty_block(): void
+ {
+ $template = "class A\n{\n {{ traits }}\n\n {{ body }}\n}";
+
+ $this->assertEquals("class A\n{\n body();\n}", Stub::fill($template, ['traits' => '', 'body' => 'body();']));
+ }
+
+ #[Test]
+ public function it_replaces_inline_placeholders_and_keeps_unknown_ones(): void
+ {
+ $this->assertEquals('Product {{ other }} {{ $blade }}', Stub::fill('{{ name }} {{ other }} {{ $blade }}', ['name' => 'Product']));
+ }
+
+ #[Test]
+ public function it_indents_code(): void
+ {
+ $this->assertEquals(" a\n\n b", Stub::indent("a\n\nb"));
+ $this->assertEquals(' a', Stub::indent('a', 2));
+ }
+}
diff --git a/tests/snapshots/categories.blade.txt b/tests/snapshots/categories.blade.txt
new file mode 100644
index 0000000..d13cf65
--- /dev/null
+++ b/tests/snapshots/categories.blade.txt
@@ -0,0 +1,238 @@
+// app/Models/Category.php
+ */
+ use HasFactory;
+
+ /**
+ * The attributes that are mass assignable.
+ *
+ * @var list
+ */
+ protected $fillable = [
+ 'name',
+ 'slug',
+ ];
+
+ /**
+ * @return HasMany
+ */
+ public function allForeigns(): HasMany
+ {
+ return $this->hasMany(AllForeign::class);
+ }
+
+ /**
+ * @return HasMany
+ */
+ public function customers(): HasMany
+ {
+ return $this->hasMany(Customer::class);
+ }
+
+ /**
+ * @return HasMany
+ */
+ public function orders(): HasMany
+ {
+ return $this->hasMany(Order::class);
+ }
+
+ /**
+ * @return HasMany
+ */
+ public function products(): HasMany
+ {
+ return $this->hasMany(Product::class);
+ }
+
+ /**
+ * @return HasMany
+ */
+ public function singleValueEnumOrders(): HasMany
+ {
+ return $this->hasMany(SingleValueEnumOrder::class);
+ }
+}
+
+// app/Filters/CategoryFilters.php
+label(__('Search'))->columns(['name', 'slug']),
+ DateRangeFilter::make('created_at')->label(__('Created at')),
+ ];
+ }
+
+ protected function sorts(): array
+ {
+ return [
+ Sort::make('name')->label(__('Name')),
+ Sort::make('slug')->label(__('Slug')),
+ Sort::make('created_at')->label(__('Created at')),
+ Sort::make('updated_at')->label(__('Updated at')),
+ ];
+ }
+}
+
+// resources/views/admin/categories/index.blade.php
+@extends('layouts.admin')
+
+@section('title', __('Categories'))
+
+@section('content')
+
+
+
{{ __('Categories') }}
+
+
+ @can('create', \App\Models\Category::class)
+
+ {{ __('New category') }}
+
+ @endcan
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @forelse($categories as $category)
+
+
+ @can('view', $category)
+ {{ $category->name }}
+ @else
+ {{ $category->name }}
+ @endcan
+
+
+
+ @empty
+
+ {{ $filters->hasActiveFilters() ? __('No categories match these filters.') : __('No categories yet.') }}
+
+ @endforelse
+
+
+ @if($categories->hasPages())
+
+
+
+ @endif
+
+@endsection
+
+// resources/views/admin/categories/create.blade.php
+@extends('layouts.admin')
+
+@section('title', __('New category'))
+
+@section('content')
+
+
+ {{ $form }}
+@endsection
+
+// resources/views/admin/categories/edit.blade.php
+@extends('layouts.admin')
+
+@section('title', __('Edit category'))
+
+@section('content')
+
+
+
+
+ {{ __('View') }}
+
+
+
+ {{ $form }}
+@endsection
+
+// resources/views/admin/categories/show.blade.php
+@extends('layouts.admin')
+
+@section('title', $category->name)
+
+@section('content')
+
+
+
+
+ @can('update', $category)
+
+ {{ __('Edit') }}
+
+ @endcan
+
+ @can('delete', $category)
+
+ @endcan
+
+
+
+
+
+
+
+@endsection
diff --git a/tests/snapshots/orders.enum-class-and-a-key-to-a-slug.txt b/tests/snapshots/orders.enum-class-and-a-key-to-a-slug.txt
new file mode 100644
index 0000000..35f1f65
--- /dev/null
+++ b/tests/snapshots/orders.enum-class-and-a-key-to-a-slug.txt
@@ -0,0 +1,382 @@
+// app/Models/Order.php
+ */
+ use HasFactory;
+
+ /**
+ * The attributes that are mass assignable.
+ *
+ * @var list
+ */
+ protected $fillable = [
+ 'order_no',
+ 'category_id',
+ 'product_slug',
+ 'status',
+ ];
+
+ /**
+ * Get the attributes that should be cast.
+ *
+ * @return array
+ */
+ protected function casts(): array
+ {
+ return [
+ 'status' => OrderStatuses::class,
+ ];
+ }
+
+ /**
+ * @return BelongsTo
+ */
+ public function product(): BelongsTo
+ {
+ return $this->belongsTo(Product::class, 'product_slug', 'slug');
+ }
+
+ /**
+ * @return BelongsTo
+ */
+ public function category(): BelongsTo
+ {
+ return $this->belongsTo(Category::class);
+ }
+
+ /**
+ * @return HasMany
+ */
+ public function payments(): HasMany
+ {
+ return $this->hasMany(Payment::class);
+ }
+}
+
+// database/factories/OrderFactory.php
+
+ */
+class OrderFactory extends Factory
+{
+ /**
+ * The name of the factory's corresponding model.
+ *
+ * @var class-string
+ */
+ protected $model = Order::class;
+
+ /**
+ * Define the model's default state.
+ *
+ * @return array
+ */
+ public function definition(): array
+ {
+ return [
+ 'order_no' => fake()->text(50),
+ 'category_id' => Category::factory(),
+ 'product_slug' => fn () => Product::factory()->create()->slug,
+ 'status' => fake()->randomElement(OrderStatuses::cases()),
+ ];
+ }
+}
+
+// app/Stencil/OrderForm.php
+columns(2)
+ ->schema([
+ TextInput::make('order_no')
+ ->label(__('Order no'))
+ ->required()
+ ->maxLength(255),
+ Select::make('category_id')
+ ->label(__('Category'))
+ ->required()
+ ->relationship('category', 'name')
+ ->searchable(),
+ Select::make('product_slug')
+ ->label(__('Product'))
+ ->required()
+ ->relationship('product', 'name')
+ ->searchable(),
+ Select::make('status')
+ ->label(__('Status'))
+ ->required()
+ ->options(OrderStatuses::class),
+ ]),
+ ];
+ }
+}
+
+// tests/Feature/Admin/OrdersControllerTest.php
+artisan('permissions:sync');
+ }
+
+ #[Test]
+ public function it_lists_the_orders(): void
+ {
+ $order = Order::factory()->create();
+
+ $this->actingAsAdmin('view_orders');
+
+ $this->get(route('admin.orders.index'))
+ ->assertOk()
+ ->assertSee($order->order_no);
+ }
+
+ #[Test]
+ public function it_does_not_list_the_orders_without_permission(): void
+ {
+ $this->actingAsAdmin();
+
+ $this->get(route('admin.orders.index'))
+ ->assertForbidden();
+ }
+
+ #[Test]
+ public function it_searches_the_orders(): void
+ {
+ $order = Order::factory()->create(['order_no' => 'ރަސްމީ ދުވަސް']);
+ $other = Order::factory()->create(['order_no' => 'Something else']);
+
+ $this->actingAsAdmin('view_orders');
+
+ $this->get(route('admin.orders.index', ['search' => 'ރަސްމީ']))
+ ->assertOk()
+ ->assertSee('ރަސްމީ ދުވަސް')
+ ->assertDontSee('Something else');
+ }
+
+ #[Test]
+ public function it_shows_the_new_order_form(): void
+ {
+ $this->actingAsAdmin('edit_orders');
+
+ $this->get(route('admin.orders.create'))
+ ->assertOk()
+ ->assertSee(__('New order'));
+ }
+
+ #[Test]
+ public function it_creates_a_order(): void
+ {
+ $category = Category::factory()->create();
+ $other_category = Category::factory()->create();
+ $product = Product::factory()->create();
+ $other_product = Product::factory()->create();
+
+ $this->actingAsAdmin('edit_orders');
+
+ $this->post(route('admin.orders.store'), [
+ 'order_no' => 'ރަސްމީ',
+ 'category_id' => $category->id,
+ 'product_slug' => $product->slug,
+ 'status' => OrderStatuses::Pending->value,
+ ])
+ ->assertRedirect()
+ ->assertSessionHasNoErrors();
+
+ $this->assertDatabaseHas('orders', [
+ 'order_no' => 'ރަސްމީ',
+ 'category_id' => $category->id,
+ 'product_slug' => $product->slug,
+ 'status' => OrderStatuses::Pending->value,
+ ]);
+ }
+
+ #[Test]
+ public function it_validates_new_orders(): void
+ {
+ $this->actingAsAdmin('edit_orders');
+
+ $this->post(route('admin.orders.store'), [
+ 'order_no' => [],
+ 'category_id' => -1,
+ 'product_slug' => 'ބަދަލު',
+ 'status' => 'ބަދަލު',
+ ])
+ ->assertSessionHasErrors(['order_no', 'category_id', 'product_slug', 'status']);
+
+ $this->assertDatabaseCount('orders', 0);
+ }
+
+ #[Test]
+ public function it_does_not_create_orders_without_permission(): void
+ {
+ $this->actingAsAdmin('view_orders');
+
+ $this->post(route('admin.orders.store'), [])
+ ->assertForbidden();
+
+ $this->assertDatabaseCount('orders', 0);
+ }
+
+ #[Test]
+ public function it_shows_a_order(): void
+ {
+ $order = Order::factory()->create();
+
+ $this->actingAsAdmin('view_orders');
+
+ $this->get(route('admin.orders.show', $order))
+ ->assertOk()
+ ->assertSee($order->order_no);
+ }
+
+ #[Test]
+ public function it_shows_the_edit_order_form(): void
+ {
+ $order = Order::factory()->create();
+
+ $this->actingAsAdmin('edit_orders');
+
+ $this->get(route('admin.orders.edit', $order))
+ ->assertOk()
+ ->assertSee(__('Edit order'));
+ }
+
+ #[Test]
+ public function it_updates_a_order(): void
+ {
+ $category = Category::factory()->create();
+ $other_category = Category::factory()->create();
+ $product = Product::factory()->create();
+ $other_product = Product::factory()->create();
+
+ $order = Order::factory()->create();
+
+ $this->actingAsAdmin('edit_orders');
+
+ $this->patch(route('admin.orders.update', $order), [
+ 'order_no' => 'ދިވެހި',
+ 'category_id' => $other_category->id,
+ 'product_slug' => $other_product->slug,
+ 'status' => OrderStatuses::Paid->value,
+ ])
+ ->assertRedirect()
+ ->assertSessionHasNoErrors();
+
+ $this->assertDatabaseHas('orders', [
+ 'id' => $order->id,
+ 'order_no' => 'ދިވެހި',
+ 'category_id' => $other_category->id,
+ 'product_slug' => $other_product->slug,
+ 'status' => OrderStatuses::Paid->value,
+ ]);
+ }
+
+ #[Test]
+ public function it_does_not_update_orders_without_permission(): void
+ {
+ $order = Order::factory()->create();
+
+ $this->actingAsAdmin('view_orders');
+
+ $this->patch(route('admin.orders.update', $order), [])
+ ->assertForbidden();
+ }
+
+ #[Test]
+ public function it_deletes_a_order(): void
+ {
+ $order = Order::factory()->create();
+
+ $this->actingAsAdmin('delete_orders');
+
+ $this->delete(route('admin.orders.destroy', $order))
+ ->assertRedirect(route('admin.orders.index'));
+
+ $this->assertModelMissing($order);
+ }
+
+ #[Test]
+ public function it_deletes_orders_in_bulk(): void
+ {
+ $orders = Order::factory()->count(2)->create();
+
+ $this->actingAsAdmin('view_orders', 'delete_orders');
+
+ $this->postJson(route('admin.orders.bulk'), [
+ 'action' => 'delete',
+ 'orders' => $orders->pluck('id')->all(),
+ ])
+ ->assertOk()
+ ->assertJson(['succeeded' => 2]);
+
+ $orders->each(fn (Order $record) => $this->assertModelMissing($record));
+ }
+
+ /**
+ * Log in as a user of the admin area with these permissions.
+ */
+ protected function actingAsAdmin(string ...$permissions): User
+ {
+ $user = User::factory()->create();
+
+ if ($permissions) {
+ $user->givePermissionTo($permissions);
+ }
+
+ $this->actingAs($user);
+
+ return $user;
+ }
+}
diff --git a/tests/snapshots/posts-with-media.blade.txt b/tests/snapshots/posts-with-media.blade.txt
new file mode 100644
index 0000000..91d8024
--- /dev/null
+++ b/tests/snapshots/posts-with-media.blade.txt
@@ -0,0 +1,286 @@
+// app/Models/Post.php
+ */
+ use HasFactory;
+
+ use InteractsWithAttachments;
+
+ /**
+ * The attributes that are mass assignable.
+ *
+ * @var list
+ */
+ protected $fillable = [
+ 'title',
+ 'published_at',
+ 'excerpt',
+ 'content',
+ 'medium_content',
+ 'tiny_content',
+ ];
+
+ /**
+ * Get the attributes that should be cast.
+ *
+ * @return array
+ */
+ protected function casts(): array
+ {
+ return [
+ 'published_at' => 'datetime',
+ ];
+ }
+
+ /**
+ * The media collections, picked from the media library.
+ */
+ public function registerAttachmentCollections(): void
+ {
+ $this->addAttachmentCollection('featured_image')
+ ->singleFile()
+ ->acceptsFile(fn (File $file) => FileTypes::isAllowed($file->mimeType, 'image'));
+
+ $this->addAttachmentCollection('gallery')
+ ->acceptsFile(fn (File $file) => FileTypes::isAllowed($file->mimeType, 'image'));
+
+ $this->addAttachmentCollection('attachments')
+ ->acceptsFile(fn (File $file) => FileTypes::isAllowed($file->mimeType, 'document'));
+ }
+}
+
+// app/Stencil/PostForm.php
+columns(2)
+ ->schema([
+ TextInput::make('title')
+ ->label(__('Title'))
+ ->required()
+ ->maxLength(255),
+ DateTimePicker::make('published_at')->label(__('Published at')),
+ Textarea::make('excerpt')
+ ->label(__('Excerpt'))
+ ->required()
+ ->rows(4)
+ ->maxLength(65535)
+ ->columnSpanFull(),
+ Textarea::make('content')
+ ->label(__('Content'))
+ ->rows(4)
+ ->maxLength(65535)
+ ->columnSpanFull(),
+ Textarea::make('medium_content')
+ ->label(__('Medium content'))
+ ->rows(4)
+ ->maxLength(65535)
+ ->columnSpanFull(),
+ Textarea::make('tiny_content')
+ ->label(__('Tiny content'))
+ ->rows(4)
+ ->maxLength(65535)
+ ->columnSpanFull(),
+ ]),
+ Section::make(__('Media'))
+ ->schema([
+ MediaPickerField::make('featured_image')
+ ->label(__('Featured image'))
+ ->mediaType('image'),
+ MediaPickerField::make('gallery')
+ ->label(__('Gallery'))
+ ->mediaType('image')
+ ->multiple(),
+ MediaPickerField::make('attachments')
+ ->label(__('Attachments'))
+ ->mediaType('document')
+ ->multiple(),
+ ]),
+ ];
+ }
+}
+
+// resources/views/admin/posts/index.blade.php
+@extends('layouts.admin')
+
+@section('title', __('Posts'))
+
+@section('content')
+
+
+
{{ __('Posts') }}
+
+
+ @can('create', \App\Models\Post::class)
+
+ {{ __('New post') }}
+
+ @endcan
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @forelse($posts as $post)
+
+
+ @can('view', $post)
+ {{ $post->title }}
+ @else
+ {{ $post->title }}
+ @endcan
+
+
+
+ @empty
+
+ {{ $filters->hasActiveFilters() ? __('No posts match these filters.') : __('No posts yet.') }}
+
+ @endforelse
+
+
+ @if($posts->hasPages())
+
+
+
+ @endif
+
+@endsection
+
+// resources/views/admin/posts/create.blade.php
+@extends('layouts.admin')
+
+@section('title', __('New post'))
+
+@section('content')
+
+
+ {{ $form }}
+@endsection
+
+// resources/views/admin/posts/edit.blade.php
+@extends('layouts.admin')
+
+@section('title', __('Edit post'))
+
+@section('content')
+
+
+
+
+ {{ __('View') }}
+
+
+
+ {{ $form }}
+@endsection
+
+// resources/views/admin/posts/show.blade.php
+@extends('layouts.admin')
+
+@section('title', $post->title)
+
+@section('content')
+
+
+
+
+ @can('update', $post)
+
+ {{ __('Edit') }}
+
+ @endcan
+
+ @can('delete', $post)
+
+ @endcan
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @foreach($post->getAttachments('gallery') as $attachment)
+
+ @endforeach
+
+
+
+
+
+ @foreach($post->getAttachmentMedia('attachments') as $media)
+ - {{ $media->name }}
+ @endforeach
+
+
+@endsection
diff --git a/tests/snapshots/posts-with-media.inertia.txt b/tests/snapshots/posts-with-media.inertia.txt
new file mode 100644
index 0000000..22740f9
--- /dev/null
+++ b/tests/snapshots/posts-with-media.inertia.txt
@@ -0,0 +1,264 @@
+// app/Http/Resources/Admin/PostResource.php
+
+ */
+ public function toArray(Request $request): array
+ {
+ return [
+ 'id' => $this->id,
+ 'title' => $this->title,
+ 'published_at' => $this->published_at?->format('Y-m-d H:i'),
+ 'excerpt' => $this->excerpt,
+ 'content' => $this->content,
+ 'medium_content' => $this->medium_content,
+ 'tiny_content' => $this->tiny_content,
+ 'featured_image' => $this->getFirstAttachmentUrl('featured_image') ?: null,
+ 'attachments' => $this->getAttachmentMedia('attachments')->map(fn ($media) => $media->getUrl())->values()->all(),
+ 'created_at' => $this->created_at?->format('Y-m-d H:i'),
+ 'updated_at' => $this->updated_at?->format('Y-m-d H:i'),
+ 'urls' => [
+ 'show' => route('admin.posts.show', $this->resource),
+ 'edit' => route('admin.posts.edit', $this->resource),
+ 'destroy' => route('admin.posts.destroy', $this->resource),
+ ],
+ 'can' => [
+ 'view' => (bool) $request->user()?->can('view', $this->resource),
+ 'update' => (bool) $request->user()?->can('update', $this->resource),
+ 'delete' => (bool) $request->user()?->can('delete', $this->resource),
+ ],
+ ];
+ }
+}
+
+// resources/js/pages/admin/posts/types.ts
+/** A post, as PostResource sends it. */
+export interface Post {
+ id: number
+ title: string
+ published_at: string | null
+ excerpt: string
+ content: string | null
+ medium_content: string | null
+ tiny_content: string | null
+ featured_image: string | null
+ attachments: string[]
+ created_at: string | null
+ updated_at: string | null
+ urls: { show: string; edit: string; destroy: string }
+ can: { view: boolean; update: boolean; delete: boolean }
+}
+
+// resources/js/pages/admin/posts/Index.tsx
+import { Head, Link, router } from "@inertiajs/react"
+import { BulkActionsTable, type BulkActionsSchema } from "@javaabu/bulk-actions"
+import { FilterBar, SortableHeading, type FiltersData } from "@javaabu/filters"
+import { type LaravelResourceCollection, LinkButton, Pagination, TableCell, TableEmptyRow, TableHeading, TableRow } from "@javaabu/forms"
+import { __ } from "@javaabu/i18n"
+import type { Post } from "./types"
+
+interface Props {
+ posts: Omit & { data: Post[] }
+ filters: FiltersData
+ bulkActions: BulkActionsSchema
+ createUrl: string | null
+}
+
+export default function Index({ posts, filters, bulkActions, createUrl }: Props) {
+ return (
+ <>
+
+
+
+
+
{__("Posts")}
+
+
+ {createUrl && (
+
+ {__("New post")}
+
+ )}
+
+
+
+
+ router.reload()}
+ headers={
+ <>
+
+
+ >
+ }
+ pagination={posts.meta.last_page > 1 ? : null}
+ >
+ {posts.data.length > 0 ? (
+ posts.data.map((post) => (
+
+
+ {post.can.view ? (
+
+ {post.title}
+
+ ) : (
+ post.title
+ )}
+
+
+
+ ))
+ ) : (
+
+ {filters.active > 0
+ ? __("No posts match these filters.")
+ : __("No posts yet.")}
+
+ )}
+
+ >
+ )
+}
+
+// resources/js/pages/admin/posts/Create.tsx
+import { Head } from "@inertiajs/react"
+import { __ } from "@javaabu/i18n"
+import { StencilMediaPicker } from "@javaabu/mediapicker"
+import { StencilForm, type StencilSchema } from "@javaabu/stencil"
+
+interface Props {
+ form: StencilSchema
+}
+
+export default function Create({ form }: Props) {
+ return (
+ <>
+
+
+
+
+
+ >
+ )
+}
+
+// resources/js/pages/admin/posts/Edit.tsx
+import { Head } from "@inertiajs/react"
+import { LinkButton } from "@javaabu/forms"
+import { __ } from "@javaabu/i18n"
+import { StencilMediaPicker } from "@javaabu/mediapicker"
+import { StencilForm, type StencilSchema } from "@javaabu/stencil"
+import type { Post } from "./types"
+
+interface Props {
+ post: Post
+ form: StencilSchema
+}
+
+export default function Edit({ post, form }: Props) {
+ return (
+ <>
+
+
+
+
+
{__("Edit post")}
+
+
+
+ {__("View")}
+
+
+
+
+ >
+ )
+}
+
+// resources/js/pages/admin/posts/Show.tsx
+import { Head, router } from "@inertiajs/react"
+import { Button, Card, Infolist, LinkButton, TextEntry } from "@javaabu/forms"
+import { __ } from "@javaabu/i18n"
+import type { Post } from "./types"
+
+interface Props {
+ post: Post
+}
+
+export default function Show({ post }: Props) {
+ return (
+ <>
+
+
+
+
+
{post.title}
+
+
+
+ {post.can.update && (
+
+ {__("Edit")}
+
+ )}
+ {post.can.delete && (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {post.featured_image &&
}
+
+
+
+
+
+ >
+ )
+}
diff --git a/tests/snapshots/products-nested-in-categories.blade.txt b/tests/snapshots/products-nested-in-categories.blade.txt
new file mode 100644
index 0000000..415a194
--- /dev/null
+++ b/tests/snapshots/products-nested-in-categories.blade.txt
@@ -0,0 +1,670 @@
+// app/Http/Controllers/Admin/ProductsController.php
+apply(Product::query()->whereBelongsTo($category, 'category'))
+ ->paginate($request->integer('per_page', 20))
+ ->withQueryString();
+
+ return view('admin.products.index', [
+ 'category' => $category,
+ 'products' => $products,
+ 'filters' => $filters,
+ 'bulk_actions' => ProductBulkActions::make()->paginator($products),
+ ]);
+ }
+
+ /**
+ * Show the form for a new product.
+ */
+ public function create(Category $category): View
+ {
+ Gate::authorize('create', Product::class);
+
+ return view('admin.products.create', [
+ 'category' => $category,
+ 'form' => ProductForm::make()->model(Product::class)->action(route('admin.categories.products.store', $category)),
+ ]);
+ }
+
+ /**
+ * Save a new product.
+ */
+ public function store(Category $category): RedirectResponse
+ {
+ Gate::authorize('create', Product::class);
+
+ $product = ProductForm::make()->model((new Product)->category()->associate($category))->save();
+
+ return redirect(route('admin.categories.products.show', [$category, $product]))
+ ->with('status', __('Product created.'));
+ }
+
+ /**
+ * Show a product.
+ */
+ public function show(Category $category, Product $product): View
+ {
+ Gate::authorize('view', $product);
+
+ $product->load(['tags']);
+
+ return view('admin.products.show', [
+ 'category' => $category,
+ 'product' => $product,
+ ]);
+ }
+
+ /**
+ * Show the form for editing a product.
+ */
+ public function edit(Category $category, Product $product): View
+ {
+ Gate::authorize('update', $product);
+
+ return view('admin.products.edit', [
+ 'category' => $category,
+ 'product' => $product,
+ 'form' => ProductForm::make()->model($product)->action(route('admin.categories.products.update', [$category, $product])),
+ ]);
+ }
+
+ /**
+ * Save the changes to a product.
+ */
+ public function update(Category $category, Product $product): RedirectResponse
+ {
+ Gate::authorize('update', $product);
+
+ ProductForm::make()->model($product)->save();
+
+ return redirect(route('admin.categories.products.show', [$category, $product]))
+ ->with('status', __('Product updated.'));
+ }
+
+ /**
+ * Delete a product.
+ */
+ public function destroy(Category $category, Product $product): RedirectResponse
+ {
+ Gate::authorize('delete', $product);
+
+ $product->delete();
+
+ return redirect(route('admin.categories.products.index', $category))
+ ->with('status', __('Product deleted.'));
+ }
+
+ /**
+ * Restore a deleted product.
+ */
+ public function restore(Category $category, Product $product): RedirectResponse
+ {
+ Gate::authorize('restore', $product);
+
+ $product->restore();
+
+ return redirect(route('admin.categories.products.show', [$category, $product]))
+ ->with('status', __('Product restored.'));
+ }
+
+ /**
+ * Permanently delete a product.
+ */
+ public function forceDelete(Category $category, Product $product): RedirectResponse
+ {
+ Gate::authorize('forceDelete', $product);
+
+ $product->forceDelete();
+
+ return redirect(route('admin.categories.products.index', $category))
+ ->with('status', __('Product permanently deleted.'));
+ }
+}
+
+// app/BulkActions/ProductBulkActions.php
+filters(ProductFilters::class);
+
+ // only the products of the category in the URL
+ $this->query(fn () => Product::query()->whereBelongsTo($this->category(), 'category'));
+ $this->url(fn () => route('admin.categories.products.bulk', $this->category()));
+ }
+
+ protected function actions(): array
+ {
+ return [
+ DeleteAction::make()->can('delete_products'),
+ RestoreAction::make()->can('delete_products'),
+ ForceDeleteAction::make()->can('force_delete_products'),
+ ];
+ }
+
+ /**
+ * The category in the URL.
+ */
+ protected function category(): Category
+ {
+ $category = request()->route('category');
+
+ if ($category instanceof Category) {
+ return $category;
+ }
+
+ return Category::query()->where('id', $category)->firstOrFail();
+ }
+}
+
+// routes/admin.php
+// Products
+Route::resource('categories.products', ProductsController::class)->scoped()->withTrashed(['show']);
+Route::patch('categories/{category}/products/{product}/restore', [ProductsController::class, 'restore'])->scopeBindings()->withTrashed()->name('categories.products.restore');
+Route::delete('categories/{category}/products/{product}/force-delete', [ProductsController::class, 'forceDelete'])->scopeBindings()->withTrashed()->name('categories.products.force-delete');
+Route::bulkActions('categories/{category}/products', ProductBulkActions::class, ['name' => 'categories.products.bulk']);
+// routes/web.php
+require __DIR__ . '/admin.php';
+// resources/views/admin/products/index.blade.php
+@extends('layouts.admin')
+
+@section('title', __('Products'))
+
+@section('content')
+
+
+
+
{{ __('Products') }}
+
+
+ @can('create', \App\Models\Product::class)
+
+ {{ __('New product') }}
+
+ @endcan
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @forelse($products as $product)
+
+
+ @can('view', $product)
+ {{ $product->name }}
+ @else
+ {{ $product->name }}
+ @endcan
+
+
+
+ @empty
+
+ {{ $filters->hasActiveFilters() ? __('No products match these filters.') : __('No products yet.') }}
+
+ @endforelse
+
+
+ @if($products->hasPages())
+
+
+
+ @endif
+
+@endsection
+
+// resources/views/admin/products/create.blade.php
+@extends('layouts.admin')
+
+@section('title', __('New product'))
+
+@section('content')
+
+
+ {{ $form }}
+@endsection
+
+// resources/views/admin/products/edit.blade.php
+@extends('layouts.admin')
+
+@section('title', __('Edit product'))
+
+@section('content')
+
+
+
+
+ {{ __('View') }}
+
+
+
+ {{ $form }}
+@endsection
+
+// resources/views/admin/products/show.blade.php
+@extends('layouts.admin')
+
+@section('title', $product->name)
+
+@section('content')
+
+
+
+
+ @if($product->trashed())
+ @can('restore', $product)
+
+ @endcan
+
+ @can('forceDelete', $product)
+
+ @endcan
+ @else
+ @can('update', $product)
+
+ {{ __('Edit') }}
+
+ @endcan
+
+ @can('delete', $product)
+
+ @endcan
+ @endif
+
+
+
+ @if($product->trashed())
+
+ {{ __('This product is deleted.') }}
+
+ @endif
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+@endsection
+
+// tests/Feature/Admin/ProductsControllerTest.php
+artisan('permissions:sync');
+ }
+
+ #[Test]
+ public function it_lists_the_products(): void
+ {
+ $category = Category::factory()->create();
+ $product = Product::factory()->for($category, 'category')->create();
+
+ $this->actingAsAdmin('view_products');
+
+ $this->get(route('admin.categories.products.index', $category))
+ ->assertOk()
+ ->assertSee($product->name);
+ }
+
+ #[Test]
+ public function it_does_not_list_the_products_without_permission(): void
+ {
+ $category = Category::factory()->create();
+ $this->actingAsAdmin();
+
+ $this->get(route('admin.categories.products.index', $category))
+ ->assertForbidden();
+ }
+
+ #[Test]
+ public function it_searches_the_products(): void
+ {
+ $category = Category::factory()->create();
+ $product = Product::factory()->for($category, 'category')->create(['name' => 'ރަސްމީ ދުވަސް']);
+ $other = Product::factory()->for($category, 'category')->create(['name' => 'Something else']);
+
+ $this->actingAsAdmin('view_products');
+
+ $this->get(route('admin.categories.products.index', [$category, 'search' => 'ރަސްމީ']))
+ ->assertOk()
+ ->assertSee('ރަސްމީ ދުވަސް')
+ ->assertDontSee('Something else');
+ }
+
+ #[Test]
+ public function it_shows_the_new_product_form(): void
+ {
+ $category = Category::factory()->create();
+ $this->actingAsAdmin('edit_products');
+
+ $this->get(route('admin.categories.products.create', $category))
+ ->assertOk()
+ ->assertSee(__('New product'));
+ }
+
+ #[Test]
+ public function it_creates_a_product(): void
+ {
+ $category = Category::factory()->create();
+ $tags = Tag::factory()->count(2)->create();
+
+ $this->actingAsAdmin('edit_products');
+
+ $this->post(route('admin.categories.products.store', $category), [
+ 'name' => 'ރަސްމީ',
+ 'price' => 10.5,
+ 'status' => 'draft',
+ 'tags' => $tags->pluck('id')->all(),
+ ])
+ ->assertRedirect()
+ ->assertSessionHasNoErrors();
+
+ $this->assertDatabaseHas('products', [
+ 'category_id' => $category->id,
+ 'name' => 'ރަސްމީ',
+ 'price' => 10.5,
+ 'status' => 'draft',
+ ]);
+
+ $product = Product::query()->latest('id')->first();
+
+ $this->assertEquals($tags->pluck('id')->all(), $product->tags()->pluck('tags.id')->sort()->values()->all());
+ }
+
+ #[Test]
+ public function it_validates_new_products(): void
+ {
+ $category = Category::factory()->create();
+ $this->actingAsAdmin('edit_products');
+
+ $this->post(route('admin.categories.products.store', $category), [
+ 'name' => [],
+ 'price' => 'ބަދަލު',
+ 'status' => 'ބަދަލު',
+ ])
+ ->assertSessionHasErrors(['name', 'price', 'status']);
+
+ $this->assertDatabaseCount('products', 0);
+ }
+
+ #[Test]
+ public function it_does_not_create_products_without_permission(): void
+ {
+ $category = Category::factory()->create();
+ $this->actingAsAdmin('view_products');
+
+ $this->post(route('admin.categories.products.store', $category), [])
+ ->assertForbidden();
+
+ $this->assertDatabaseCount('products', 0);
+ }
+
+ #[Test]
+ public function it_shows_a_product(): void
+ {
+ $category = Category::factory()->create();
+ $product = Product::factory()->for($category, 'category')->create();
+
+ $this->actingAsAdmin('view_products');
+
+ $this->get(route('admin.categories.products.show', [$category, $product]))
+ ->assertOk()
+ ->assertSee($product->name);
+ }
+
+ #[Test]
+ public function it_shows_the_edit_product_form(): void
+ {
+ $category = Category::factory()->create();
+ $product = Product::factory()->for($category, 'category')->create();
+
+ $this->actingAsAdmin('edit_products');
+
+ $this->get(route('admin.categories.products.edit', [$category, $product]))
+ ->assertOk()
+ ->assertSee(__('Edit product'));
+ }
+
+ #[Test]
+ public function it_updates_a_product(): void
+ {
+ $category = Category::factory()->create();
+ $tags = Tag::factory()->count(2)->create();
+
+ $product = Product::factory()->for($category, 'category')->create();
+
+ $this->actingAsAdmin('edit_products');
+
+ $this->patch(route('admin.categories.products.update', [$category, $product]), [
+ 'name' => 'ދިވެހި',
+ 'price' => 20.25,
+ 'status' => 'published',
+ 'tags' => [$tags->first()->id],
+ ])
+ ->assertRedirect()
+ ->assertSessionHasNoErrors();
+
+ $this->assertDatabaseHas('products', [
+ 'id' => $product->id,
+ 'name' => 'ދިވެހި',
+ 'price' => 20.25,
+ 'status' => 'published',
+ ]);
+
+ $product->refresh();
+
+ $this->assertEquals([$tags->first()->id], $product->tags()->pluck('tags.id')->sort()->values()->all());
+ }
+
+ #[Test]
+ public function it_does_not_update_products_without_permission(): void
+ {
+ $category = Category::factory()->create();
+ $product = Product::factory()->for($category, 'category')->create();
+
+ $this->actingAsAdmin('view_products');
+
+ $this->patch(route('admin.categories.products.update', [$category, $product]), [])
+ ->assertForbidden();
+ }
+
+ #[Test]
+ public function it_deletes_a_product(): void
+ {
+ $category = Category::factory()->create();
+ $product = Product::factory()->for($category, 'category')->create();
+
+ $this->actingAsAdmin('delete_products');
+
+ $this->delete(route('admin.categories.products.destroy', [$category, $product]))
+ ->assertRedirect(route('admin.categories.products.index', $category));
+
+ $this->assertSoftDeleted($product);
+ }
+
+ #[Test]
+ public function it_restores_a_deleted_product(): void
+ {
+ $category = Category::factory()->create();
+ $product = Product::factory()->for($category, 'category')->create();
+ $product->delete();
+
+ $this->actingAsAdmin('delete_products');
+
+ $this->patch(route('admin.categories.products.restore', [$category, $product]))
+ ->assertRedirect();
+
+ $this->assertNotSoftDeleted($product);
+ }
+
+ #[Test]
+ public function it_permanently_deletes_a_product(): void
+ {
+ $category = Category::factory()->create();
+ $product = Product::factory()->for($category, 'category')->create();
+ $product->delete();
+
+ $this->actingAsAdmin('force_delete_products');
+
+ $this->delete(route('admin.categories.products.force-delete', [$category, $product]))
+ ->assertRedirect(route('admin.categories.products.index', $category));
+
+ $this->assertModelMissing($product);
+ }
+
+ #[Test]
+ public function it_deletes_products_in_bulk(): void
+ {
+ $category = Category::factory()->create();
+ $products = Product::factory()->for($category, 'category')->count(2)->create();
+
+ $this->actingAsAdmin('view_products', 'delete_products');
+
+ $this->postJson(route('admin.categories.products.bulk', $category), [
+ 'action' => 'delete',
+ 'products' => $products->pluck('id')->all(),
+ ])
+ ->assertOk()
+ ->assertJson(['succeeded' => 2]);
+
+ $products->each(fn (Product $record) => $this->assertSoftDeleted($record));
+ }
+
+ /**
+ * Log in as a user of the admin area with these permissions.
+ */
+ protected function actingAsAdmin(string ...$permissions): User
+ {
+ $user = User::factory()->create();
+
+ if ($permissions) {
+ $user->givePermissionTo($permissions);
+ }
+
+ $this->actingAs($user);
+
+ return $user;
+ }
+}
diff --git a/tests/snapshots/products-nested-in-categories.inertia.txt b/tests/snapshots/products-nested-in-categories.inertia.txt
new file mode 100644
index 0000000..a6d1306
--- /dev/null
+++ b/tests/snapshots/products-nested-in-categories.inertia.txt
@@ -0,0 +1,768 @@
+// app/Http/Controllers/Admin/ProductsController.php
+apply(Product::query()->whereBelongsTo($category, 'category'))
+ ->paginate($request->integer('per_page', 20))
+ ->withQueryString();
+
+ return Inertia::render('admin/products/Index', [
+ 'category' => $category,
+ 'products' => ProductResource::collection($products),
+ 'filters' => $filters,
+ 'bulkActions' => ProductBulkActions::make()->paginator($products),
+ 'createUrl' => Gate::allows('create', Product::class) ? route('admin.categories.products.create', $category) : null,
+ ]);
+ }
+
+ /**
+ * Show the form for a new product.
+ */
+ public function create(Category $category): Response
+ {
+ Gate::authorize('create', Product::class);
+
+ return Inertia::render('admin/products/Create', [
+ 'category' => $category,
+ 'form' => ProductForm::make()->model(Product::class)->action(route('admin.categories.products.store', $category)),
+ ]);
+ }
+
+ /**
+ * Save a new product.
+ */
+ public function store(Category $category): RedirectResponse
+ {
+ Gate::authorize('create', Product::class);
+
+ $product = ProductForm::make()->model((new Product)->category()->associate($category))->save();
+
+ return redirect(route('admin.categories.products.show', [$category, $product]))
+ ->with('status', __('Product created.'));
+ }
+
+ /**
+ * Show a product.
+ */
+ public function show(Category $category, Product $product): Response
+ {
+ Gate::authorize('view', $product);
+
+ $product->load(['tags']);
+
+ return Inertia::render('admin/products/Show', [
+ 'category' => $category,
+ 'product' => ProductResource::make($product)->resolve(),
+ ]);
+ }
+
+ /**
+ * Show the form for editing a product.
+ */
+ public function edit(Category $category, Product $product): Response
+ {
+ Gate::authorize('update', $product);
+
+ return Inertia::render('admin/products/Edit', [
+ 'category' => $category,
+ 'product' => ProductResource::make($product)->resolve(),
+ 'form' => ProductForm::make()->model($product)->action(route('admin.categories.products.update', [$category, $product])),
+ ]);
+ }
+
+ /**
+ * Save the changes to a product.
+ */
+ public function update(Category $category, Product $product): RedirectResponse
+ {
+ Gate::authorize('update', $product);
+
+ ProductForm::make()->model($product)->save();
+
+ return redirect(route('admin.categories.products.show', [$category, $product]))
+ ->with('status', __('Product updated.'));
+ }
+
+ /**
+ * Delete a product.
+ */
+ public function destroy(Category $category, Product $product): RedirectResponse
+ {
+ Gate::authorize('delete', $product);
+
+ $product->delete();
+
+ return redirect(route('admin.categories.products.index', $category))
+ ->with('status', __('Product deleted.'));
+ }
+
+ /**
+ * Restore a deleted product.
+ */
+ public function restore(Category $category, Product $product): RedirectResponse
+ {
+ Gate::authorize('restore', $product);
+
+ $product->restore();
+
+ return redirect(route('admin.categories.products.show', [$category, $product]))
+ ->with('status', __('Product restored.'));
+ }
+
+ /**
+ * Permanently delete a product.
+ */
+ public function forceDelete(Category $category, Product $product): RedirectResponse
+ {
+ Gate::authorize('forceDelete', $product);
+
+ $product->forceDelete();
+
+ return redirect(route('admin.categories.products.index', $category))
+ ->with('status', __('Product permanently deleted.'));
+ }
+}
+
+// app/Http/Resources/Admin/ProductResource.php
+
+ */
+ public function toArray(Request $request): array
+ {
+ return [
+ 'id' => $this->id,
+ 'name' => $this->name,
+ 'address' => $this->address,
+ 'slug' => $this->slug,
+ 'description' => $this->description,
+ 'price' => $this->price,
+ 'stock' => $this->stock,
+ 'on_sale' => $this->on_sale,
+ 'features' => $this->features,
+ 'published_at' => $this->published_at?->format('Y-m-d H:i'),
+ 'expire_at' => $this->expire_at?->format('Y-m-d H:i'),
+ 'released_on' => $this->released_on?->format('Y-m-d'),
+ 'sale_time' => $this->sale_time,
+ 'status' => $this->status,
+ 'category_id' => $this->category_id,
+ 'category' => $this->whenLoaded('category', fn () => [
+ 'id' => $this->category->id,
+ 'name' => $this->category->name,
+ ]),
+ 'manufactured_year' => $this->manufactured_year,
+ 'tags' => $this->whenLoaded('tags', fn () => $this->tags->map(fn (Tag $tag) => [
+ 'id' => $tag->getKey(),
+ 'name' => $tag->name,
+ ])->all()),
+ 'created_at' => $this->created_at?->format('Y-m-d H:i'),
+ 'updated_at' => $this->updated_at?->format('Y-m-d H:i'),
+ 'deleted_at' => $this->deleted_at?->format('Y-m-d H:i'),
+ 'urls' => [
+ 'show' => route('admin.categories.products.show', [$this->category_id, $this->resource]),
+ 'edit' => route('admin.categories.products.edit', [$this->category_id, $this->resource]),
+ 'destroy' => route('admin.categories.products.destroy', [$this->category_id, $this->resource]),
+ 'restore' => route('admin.categories.products.restore', [$this->category_id, $this->resource]),
+ 'force_delete' => route('admin.categories.products.force-delete', [$this->category_id, $this->resource]),
+ ],
+ 'can' => [
+ 'view' => (bool) $request->user()?->can('view', $this->resource),
+ 'update' => (bool) $request->user()?->can('update', $this->resource),
+ 'delete' => (bool) $request->user()?->can('delete', $this->resource),
+ 'restore' => (bool) $request->user()?->can('restore', $this->resource),
+ 'forceDelete' => (bool) $request->user()?->can('forceDelete', $this->resource),
+ ],
+ ];
+ }
+}
+
+// resources/js/pages/admin/products/types.ts
+/** A product, as ProductResource sends it. */
+export interface Product {
+ id: number
+ name: string
+ address: string
+ slug: string
+ description: string | null
+ price: string
+ stock: number
+ on_sale: boolean
+ features: string
+ published_at: string
+ expire_at: string
+ released_on: string
+ sale_time: string
+ status: "draft" | "published"
+ category_id: number | null
+ category?: { id: number; name: string } | null
+ manufactured_year: number
+ tags?: { id: number; name: string }[]
+ created_at: string | null
+ updated_at: string | null
+ deleted_at: string | null
+ urls: { show: string; edit: string; destroy: string; restore: string; force_delete: string }
+ can: { view: boolean; update: boolean; delete: boolean; restore: boolean; forceDelete: boolean }
+}
+
+/** The category the products belong to. */
+export interface Category {
+ id: number
+ name: string
+}
+
+// resources/js/pages/admin/products/Index.tsx
+import { Head, Link, router } from "@inertiajs/react"
+import { BulkActionsTable, type BulkActionsSchema } from "@javaabu/bulk-actions"
+import { FilterBar, SortableHeading, type FiltersData } from "@javaabu/filters"
+import { type LaravelResourceCollection, LinkButton, Pagination, TableCell, TableEmptyRow, TableHeading, TableRow } from "@javaabu/forms"
+import { __ } from "@javaabu/i18n"
+import type { Product, Category } from "./types"
+
+interface Props {
+ category: Category
+ products: Omit & { data: Product[] }
+ filters: FiltersData
+ bulkActions: BulkActionsSchema
+ createUrl: string | null
+}
+
+export default function Index({ category, products, filters, bulkActions, createUrl }: Props) {
+ return (
+ <>
+
+
+
+
+
+
{__("Products")}
+
+
+ {createUrl && (
+
+ {__("New product")}
+
+ )}
+
+
+
+
+ router.reload()}
+ headers={
+ <>
+
+
+ >
+ }
+ pagination={products.meta.last_page > 1 ? : null}
+ >
+ {products.data.length > 0 ? (
+ products.data.map((product) => (
+
+
+ {product.can.view ? (
+
+ {product.name}
+
+ ) : (
+ product.name
+ )}
+
+
+
+ ))
+ ) : (
+
+ {filters.active > 0
+ ? __("No products match these filters.")
+ : __("No products yet.")}
+
+ )}
+
+ >
+ )
+}
+
+// resources/js/pages/admin/products/Create.tsx
+import { Head } from "@inertiajs/react"
+import { __ } from "@javaabu/i18n"
+import { StencilForm, type StencilSchema } from "@javaabu/stencil"
+import type { Category } from "./types"
+
+interface Props {
+ category: Category
+ form: StencilSchema
+}
+
+export default function Create({ category, form }: Props) {
+ return (
+ <>
+
+
+
+
+
+
{__("New product")}
+
+
+
+
+ >
+ )
+}
+
+// resources/js/pages/admin/products/Edit.tsx
+import { Head } from "@inertiajs/react"
+import { LinkButton } from "@javaabu/forms"
+import { __ } from "@javaabu/i18n"
+import { StencilForm, type StencilSchema } from "@javaabu/stencil"
+import type { Product, Category } from "./types"
+
+interface Props {
+ category: Category
+ product: Product
+ form: StencilSchema
+}
+
+export default function Edit({ category, product, form }: Props) {
+ return (
+ <>
+
+
+
+
+
+
{__("Edit product")}
+
+
+
+ {__("View")}
+
+
+
+
+ >
+ )
+}
+
+// resources/js/pages/admin/products/Show.tsx
+import { Head, router } from "@inertiajs/react"
+import { Alert, BooleanEntry, Button, Infolist, LinkButton, TextEntry } from "@javaabu/forms"
+import { __ } from "@javaabu/i18n"
+import type { Product, Category } from "./types"
+
+interface Props {
+ category: Category
+ product: Product
+}
+
+export default function Show({ category, product }: Props) {
+ return (
+ <>
+
+
+
+
+
+
{product.name}
+
+
+
+ {product.deleted_at ? (
+ <>
+ {product.can.restore && (
+
+ )}
+ {product.can.forceDelete && (
+
+ )}
+ >
+ ) : (
+ <>
+ {product.can.update && (
+
+ {__("Edit")}
+
+ )}
+ {product.can.delete && (
+
+ )}
+ >
+ )}
+
+
+
+ {product.deleted_at && (
+
+ {__("This product is deleted.")}
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ related.name).join(", ")} />
+
+ >
+ )
+}
+
+// tests/Feature/Admin/ProductsControllerTest.php
+artisan('permissions:sync');
+ }
+
+ #[Test]
+ public function it_lists_the_products(): void
+ {
+ $category = Category::factory()->create();
+ $product = Product::factory()->for($category, 'category')->create();
+
+ $this->actingAsAdmin('view_products');
+
+ $this->get(route('admin.categories.products.index', $category))
+ ->assertOk()
+ ->assertInertia(fn (AssertableInertia $page) => $page
+ ->component('admin/products/Index')
+ ->has('products.data', 1)
+ );
+ }
+
+ #[Test]
+ public function it_does_not_list_the_products_without_permission(): void
+ {
+ $category = Category::factory()->create();
+ $this->actingAsAdmin();
+
+ $this->get(route('admin.categories.products.index', $category))
+ ->assertForbidden();
+ }
+
+ #[Test]
+ public function it_searches_the_products(): void
+ {
+ $category = Category::factory()->create();
+ $product = Product::factory()->for($category, 'category')->create(['name' => 'ރަސްމީ ދުވަސް']);
+ $other = Product::factory()->for($category, 'category')->create(['name' => 'Something else']);
+
+ $this->actingAsAdmin('view_products');
+
+ $this->get(route('admin.categories.products.index', [$category, 'search' => 'ރަސްމީ']))
+ ->assertOk()
+ ->assertInertia(fn (AssertableInertia $page) => $page
+ ->component('admin/products/Index')
+ ->has('products.data', 1)
+ ->where('products.data.0.id', $product->id)
+ );
+ }
+
+ #[Test]
+ public function it_shows_the_new_product_form(): void
+ {
+ $category = Category::factory()->create();
+ $this->actingAsAdmin('edit_products');
+
+ $this->get(route('admin.categories.products.create', $category))
+ ->assertOk()
+ ->assertInertia(fn (AssertableInertia $page) => $page
+ ->component('admin/products/Create')
+ ->has('form')
+ );
+ }
+
+ #[Test]
+ public function it_creates_a_product(): void
+ {
+ $category = Category::factory()->create();
+ $this->actingAsAdmin('edit_products');
+
+ $this->post(route('admin.categories.products.store', $category), [
+ 'name' => 'ރަސްމީ',
+ 'price' => 10.5,
+ ])
+ ->assertRedirect()
+ ->assertSessionHasNoErrors();
+
+ $this->assertDatabaseHas('products', [
+ 'category_id' => $category->id,
+ 'name' => 'ރަސްމީ',
+ 'price' => 10.5,
+ ]);
+ }
+
+ #[Test]
+ public function it_validates_new_products(): void
+ {
+ $category = Category::factory()->create();
+ $this->actingAsAdmin('edit_products');
+
+ $this->post(route('admin.categories.products.store', $category), [
+ 'name' => [],
+ 'price' => 'ބަދަލު',
+ ])
+ ->assertSessionHasErrors(['name', 'price']);
+
+ $this->assertDatabaseCount('products', 0);
+ }
+
+ #[Test]
+ public function it_does_not_create_products_without_permission(): void
+ {
+ $category = Category::factory()->create();
+ $this->actingAsAdmin('view_products');
+
+ $this->post(route('admin.categories.products.store', $category), [])
+ ->assertForbidden();
+
+ $this->assertDatabaseCount('products', 0);
+ }
+
+ #[Test]
+ public function it_shows_a_product(): void
+ {
+ $category = Category::factory()->create();
+ $product = Product::factory()->for($category, 'category')->create();
+
+ $this->actingAsAdmin('view_products');
+
+ $this->get(route('admin.categories.products.show', [$category, $product]))
+ ->assertOk()
+ ->assertInertia(fn (AssertableInertia $page) => $page
+ ->component('admin/products/Show')
+ ->where('product.id', $product->id)
+ );
+ }
+
+ #[Test]
+ public function it_shows_the_edit_product_form(): void
+ {
+ $category = Category::factory()->create();
+ $product = Product::factory()->for($category, 'category')->create();
+
+ $this->actingAsAdmin('edit_products');
+
+ $this->get(route('admin.categories.products.edit', [$category, $product]))
+ ->assertOk()
+ ->assertInertia(fn (AssertableInertia $page) => $page
+ ->component('admin/products/Edit')
+ ->has('form')
+ );
+ }
+
+ #[Test]
+ public function it_updates_a_product(): void
+ {
+ $category = Category::factory()->create();
+ $product = Product::factory()->for($category, 'category')->create();
+
+ $this->actingAsAdmin('edit_products');
+
+ $this->patch(route('admin.categories.products.update', [$category, $product]), [
+ 'name' => 'ދިވެހި',
+ 'price' => 20.25,
+ ])
+ ->assertRedirect()
+ ->assertSessionHasNoErrors();
+
+ $this->assertDatabaseHas('products', [
+ 'id' => $product->id,
+ 'name' => 'ދިވެހި',
+ 'price' => 20.25,
+ ]);
+ }
+
+ #[Test]
+ public function it_does_not_update_products_without_permission(): void
+ {
+ $category = Category::factory()->create();
+ $product = Product::factory()->for($category, 'category')->create();
+
+ $this->actingAsAdmin('view_products');
+
+ $this->patch(route('admin.categories.products.update', [$category, $product]), [])
+ ->assertForbidden();
+ }
+
+ #[Test]
+ public function it_deletes_a_product(): void
+ {
+ $category = Category::factory()->create();
+ $product = Product::factory()->for($category, 'category')->create();
+
+ $this->actingAsAdmin('delete_products');
+
+ $this->delete(route('admin.categories.products.destroy', [$category, $product]))
+ ->assertRedirect(route('admin.categories.products.index', $category));
+
+ $this->assertSoftDeleted($product);
+ }
+
+ #[Test]
+ public function it_restores_a_deleted_product(): void
+ {
+ $category = Category::factory()->create();
+ $product = Product::factory()->for($category, 'category')->create();
+ $product->delete();
+
+ $this->actingAsAdmin('delete_products');
+
+ $this->patch(route('admin.categories.products.restore', [$category, $product]))
+ ->assertRedirect();
+
+ $this->assertNotSoftDeleted($product);
+ }
+
+ #[Test]
+ public function it_permanently_deletes_a_product(): void
+ {
+ $category = Category::factory()->create();
+ $product = Product::factory()->for($category, 'category')->create();
+ $product->delete();
+
+ $this->actingAsAdmin('force_delete_products');
+
+ $this->delete(route('admin.categories.products.force-delete', [$category, $product]))
+ ->assertRedirect(route('admin.categories.products.index', $category));
+
+ $this->assertModelMissing($product);
+ }
+
+ #[Test]
+ public function it_deletes_products_in_bulk(): void
+ {
+ $category = Category::factory()->create();
+ $products = Product::factory()->for($category, 'category')->count(2)->create();
+
+ $this->actingAsAdmin('view_products', 'delete_products');
+
+ $this->postJson(route('admin.categories.products.bulk', $category), [
+ 'action' => 'delete',
+ 'products' => $products->pluck('id')->all(),
+ ])
+ ->assertOk()
+ ->assertJson(['succeeded' => 2]);
+
+ $products->each(fn (Product $record) => $this->assertSoftDeleted($record));
+ }
+
+ /**
+ * Log in as a user of the admin area with these permissions.
+ */
+ protected function actingAsAdmin(string ...$permissions): User
+ {
+ $user = User::factory()->create();
+
+ if ($permissions) {
+ $user->givePermissionTo($permissions);
+ }
+
+ $this->actingAs($user);
+
+ return $user;
+ }
+}
diff --git a/tests/snapshots/products.blade.txt b/tests/snapshots/products.blade.txt
new file mode 100644
index 0000000..ce5570a
--- /dev/null
+++ b/tests/snapshots/products.blade.txt
@@ -0,0 +1,1017 @@
+// app/Models/Product.php
+ */
+ use HasFactory;
+
+ use SoftDeletes;
+
+ /**
+ * The attributes that are mass assignable.
+ *
+ * @var list
+ */
+ protected $fillable = [
+ 'name',
+ 'address',
+ 'slug',
+ 'description',
+ 'price',
+ 'stock',
+ 'on_sale',
+ 'features',
+ 'published_at',
+ 'expire_at',
+ 'released_on',
+ 'sale_time',
+ 'status',
+ 'category_id',
+ 'manufactured_year',
+ ];
+
+ /**
+ * Get the attributes that should be cast.
+ *
+ * @return array
+ */
+ protected function casts(): array
+ {
+ return [
+ 'price' => 'decimal:2',
+ 'stock' => 'integer',
+ 'on_sale' => 'boolean',
+ 'published_at' => 'datetime',
+ 'expire_at' => 'datetime',
+ 'released_on' => 'date',
+ 'manufactured_year' => 'integer',
+ ];
+ }
+
+ /**
+ * @return BelongsTo
+ */
+ public function category(): BelongsTo
+ {
+ return $this->belongsTo(Category::class);
+ }
+
+ /**
+ * @return HasMany
+ */
+ public function allForeigns(): HasMany
+ {
+ return $this->hasMany(AllForeign::class, 'product_slug', 'slug');
+ }
+
+ /**
+ * @return HasMany
+ */
+ public function orders(): HasMany
+ {
+ return $this->hasMany(Order::class, 'product_slug', 'slug');
+ }
+
+ /**
+ * @return HasMany
+ */
+ public function singleValueEnumOrders(): HasMany
+ {
+ return $this->hasMany(SingleValueEnumOrder::class, 'product_slug', 'slug');
+ }
+
+ /**
+ * @return BelongsToMany
+ */
+ public function tags(): BelongsToMany
+ {
+ return $this->belongsToMany(Tag::class);
+ }
+}
+
+// database/factories/ProductFactory.php
+
+ */
+class ProductFactory extends Factory
+{
+ /**
+ * The name of the factory's corresponding model.
+ *
+ * @var class-string
+ */
+ protected $model = Product::class;
+
+ /**
+ * Define the model's default state.
+ *
+ * @return array
+ */
+ public function definition(): array
+ {
+ return [
+ 'name' => fake()->text(50),
+ 'address' => fake()->text(50),
+ 'slug' => fake()->unique()->text(50),
+ 'description' => fake()->optional()->paragraph(),
+ 'price' => fake()->randomFloat(2, 0, 10000),
+ 'stock' => fake()->numberBetween(0, 10000),
+ 'on_sale' => fake()->boolean(),
+ 'features' => fake()->paragraph(),
+ 'published_at' => fake()->dateTimeBetween('-1 year', '+1 year'),
+ 'expire_at' => fake()->dateTimeBetween('-1 year', '+1 year'),
+ 'released_on' => fake()->date(),
+ 'sale_time' => fake()->time('H:i'),
+ 'status' => fake()->randomElement(['draft', 'published']),
+ 'category_id' => fn () => fake()->boolean() ? Category::factory() : null,
+ 'manufactured_year' => fake()->numberBetween(0, 10000),
+ ];
+ }
+}
+
+// app/Policies/ProductPolicy.php
+label(__('Search'))->columns(['name', 'address', 'slug', 'description', 'features']),
+ NumberRangeFilter::make('price')->label(__('Price')),
+ NumberRangeFilter::make('stock')->label(__('Stock'))->integer(),
+ TernaryFilter::make('on_sale')->label(__('On sale')),
+ SelectFilter::make('status')->label(__('Status'))->options(['draft' => __('Draft'), 'published' => __('Published')]),
+ SelectFilter::make('category_id')->label(__('Category'))->relationship('category', 'name')->searchable(),
+ SelectFilter::make('tags')->label(__('Tags'))->relationship('tags', 'name')->multiple(),
+ DateRangeFilter::make('created_at')->label(__('Created at')),
+ TrashedFilter::make(),
+ ];
+ }
+
+ protected function sorts(): array
+ {
+ return [
+ Sort::make('name')->label(__('Name')),
+ Sort::make('address')->label(__('Address')),
+ Sort::make('slug')->label(__('Slug')),
+ Sort::make('price')->label(__('Price')),
+ Sort::make('stock')->label(__('Stock')),
+ Sort::make('created_at')->label(__('Created at')),
+ Sort::make('updated_at')->label(__('Updated at')),
+ ];
+ }
+}
+
+// app/BulkActions/ProductBulkActions.php
+filters(ProductFilters::class);
+ }
+
+ protected function actions(): array
+ {
+ return [
+ DeleteAction::make()->can('delete_products'),
+ RestoreAction::make()->can('delete_products'),
+ ForceDeleteAction::make()->can('force_delete_products'),
+ ];
+ }
+}
+
+// app/Stencil/ProductForm.php
+columns(2)
+ ->schema([
+ TextInput::make('name')
+ ->label(__('Name'))
+ ->required()
+ ->maxLength(255),
+ TextInput::make('address')
+ ->label(__('Address'))
+ ->required()
+ ->maxLength(255),
+ TextInput::make('slug')
+ ->label(__('Slug'))
+ ->required()
+ ->maxLength(255)
+ ->unique(),
+ Textarea::make('description')
+ ->label(__('Description'))
+ ->rows(4)
+ ->maxLength(65535)
+ ->columnSpanFull(),
+ TextInput::make('price')
+ ->label(__('Price'))
+ ->required()
+ ->numeric()
+ ->decimals(2)
+ ->minValue(-99999999)
+ ->maxValue(99999999),
+ TextInput::make('stock')
+ ->label(__('Stock'))
+ ->required()
+ ->integer()
+ ->minValue(-2147483648)
+ ->maxValue(2147483647),
+ Checkbox::make('on_sale')->label(__('On sale')),
+ Textarea::make('features')
+ ->label(__('Features'))
+ ->required()
+ ->rows(4)
+ ->maxLength(65535)
+ ->columnSpanFull(),
+ DateTimePicker::make('published_at')->label(__('Published at'))->required(),
+ DateTimePicker::make('expire_at')->label(__('Expire at'))->required(),
+ DatePicker::make('released_on')->label(__('Released on'))->required(),
+ TimePicker::make('sale_time')->label(__('Sale time'))->required(),
+ Select::make('status')
+ ->label(__('Status'))
+ ->required()
+ ->options(['draft' => __('Draft'), 'published' => __('Published')]),
+ Select::make('category_id')
+ ->label(__('Category'))
+ ->relationship('category', 'name')
+ ->searchable(),
+ TextInput::make('manufactured_year')
+ ->label(__('Manufactured year'))
+ ->required()
+ ->integer()
+ ->minValue(-2147483648)
+ ->maxValue(2147483647),
+ Select::make('tags')
+ ->label(__('Tags'))
+ ->multiple()
+ ->relationship('tags', 'name')
+ ->searchable(),
+ ]),
+ ];
+ }
+}
+
+// app/Http/Controllers/Admin/ProductsController.php
+apply(Product::query())
+ ->paginate($request->integer('per_page', 20))
+ ->withQueryString();
+
+ return view('admin.products.index', [
+ 'products' => $products,
+ 'filters' => $filters,
+ 'bulk_actions' => ProductBulkActions::make()->paginator($products),
+ ]);
+ }
+
+ /**
+ * Show the form for a new product.
+ */
+ public function create(): View
+ {
+ Gate::authorize('create', Product::class);
+
+ return view('admin.products.create', [
+ 'form' => ProductForm::make()->model(Product::class)->action(route('admin.products.store')),
+ ]);
+ }
+
+ /**
+ * Save a new product.
+ */
+ public function store(): RedirectResponse
+ {
+ Gate::authorize('create', Product::class);
+
+ $product = ProductForm::make()->model(Product::class)->save();
+
+ return redirect(route('admin.products.show', $product))
+ ->with('status', __('Product created.'));
+ }
+
+ /**
+ * Show a product.
+ */
+ public function show(Product $product): View
+ {
+ Gate::authorize('view', $product);
+
+ $product->load(['category', 'tags']);
+
+ return view('admin.products.show', [
+ 'product' => $product,
+ ]);
+ }
+
+ /**
+ * Show the form for editing a product.
+ */
+ public function edit(Product $product): View
+ {
+ Gate::authorize('update', $product);
+
+ return view('admin.products.edit', [
+ 'product' => $product,
+ 'form' => ProductForm::make()->model($product)->action(route('admin.products.update', $product)),
+ ]);
+ }
+
+ /**
+ * Save the changes to a product.
+ */
+ public function update(Product $product): RedirectResponse
+ {
+ Gate::authorize('update', $product);
+
+ ProductForm::make()->model($product)->save();
+
+ return redirect(route('admin.products.show', $product))
+ ->with('status', __('Product updated.'));
+ }
+
+ /**
+ * Delete a product.
+ */
+ public function destroy(Product $product): RedirectResponse
+ {
+ Gate::authorize('delete', $product);
+
+ $product->delete();
+
+ return redirect(route('admin.products.index'))
+ ->with('status', __('Product deleted.'));
+ }
+
+ /**
+ * Restore a deleted product.
+ */
+ public function restore(Product $product): RedirectResponse
+ {
+ Gate::authorize('restore', $product);
+
+ $product->restore();
+
+ return redirect(route('admin.products.show', $product))
+ ->with('status', __('Product restored.'));
+ }
+
+ /**
+ * Permanently delete a product.
+ */
+ public function forceDelete(Product $product): RedirectResponse
+ {
+ Gate::authorize('forceDelete', $product);
+
+ $product->forceDelete();
+
+ return redirect(route('admin.products.index'))
+ ->with('status', __('Product permanently deleted.'));
+ }
+}
+
+// routes/admin.php
+// Products
+Route::resource('products', ProductsController::class)->withTrashed(['show']);
+Route::patch('products/{product}/restore', [ProductsController::class, 'restore'])->withTrashed()->name('products.restore');
+Route::delete('products/{product}/force-delete', [ProductsController::class, 'forceDelete'])->withTrashed()->name('products.force-delete');
+Route::bulkActions('products', ProductBulkActions::class);
+// routes/web.php
+require __DIR__ . '/admin.php';
+// tests/Feature/Admin/ProductsControllerTest.php
+artisan('permissions:sync');
+ }
+
+ #[Test]
+ public function it_lists_the_products(): void
+ {
+ $product = Product::factory()->create();
+
+ $this->actingAsAdmin('view_products');
+
+ $this->get(route('admin.products.index'))
+ ->assertOk()
+ ->assertSee($product->name);
+ }
+
+ #[Test]
+ public function it_does_not_list_the_products_without_permission(): void
+ {
+ $this->actingAsAdmin();
+
+ $this->get(route('admin.products.index'))
+ ->assertForbidden();
+ }
+
+ #[Test]
+ public function it_searches_the_products(): void
+ {
+ $product = Product::factory()->create(['name' => 'ރަސްމީ ދުވަސް']);
+ $other = Product::factory()->create(['name' => 'Something else']);
+
+ $this->actingAsAdmin('view_products');
+
+ $this->get(route('admin.products.index', ['search' => 'ރަސްމީ']))
+ ->assertOk()
+ ->assertSee('ރަސްމީ ދުވަސް')
+ ->assertDontSee('Something else');
+ }
+
+ #[Test]
+ public function it_shows_the_new_product_form(): void
+ {
+ $this->actingAsAdmin('edit_products');
+
+ $this->get(route('admin.products.create'))
+ ->assertOk()
+ ->assertSee(__('New product'));
+ }
+
+ #[Test]
+ public function it_creates_a_product(): void
+ {
+ $category = Category::factory()->create();
+ $other_category = Category::factory()->create();
+ $tags = Tag::factory()->count(2)->create();
+
+ $this->actingAsAdmin('edit_products');
+
+ $this->post(route('admin.products.store'), [
+ 'name' => 'ރަސްމީ',
+ 'address' => 'ރަސްމީ',
+ 'slug' => 'ރަސްމީ',
+ 'description' => 'ދިވެހިރާއްޖޭގެ ޖުމްހޫރިއްޔާ',
+ 'price' => 10.5,
+ 'stock' => 10,
+ 'on_sale' => true,
+ 'features' => 'ދިވެހިރާއްޖޭގެ ޖުމްހޫރިއްޔާ',
+ 'published_at' => '2024-02-12 14:54',
+ 'expire_at' => '2024-02-12 14:54',
+ 'released_on' => '2024-02-12',
+ 'sale_time' => '14:54',
+ 'status' => 'draft',
+ 'category_id' => $category->id,
+ 'manufactured_year' => 10,
+ 'tags' => $tags->pluck('id')->all(),
+ ])
+ ->assertRedirect()
+ ->assertSessionHasNoErrors();
+
+ $this->assertDatabaseHas('products', [
+ 'name' => 'ރަސްމީ',
+ 'address' => 'ރަސްމީ',
+ 'slug' => 'ރަސްމީ',
+ 'description' => 'ދިވެހިރާއްޖޭގެ ޖުމްހޫރިއްޔާ',
+ 'price' => 10.5,
+ 'stock' => 10,
+ 'on_sale' => true,
+ 'features' => 'ދިވެހިރާއްޖޭގެ ޖުމްހޫރިއްޔާ',
+ 'status' => 'draft',
+ 'category_id' => $category->id,
+ 'manufactured_year' => 10,
+ ]);
+
+ $product = Product::query()->latest('id')->first();
+
+ $this->assertEquals('2024-02-12 14:54', $product->published_at?->format('Y-m-d H:i'));
+ $this->assertEquals('2024-02-12 14:54', $product->expire_at?->format('Y-m-d H:i'));
+ $this->assertEquals('2024-02-12', $product->released_on?->format('Y-m-d'));
+ $this->assertEquals('14:54', substr((string) $product->sale_time, 0, 5));
+ $this->assertEquals($tags->pluck('id')->all(), $product->tags()->pluck('tags.id')->sort()->values()->all());
+ }
+
+ #[Test]
+ public function it_validates_new_products(): void
+ {
+ $this->actingAsAdmin('edit_products');
+
+ $this->post(route('admin.products.store'), [
+ 'name' => [],
+ 'address' => [],
+ 'slug' => [],
+ 'description' => [],
+ 'price' => 'ބަދަލު',
+ 'stock' => 'ބަދަލު',
+ 'features' => [],
+ 'published_at' => 'ބަދަލު',
+ 'expire_at' => 'ބަދަލު',
+ 'released_on' => 'ބަދަލު',
+ 'sale_time' => 'ބަދަލު',
+ 'status' => 'ބަދަލު',
+ 'category_id' => -1,
+ 'manufactured_year' => 'ބަދަލު',
+ ])
+ ->assertSessionHasErrors(['name', 'address', 'slug', 'description', 'price', 'stock', 'features', 'published_at', 'expire_at', 'released_on', 'sale_time', 'status', 'category_id', 'manufactured_year']);
+
+ $this->assertDatabaseCount('products', 0);
+ }
+
+ #[Test]
+ public function it_does_not_create_products_without_permission(): void
+ {
+ $this->actingAsAdmin('view_products');
+
+ $this->post(route('admin.products.store'), [])
+ ->assertForbidden();
+
+ $this->assertDatabaseCount('products', 0);
+ }
+
+ #[Test]
+ public function it_shows_a_product(): void
+ {
+ $product = Product::factory()->create();
+
+ $this->actingAsAdmin('view_products');
+
+ $this->get(route('admin.products.show', $product))
+ ->assertOk()
+ ->assertSee($product->name);
+ }
+
+ #[Test]
+ public function it_shows_the_edit_product_form(): void
+ {
+ $product = Product::factory()->create();
+
+ $this->actingAsAdmin('edit_products');
+
+ $this->get(route('admin.products.edit', $product))
+ ->assertOk()
+ ->assertSee(__('Edit product'));
+ }
+
+ #[Test]
+ public function it_updates_a_product(): void
+ {
+ $category = Category::factory()->create();
+ $other_category = Category::factory()->create();
+ $tags = Tag::factory()->count(2)->create();
+
+ $product = Product::factory()->create();
+
+ $this->actingAsAdmin('edit_products');
+
+ $this->patch(route('admin.products.update', $product), [
+ 'name' => 'ދިވެހި',
+ 'address' => 'ދިވެހި',
+ 'slug' => 'ދިވެހި',
+ 'description' => 'Republic of Maldives',
+ 'price' => 20.25,
+ 'stock' => 20,
+ 'on_sale' => false,
+ 'features' => 'Republic of Maldives',
+ 'published_at' => '2025-11-06 09:30',
+ 'expire_at' => '2025-11-06 09:30',
+ 'released_on' => '2025-11-06',
+ 'sale_time' => '09:30',
+ 'status' => 'published',
+ 'category_id' => $other_category->id,
+ 'manufactured_year' => 20,
+ 'tags' => [$tags->first()->id],
+ ])
+ ->assertRedirect()
+ ->assertSessionHasNoErrors();
+
+ $this->assertDatabaseHas('products', [
+ 'id' => $product->id,
+ 'name' => 'ދިވެހި',
+ 'address' => 'ދިވެހި',
+ 'slug' => 'ދިވެހި',
+ 'description' => 'Republic of Maldives',
+ 'price' => 20.25,
+ 'stock' => 20,
+ 'on_sale' => false,
+ 'features' => 'Republic of Maldives',
+ 'status' => 'published',
+ 'category_id' => $other_category->id,
+ 'manufactured_year' => 20,
+ ]);
+
+ $product->refresh();
+
+ $this->assertEquals('2025-11-06 09:30', $product->published_at?->format('Y-m-d H:i'));
+ $this->assertEquals('2025-11-06 09:30', $product->expire_at?->format('Y-m-d H:i'));
+ $this->assertEquals('2025-11-06', $product->released_on?->format('Y-m-d'));
+ $this->assertEquals('09:30', substr((string) $product->sale_time, 0, 5));
+ $this->assertEquals([$tags->first()->id], $product->tags()->pluck('tags.id')->sort()->values()->all());
+ }
+
+ #[Test]
+ public function it_does_not_update_products_without_permission(): void
+ {
+ $product = Product::factory()->create();
+
+ $this->actingAsAdmin('view_products');
+
+ $this->patch(route('admin.products.update', $product), [])
+ ->assertForbidden();
+ }
+
+ #[Test]
+ public function it_deletes_a_product(): void
+ {
+ $product = Product::factory()->create();
+
+ $this->actingAsAdmin('delete_products');
+
+ $this->delete(route('admin.products.destroy', $product))
+ ->assertRedirect(route('admin.products.index'));
+
+ $this->assertSoftDeleted($product);
+ }
+
+ #[Test]
+ public function it_restores_a_deleted_product(): void
+ {
+ $product = Product::factory()->create();
+ $product->delete();
+
+ $this->actingAsAdmin('delete_products');
+
+ $this->patch(route('admin.products.restore', $product))
+ ->assertRedirect();
+
+ $this->assertNotSoftDeleted($product);
+ }
+
+ #[Test]
+ public function it_permanently_deletes_a_product(): void
+ {
+ $product = Product::factory()->create();
+ $product->delete();
+
+ $this->actingAsAdmin('force_delete_products');
+
+ $this->delete(route('admin.products.force-delete', $product))
+ ->assertRedirect(route('admin.products.index'));
+
+ $this->assertModelMissing($product);
+ }
+
+ #[Test]
+ public function it_deletes_products_in_bulk(): void
+ {
+ $products = Product::factory()->count(2)->create();
+
+ $this->actingAsAdmin('view_products', 'delete_products');
+
+ $this->postJson(route('admin.products.bulk'), [
+ 'action' => 'delete',
+ 'products' => $products->pluck('id')->all(),
+ ])
+ ->assertOk()
+ ->assertJson(['succeeded' => 2]);
+
+ $products->each(fn (Product $record) => $this->assertSoftDeleted($record));
+ }
+
+ /**
+ * Log in as a user of the admin area with these permissions.
+ */
+ protected function actingAsAdmin(string ...$permissions): User
+ {
+ $user = User::factory()->create();
+
+ if ($permissions) {
+ $user->givePermissionTo($permissions);
+ }
+
+ $this->actingAs($user);
+
+ return $user;
+ }
+}
+
+// resources/views/admin/products/index.blade.php
+@extends('layouts.admin')
+
+@section('title', __('Products'))
+
+@section('content')
+
+
+
{{ __('Products') }}
+
+
+ @can('create', \App\Models\Product::class)
+
+ {{ __('New product') }}
+
+ @endcan
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @forelse($products as $product)
+
+
+ @can('view', $product)
+ {{ $product->name }}
+ @else
+ {{ $product->name }}
+ @endcan
+
+
+
+
+
+
+ @empty
+
+ {{ $filters->hasActiveFilters() ? __('No products match these filters.') : __('No products yet.') }}
+
+ @endforelse
+
+
+ @if($products->hasPages())
+
+
+
+ @endif
+
+@endsection
+
+// resources/views/admin/products/create.blade.php
+@extends('layouts.admin')
+
+@section('title', __('New product'))
+
+@section('content')
+
+
+ {{ $form }}
+@endsection
+
+// resources/views/admin/products/edit.blade.php
+@extends('layouts.admin')
+
+@section('title', __('Edit product'))
+
+@section('content')
+
+
+
+
+ {{ __('View') }}
+
+
+
+ {{ $form }}
+@endsection
+
+// resources/views/admin/products/show.blade.php
+@extends('layouts.admin')
+
+@section('title', $product->name)
+
+@section('content')
+
+
+
+
+ @if($product->trashed())
+ @can('restore', $product)
+
+ @endcan
+
+ @can('forceDelete', $product)
+
+ @endcan
+ @else
+ @can('update', $product)
+
+ {{ __('Edit') }}
+
+ @endcan
+
+ @can('delete', $product)
+
+ @endcan
+ @endif
+
+
+
+ @if($product->trashed())
+
+ {{ __('This product is deleted.') }}
+
+ @endif
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+@endsection
+
+// app/Exports/ProductsExport.php
+apply(Product::query())
+ ->paginate($request->integer('per_page', 20))
+ ->withQueryString();
+
+ return Inertia::render('admin/products/Index', [
+ 'products' => ProductResource::collection($products),
+ 'filters' => $filters,
+ 'bulkActions' => ProductBulkActions::make()->paginator($products),
+ 'createUrl' => Gate::allows('create', Product::class) ? route('admin.products.create') : null,
+ ]);
+ }
+
+ /**
+ * Show the form for a new product.
+ */
+ public function create(): Response
+ {
+ Gate::authorize('create', Product::class);
+
+ return Inertia::render('admin/products/Create', [
+ 'form' => ProductForm::make()->model(Product::class)->action(route('admin.products.store')),
+ ]);
+ }
+
+ /**
+ * Save a new product.
+ */
+ public function store(): RedirectResponse
+ {
+ Gate::authorize('create', Product::class);
+
+ $product = ProductForm::make()->model(Product::class)->save();
+
+ return redirect(route('admin.products.show', $product))
+ ->with('status', __('Product created.'));
+ }
+
+ /**
+ * Show a product.
+ */
+ public function show(Product $product): Response
+ {
+ Gate::authorize('view', $product);
+
+ $product->load(['category', 'tags']);
+
+ return Inertia::render('admin/products/Show', [
+ 'product' => ProductResource::make($product)->resolve(),
+ ]);
+ }
+
+ /**
+ * Show the form for editing a product.
+ */
+ public function edit(Product $product): Response
+ {
+ Gate::authorize('update', $product);
+
+ return Inertia::render('admin/products/Edit', [
+ 'product' => ProductResource::make($product)->resolve(),
+ 'form' => ProductForm::make()->model($product)->action(route('admin.products.update', $product)),
+ ]);
+ }
+
+ /**
+ * Save the changes to a product.
+ */
+ public function update(Product $product): RedirectResponse
+ {
+ Gate::authorize('update', $product);
+
+ ProductForm::make()->model($product)->save();
+
+ return redirect(route('admin.products.show', $product))
+ ->with('status', __('Product updated.'));
+ }
+
+ /**
+ * Delete a product.
+ */
+ public function destroy(Product $product): RedirectResponse
+ {
+ Gate::authorize('delete', $product);
+
+ $product->delete();
+
+ return redirect(route('admin.products.index'))
+ ->with('status', __('Product deleted.'));
+ }
+
+ /**
+ * Restore a deleted product.
+ */
+ public function restore(Product $product): RedirectResponse
+ {
+ Gate::authorize('restore', $product);
+
+ $product->restore();
+
+ return redirect(route('admin.products.show', $product))
+ ->with('status', __('Product restored.'));
+ }
+
+ /**
+ * Permanently delete a product.
+ */
+ public function forceDelete(Product $product): RedirectResponse
+ {
+ Gate::authorize('forceDelete', $product);
+
+ $product->forceDelete();
+
+ return redirect(route('admin.products.index'))
+ ->with('status', __('Product permanently deleted.'));
+ }
+}
+
+// app/Http/Resources/Admin/ProductResource.php
+
+ */
+ public function toArray(Request $request): array
+ {
+ return [
+ 'id' => $this->id,
+ 'name' => $this->name,
+ 'address' => $this->address,
+ 'slug' => $this->slug,
+ 'description' => $this->description,
+ 'price' => $this->price,
+ 'stock' => $this->stock,
+ 'on_sale' => $this->on_sale,
+ 'features' => $this->features,
+ 'published_at' => $this->published_at?->format('Y-m-d H:i'),
+ 'expire_at' => $this->expire_at?->format('Y-m-d H:i'),
+ 'released_on' => $this->released_on?->format('Y-m-d'),
+ 'sale_time' => $this->sale_time,
+ 'status' => $this->status,
+ 'category_id' => $this->category_id,
+ 'category' => $this->whenLoaded('category', fn () => [
+ 'id' => $this->category->id,
+ 'name' => $this->category->name,
+ ]),
+ 'manufactured_year' => $this->manufactured_year,
+ 'tags' => $this->whenLoaded('tags', fn () => $this->tags->map(fn (Tag $tag) => [
+ 'id' => $tag->getKey(),
+ 'name' => $tag->name,
+ ])->all()),
+ 'created_at' => $this->created_at?->format('Y-m-d H:i'),
+ 'updated_at' => $this->updated_at?->format('Y-m-d H:i'),
+ 'deleted_at' => $this->deleted_at?->format('Y-m-d H:i'),
+ 'urls' => [
+ 'show' => route('admin.products.show', $this->resource),
+ 'edit' => route('admin.products.edit', $this->resource),
+ 'destroy' => route('admin.products.destroy', $this->resource),
+ 'restore' => route('admin.products.restore', $this->resource),
+ 'force_delete' => route('admin.products.force-delete', $this->resource),
+ ],
+ 'can' => [
+ 'view' => (bool) $request->user()?->can('view', $this->resource),
+ 'update' => (bool) $request->user()?->can('update', $this->resource),
+ 'delete' => (bool) $request->user()?->can('delete', $this->resource),
+ 'restore' => (bool) $request->user()?->can('restore', $this->resource),
+ 'forceDelete' => (bool) $request->user()?->can('forceDelete', $this->resource),
+ ],
+ ];
+ }
+}
+
+// resources/js/pages/admin/products/types.ts
+/** A product, as ProductResource sends it. */
+export interface Product {
+ id: number
+ name: string
+ address: string
+ slug: string
+ description: string | null
+ price: string
+ stock: number
+ on_sale: boolean
+ features: string
+ published_at: string
+ expire_at: string
+ released_on: string
+ sale_time: string
+ status: "draft" | "published"
+ category_id: number | null
+ category?: { id: number; name: string } | null
+ manufactured_year: number
+ tags?: { id: number; name: string }[]
+ created_at: string | null
+ updated_at: string | null
+ deleted_at: string | null
+ urls: { show: string; edit: string; destroy: string; restore: string; force_delete: string }
+ can: { view: boolean; update: boolean; delete: boolean; restore: boolean; forceDelete: boolean }
+}
+
+// resources/js/pages/admin/products/Index.tsx
+import { Head, Link, router } from "@inertiajs/react"
+import { BulkActionsTable, type BulkActionsSchema } from "@javaabu/bulk-actions"
+import { FilterBar, SortableHeading, type FiltersData } from "@javaabu/filters"
+import { type LaravelResourceCollection, LinkButton, Pagination, TableCell, TableEmptyRow, TableHeading, TableRow } from "@javaabu/forms"
+import { __ } from "@javaabu/i18n"
+import type { Product } from "./types"
+
+interface Props {
+ products: Omit & { data: Product[] }
+ filters: FiltersData
+ bulkActions: BulkActionsSchema
+ createUrl: string | null
+}
+
+export default function Index({ products, filters, bulkActions, createUrl }: Props) {
+ return (
+ <>
+
+
+
+
+
{__("Products")}
+
+
+ {createUrl && (
+
+ {__("New product")}
+
+ )}
+
+
+
+
+ router.reload()}
+ headers={
+ <>
+
+
+
+
+
+ >
+ }
+ pagination={products.meta.last_page > 1 ? : null}
+ >
+ {products.data.length > 0 ? (
+ products.data.map((product) => (
+
+
+ {product.can.view ? (
+
+ {product.name}
+
+ ) : (
+ product.name
+ )}
+
+
+
+
+
+
+ ))
+ ) : (
+
+ {filters.active > 0
+ ? __("No products match these filters.")
+ : __("No products yet.")}
+
+ )}
+
+ >
+ )
+}
+
+// resources/js/pages/admin/products/Create.tsx
+import { Head } from "@inertiajs/react"
+import { __ } from "@javaabu/i18n"
+import { StencilForm, type StencilSchema } from "@javaabu/stencil"
+
+interface Props {
+ form: StencilSchema
+}
+
+export default function Create({ form }: Props) {
+ return (
+ <>
+
+
+
+
+
{__("New product")}
+
+
+
+
+ >
+ )
+}
+
+// resources/js/pages/admin/products/Edit.tsx
+import { Head } from "@inertiajs/react"
+import { LinkButton } from "@javaabu/forms"
+import { __ } from "@javaabu/i18n"
+import { StencilForm, type StencilSchema } from "@javaabu/stencil"
+import type { Product } from "./types"
+
+interface Props {
+ product: Product
+ form: StencilSchema
+}
+
+export default function Edit({ product, form }: Props) {
+ return (
+ <>
+
+
+
+
+
{__("Edit product")}
+
+
+
+ {__("View")}
+
+
+
+
+ >
+ )
+}
+
+// resources/js/pages/admin/products/Show.tsx
+import { Head, router } from "@inertiajs/react"
+import { Alert, BooleanEntry, Button, Infolist, LinkButton, TextEntry } from "@javaabu/forms"
+import { __ } from "@javaabu/i18n"
+import type { Product } from "./types"
+
+interface Props {
+ product: Product
+}
+
+export default function Show({ product }: Props) {
+ return (
+ <>
+
+
+
+
+
{product.name}
+
+
+
+ {product.deleted_at ? (
+ <>
+ {product.can.restore && (
+
+ )}
+ {product.can.forceDelete && (
+
+ )}
+ >
+ ) : (
+ <>
+ {product.can.update && (
+
+ {__("Edit")}
+
+ )}
+ {product.can.delete && (
+
+ )}
+ >
+ )}
+
+
+
+ {product.deleted_at && (
+
+ {__("This product is deleted.")}
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ related.name).join(", ")} />
+
+ >
+ )
+}
+
+// tests/Feature/Admin/ProductsControllerTest.php
+artisan('permissions:sync');
+ }
+
+ #[Test]
+ public function it_lists_the_products(): void
+ {
+ $product = Product::factory()->create();
+
+ $this->actingAsAdmin('view_products');
+
+ $this->get(route('admin.products.index'))
+ ->assertOk()
+ ->assertInertia(fn (AssertableInertia $page) => $page
+ ->component('admin/products/Index')
+ ->has('products.data', 1)
+ );
+ }
+
+ #[Test]
+ public function it_does_not_list_the_products_without_permission(): void
+ {
+ $this->actingAsAdmin();
+
+ $this->get(route('admin.products.index'))
+ ->assertForbidden();
+ }
+
+ #[Test]
+ public function it_searches_the_products(): void
+ {
+ $product = Product::factory()->create(['name' => 'ރަސްމީ ދުވަސް']);
+ $other = Product::factory()->create(['name' => 'Something else']);
+
+ $this->actingAsAdmin('view_products');
+
+ $this->get(route('admin.products.index', ['search' => 'ރަސްމީ']))
+ ->assertOk()
+ ->assertInertia(fn (AssertableInertia $page) => $page
+ ->component('admin/products/Index')
+ ->has('products.data', 1)
+ ->where('products.data.0.id', $product->id)
+ );
+ }
+
+ #[Test]
+ public function it_shows_the_new_product_form(): void
+ {
+ $this->actingAsAdmin('edit_products');
+
+ $this->get(route('admin.products.create'))
+ ->assertOk()
+ ->assertInertia(fn (AssertableInertia $page) => $page
+ ->component('admin/products/Create')
+ ->has('form')
+ );
+ }
+
+ #[Test]
+ public function it_creates_a_product(): void
+ {
+ $category = Category::factory()->create();
+ $other_category = Category::factory()->create();
+ $tags = Tag::factory()->count(2)->create();
+
+ $this->actingAsAdmin('edit_products');
+
+ $this->post(route('admin.products.store'), [
+ 'name' => 'ރަސްމީ',
+ 'address' => 'ރަސްމީ',
+ 'slug' => 'ރަސްމީ',
+ 'description' => 'ދިވެހިރާއްޖޭގެ ޖުމްހޫރިއްޔާ',
+ 'price' => 10.5,
+ 'stock' => 10,
+ 'on_sale' => true,
+ 'features' => 'ދިވެހިރާއްޖޭގެ ޖުމްހޫރިއްޔާ',
+ 'published_at' => '2024-02-12 14:54',
+ 'expire_at' => '2024-02-12 14:54',
+ 'released_on' => '2024-02-12',
+ 'sale_time' => '14:54',
+ 'status' => 'draft',
+ 'category_id' => $category->id,
+ 'manufactured_year' => 10,
+ 'tags' => $tags->pluck('id')->all(),
+ ])
+ ->assertRedirect()
+ ->assertSessionHasNoErrors();
+
+ $this->assertDatabaseHas('products', [
+ 'name' => 'ރަސްމީ',
+ 'address' => 'ރަސްމީ',
+ 'slug' => 'ރަސްމީ',
+ 'description' => 'ދިވެހިރާއްޖޭގެ ޖުމްހޫރިއްޔާ',
+ 'price' => 10.5,
+ 'stock' => 10,
+ 'on_sale' => true,
+ 'features' => 'ދިވެހިރާއްޖޭގެ ޖުމްހޫރިއްޔާ',
+ 'status' => 'draft',
+ 'category_id' => $category->id,
+ 'manufactured_year' => 10,
+ ]);
+
+ $product = Product::query()->latest('id')->first();
+
+ $this->assertEquals('2024-02-12 14:54', $product->published_at?->format('Y-m-d H:i'));
+ $this->assertEquals('2024-02-12 14:54', $product->expire_at?->format('Y-m-d H:i'));
+ $this->assertEquals('2024-02-12', $product->released_on?->format('Y-m-d'));
+ $this->assertEquals('14:54', substr((string) $product->sale_time, 0, 5));
+ $this->assertEquals($tags->pluck('id')->all(), $product->tags()->pluck('tags.id')->sort()->values()->all());
+ }
+
+ #[Test]
+ public function it_validates_new_products(): void
+ {
+ $this->actingAsAdmin('edit_products');
+
+ $this->post(route('admin.products.store'), [
+ 'name' => [],
+ 'address' => [],
+ 'slug' => [],
+ 'description' => [],
+ 'price' => 'ބަދަލު',
+ 'stock' => 'ބަދަލު',
+ 'features' => [],
+ 'published_at' => 'ބަދަލު',
+ 'expire_at' => 'ބަދަލު',
+ 'released_on' => 'ބަދަލު',
+ 'sale_time' => 'ބަދަލު',
+ 'status' => 'ބަދަލު',
+ 'category_id' => -1,
+ 'manufactured_year' => 'ބަދަލު',
+ ])
+ ->assertSessionHasErrors(['name', 'address', 'slug', 'description', 'price', 'stock', 'features', 'published_at', 'expire_at', 'released_on', 'sale_time', 'status', 'category_id', 'manufactured_year']);
+
+ $this->assertDatabaseCount('products', 0);
+ }
+
+ #[Test]
+ public function it_does_not_create_products_without_permission(): void
+ {
+ $this->actingAsAdmin('view_products');
+
+ $this->post(route('admin.products.store'), [])
+ ->assertForbidden();
+
+ $this->assertDatabaseCount('products', 0);
+ }
+
+ #[Test]
+ public function it_shows_a_product(): void
+ {
+ $product = Product::factory()->create();
+
+ $this->actingAsAdmin('view_products');
+
+ $this->get(route('admin.products.show', $product))
+ ->assertOk()
+ ->assertInertia(fn (AssertableInertia $page) => $page
+ ->component('admin/products/Show')
+ ->where('product.id', $product->id)
+ );
+ }
+
+ #[Test]
+ public function it_shows_the_edit_product_form(): void
+ {
+ $product = Product::factory()->create();
+
+ $this->actingAsAdmin('edit_products');
+
+ $this->get(route('admin.products.edit', $product))
+ ->assertOk()
+ ->assertInertia(fn (AssertableInertia $page) => $page
+ ->component('admin/products/Edit')
+ ->has('form')
+ );
+ }
+
+ #[Test]
+ public function it_updates_a_product(): void
+ {
+ $category = Category::factory()->create();
+ $other_category = Category::factory()->create();
+ $tags = Tag::factory()->count(2)->create();
+
+ $product = Product::factory()->create();
+
+ $this->actingAsAdmin('edit_products');
+
+ $this->patch(route('admin.products.update', $product), [
+ 'name' => 'ދިވެހި',
+ 'address' => 'ދިވެހި',
+ 'slug' => 'ދިވެހި',
+ 'description' => 'Republic of Maldives',
+ 'price' => 20.25,
+ 'stock' => 20,
+ 'on_sale' => false,
+ 'features' => 'Republic of Maldives',
+ 'published_at' => '2025-11-06 09:30',
+ 'expire_at' => '2025-11-06 09:30',
+ 'released_on' => '2025-11-06',
+ 'sale_time' => '09:30',
+ 'status' => 'published',
+ 'category_id' => $other_category->id,
+ 'manufactured_year' => 20,
+ 'tags' => [$tags->first()->id],
+ ])
+ ->assertRedirect()
+ ->assertSessionHasNoErrors();
+
+ $this->assertDatabaseHas('products', [
+ 'id' => $product->id,
+ 'name' => 'ދިވެހި',
+ 'address' => 'ދިވެހި',
+ 'slug' => 'ދިވެހި',
+ 'description' => 'Republic of Maldives',
+ 'price' => 20.25,
+ 'stock' => 20,
+ 'on_sale' => false,
+ 'features' => 'Republic of Maldives',
+ 'status' => 'published',
+ 'category_id' => $other_category->id,
+ 'manufactured_year' => 20,
+ ]);
+
+ $product->refresh();
+
+ $this->assertEquals('2025-11-06 09:30', $product->published_at?->format('Y-m-d H:i'));
+ $this->assertEquals('2025-11-06 09:30', $product->expire_at?->format('Y-m-d H:i'));
+ $this->assertEquals('2025-11-06', $product->released_on?->format('Y-m-d'));
+ $this->assertEquals('09:30', substr((string) $product->sale_time, 0, 5));
+ $this->assertEquals([$tags->first()->id], $product->tags()->pluck('tags.id')->sort()->values()->all());
+ }
+
+ #[Test]
+ public function it_does_not_update_products_without_permission(): void
+ {
+ $product = Product::factory()->create();
+
+ $this->actingAsAdmin('view_products');
+
+ $this->patch(route('admin.products.update', $product), [])
+ ->assertForbidden();
+ }
+
+ #[Test]
+ public function it_deletes_a_product(): void
+ {
+ $product = Product::factory()->create();
+
+ $this->actingAsAdmin('delete_products');
+
+ $this->delete(route('admin.products.destroy', $product))
+ ->assertRedirect(route('admin.products.index'));
+
+ $this->assertSoftDeleted($product);
+ }
+
+ #[Test]
+ public function it_restores_a_deleted_product(): void
+ {
+ $product = Product::factory()->create();
+ $product->delete();
+
+ $this->actingAsAdmin('delete_products');
+
+ $this->patch(route('admin.products.restore', $product))
+ ->assertRedirect();
+
+ $this->assertNotSoftDeleted($product);
+ }
+
+ #[Test]
+ public function it_permanently_deletes_a_product(): void
+ {
+ $product = Product::factory()->create();
+ $product->delete();
+
+ $this->actingAsAdmin('force_delete_products');
+
+ $this->delete(route('admin.products.force-delete', $product))
+ ->assertRedirect(route('admin.products.index'));
+
+ $this->assertModelMissing($product);
+ }
+
+ #[Test]
+ public function it_deletes_products_in_bulk(): void
+ {
+ $products = Product::factory()->count(2)->create();
+
+ $this->actingAsAdmin('view_products', 'delete_products');
+
+ $this->postJson(route('admin.products.bulk'), [
+ 'action' => 'delete',
+ 'products' => $products->pluck('id')->all(),
+ ])
+ ->assertOk()
+ ->assertJson(['succeeded' => 2]);
+
+ $products->each(fn (Product $record) => $this->assertSoftDeleted($record));
+ }
+
+ /**
+ * Log in as a user of the admin area with these permissions.
+ */
+ protected function actingAsAdmin(string ...$permissions): User
+ {
+ $user = User::factory()->create();
+
+ if ($permissions) {
+ $user->givePermissionTo($permissions);
+ }
+
+ $this->actingAs($user);
+
+ return $user;
+ }
+}
diff --git a/tests/snapshots/tags.a-pivot-on-the-other-side.txt b/tests/snapshots/tags.a-pivot-on-the-other-side.txt
new file mode 100644
index 0000000..739885d
--- /dev/null
+++ b/tests/snapshots/tags.a-pivot-on-the-other-side.txt
@@ -0,0 +1,64 @@
+// app/Models/Tag.php
+ */
+ use HasFactory;
+
+ /**
+ * The attributes that are mass assignable.
+ *
+ * @var list
+ */
+ protected $fillable = [
+ 'name',
+ ];
+
+ /**
+ * @return BelongsToMany
+ */
+ public function products(): BelongsToMany
+ {
+ return $this->belongsToMany(Product::class);
+ }
+}
+
+// app/Stencil/TagForm.php
+columns(2)
+ ->schema([
+ TextInput::make('name')
+ ->label(__('Name'))
+ ->required()
+ ->maxLength(255),
+ Select::make('products')
+ ->label(__('Products'))
+ ->multiple()
+ ->relationship('products', 'name')
+ ->searchable(),
+ ]),
+ ];
+ }
+}
diff --git a/tests/stubs/AfterAppend.php b/tests/stubs/AfterAppend.php
deleted file mode 100644
index 6952b62..0000000
--- a/tests/stubs/AfterAppend.php
+++ /dev/null
@@ -1,7 +0,0 @@
-getHidden());
- }
-
- /**
- * Get the allowed includes
- *
- * @return array
- */
- public function getAllowedIncludes(): array
- {
- return [
- ];
- }
-
- /**
- * Get the allowed appends
- *
- * @return array
- */
- public function getAllowedAppends(): array
- {
- return [
- ];
- }
-
- /**
- * Get the allowed sorts
- *
- * @return array
- */
- public function getAllowedSorts(): array
- {
- return [
- 'id',
- 'created_at',
- 'updated_at',
- 'name',
- 'slug',
- ];
- }
-
- /**
- * Get the default sort
- *
- * @return string
- */
- public function getDefaultSort(): string
- {
- return 'created_at';
- }
-
- /**
- * Get the allowed filters
- *
- * @return array
- */
- public function getAllowedFilters(): array
- {
- return [
- 'id',
- 'name',
- 'slug',
- AllowedFilter::scope('search'),
- ];
- }
-}
diff --git a/tests/stubs/Controllers/Api/ProductsController.php b/tests/stubs/Controllers/Api/ProductsController.php
deleted file mode 100644
index c4ba4e0..0000000
--- a/tests/stubs/Controllers/Api/ProductsController.php
+++ /dev/null
@@ -1,104 +0,0 @@
-getHidden());
- }
-
- /**
- * Get the allowed includes
- *
- * @return array
- */
- public function getAllowedIncludes(): array
- {
- return [
- 'category',
- ];
- }
-
- /**
- * Get the allowed appends
- *
- * @return array
- */
- public function getAllowedAppends(): array
- {
- return [
- ];
- }
-
- /**
- * Get the allowed sorts
- *
- * @return array
- */
- public function getAllowedSorts(): array
- {
- return [
- 'id',
- 'created_at',
- 'updated_at',
- 'name',
- 'address',
- 'slug',
- 'price',
- 'stock',
- 'published_at',
- 'expire_at',
- 'released_on',
- 'sale_time',
- 'manufactured_year',
- ];
- }
-
- /**
- * Get the default sort
- *
- * @return string
- */
- public function getDefaultSort(): string
- {
- return 'created_at';
- }
-
- /**
- * Get the allowed filters
- *
- * @return array
- */
- public function getAllowedFilters(): array
- {
- return [
- 'id',
- 'name',
- 'address',
- 'slug',
- AllowedFilter::scope('search'),
- ];
- }
-}
diff --git a/tests/stubs/Controllers/CategoriesController.php b/tests/stubs/Controllers/CategoriesController.php
deleted file mode 100644
index 3bcc38f..0000000
--- a/tests/stubs/Controllers/CategoriesController.php
+++ /dev/null
@@ -1,169 +0,0 @@
-authorizeResource(Category::class);
- }
-
- /**
- * Initialize orderbys
- */
- protected static function initOrderbys()
- {
- static::$orderbys = [
- 'id' => __('Id'),
- 'created_at' => __('Created At'),
- 'updated_at' => __('Updated At'),
- 'name' => __('Name'),
- 'slug' => __('Slug'),
- ];
- }
-
- /**
- * Display a listing of the resource.
- */
- public function index(Request $request)
- {
- $title = __('All Categories');
- $orderby = $this->getOrderBy($request, 'created_at');
- $order = $this->getOrder($request, 'created_at', $orderby);
- $per_page = $this->getPerPage($request);
-
- $categories = Category::orderBy($orderby, $order);
-
- $search = null;
- if ($search = $request->input('search')) {
- $categories->search($search);
- $title = __('Categories matching \':search\'', ['search' => $search]);
- }
-
- if ($date_field = $request->input('date_field')) {
- $categories->dateBetween($date_field, $request->input('date_from'), $request->input('date_to'));
- }
-
- if ($request->download) {
- return (new CategoriesExport($categories))->download('categories.xlsx');
- }
-
- $categories = $categories->paginate($per_page)
- ->appends($request->except('page'));
-
- return view('admin.categories.index', compact('categories', 'title', 'per_page', 'search'));
- }
-
- /**
- * Show the form for creating a new resource.
- */
- public function create(Request $request)
- {
- return view('admin.categories.create');
- }
-
- /**
- * Store a newly created resource in storage.
- */
- public function store(CategoriesRequest $request)
- {
- $category = new Category($request->validated());
-
- $category->save();
-
- $this->flashSuccessMessage();
-
- return redirect()->action([CategoriesController::class, 'edit'], $category);
- }
-
- /**
- * Display the specified resource.
- */
- public function show(Category $category)
- {
- return view('admin.categories.show', compact('category'));
- }
-
- /**
- * Show the form for editing the specified resource.
- */
- public function edit(Category $category)
- {
- return view('admin.categories.edit', compact('category'));
- }
-
- /**
- * Update the specified resource in storage.
- */
- public function update(CategoriesRequest $request, Category $category)
- {
- $category->fill($request->validated());
-
- $category->save();
-
- $this->flashSuccessMessage();
-
- return redirect()->action([CategoriesController::class, 'edit'], $category);
- }
-
- /**
- * Remove the specified resource from storage.
- */
- public function destroy(Category $category, Request $request)
- {
- $category->delete();
-
- if ($request->expectsJson()) {
- return response()->json(true);
- }
-
- return redirect()->action([CategoriesController::class, 'index']);
- }
-
- /**
- * Perform bulk action on the resource
- */
- public function bulk(Request $request)
- {
- $this->authorize('viewAny', Category::class);
-
- $this->validate($request, [
- 'action' => 'required|in:delete',
- 'categories' => 'required|array',
- 'categories.*' => 'exists:categories,id',
- ]);
-
- $action = $request->input('action');
- $ids = $request->input('categories', []);
-
- switch ($action) {
- case 'delete':
- // make sure allowed to delete
- $this->authorize('delete_categories');
-
- Category::whereIn('id', $ids)
- ->get()
- ->each(function (Category $category) {
- $category->delete();
- });
- break;
- }
-
- $this->flashSuccessMessage();
-
- return $this->redirect($request, action([CategoriesController::class, 'index']));
- }
-}
diff --git a/tests/stubs/Controllers/Customer/Auth/ConfirmPasswordController.php b/tests/stubs/Controllers/Customer/Auth/ConfirmPasswordController.php
deleted file mode 100644
index d961270..0000000
--- a/tests/stubs/Controllers/Customer/Auth/ConfirmPasswordController.php
+++ /dev/null
@@ -1,20 +0,0 @@
-middleware('guest:web_customer');
- }
-
- #[\Override]
- public function getBroker(): PasswordBroker
- {
- return Password::broker('customers');
- }
-
- #[\Override]
- public function getPasswordResetForm(): View
- {
- return view('customer.auth.passwords.email');
- }
-}
diff --git a/tests/stubs/Controllers/Customer/Auth/LoginController.php b/tests/stubs/Controllers/Customer/Auth/LoginController.php
deleted file mode 100644
index 0c4faf7..0000000
--- a/tests/stubs/Controllers/Customer/Auth/LoginController.php
+++ /dev/null
@@ -1,19 +0,0 @@
- ['required', 'string', 'max:255'],
- 'email' => ['required', 'string', 'email', 'max:255', Rule::unique('customers', 'email')],
- 'password' => ['required', 'string', Password::min(8), 'confirmed'],
- 'designation' => ['required', Rule::unique('customers', 'designation'), 'string', 'max:255'],
- 'address' => ['required', 'string', 'max:255'],
- 'expire_at' => ['required', 'date'],
- 'agreement' => ['accepted'],
- ];
-
- if (! app()->runningUnitTests()) {
- $rules[recaptchaFieldName()] = recaptchaRuleName();
- }
-
- return Validator::make($data, $rules);
- }
-
- /**
- * Create a new user instance after a valid registration.
- */
- public function create(array $data)
- {
- $class = $this->userClass();
-
- $customer = new $class();
-
- $customer->name = $data['name'];
- $customer->email = $data['email'];
- $customer->password = $data['password'];
- $customer->status = UserStatuses::APPROVED;
- $customer->designation = $data['designation'];
- $customer->address = $data['address'];
- $customer->expire_at = $data['expire_at'];
-
- $customer->save();
-
- return $customer;
- }
-}
diff --git a/tests/stubs/Controllers/Customer/Auth/ResetPasswordController.php b/tests/stubs/Controllers/Customer/Auth/ResetPasswordController.php
deleted file mode 100644
index 8075b54..0000000
--- a/tests/stubs/Controllers/Customer/Auth/ResetPasswordController.php
+++ /dev/null
@@ -1,25 +0,0 @@
-user();
-
- return view('customer.account.edit', compact('customer'));
- }
-
- /**
- * Update the customer account
- */
- public function update(CustomersRequest $request)
- {
- /** @var Customer $customer */
- $customer = $request->user();
-
- if ($action = $request->input('action')) {
- switch ($action) {
- case 'update_password':
- // update password
- if ($password = $request->input('password')) {
- $customer->password = $password;
- $customer->save();
-
- flash_push('alerts', [
- 'text' => __('Password changed successfully'),
- 'type' => 'success',
- 'title' => __('Success!'),
- ]);
- }
- break;
- }
- } else {
- // update the email
- if (($new_email = $request->email) && $new_email != $customer->email) {
- $new_email = $customer->requestEmailUpdate($new_email);
- if ($new_email) {
- flash_push('alerts', [
- 'text' => __('We\'ve sent a verification link to :new_email. Please verify the new email address to update it', compact('new_email')),
- 'type' => 'success',
- 'title' => __('New Email Verification Sent'),
- ]);
- }
- }
-
- $customer->fill($request->validated());
-
- if ($request->hasAny(['on_sale', 'sync_on_sale'])) {
- $customer->on_sale = $request->input('on_sale', false);
- }
-
- if ($request->has('category')) {
- $customer->category()->associate($request->input('category'));
- }
-
- $customer->save();
-
- // update avatar
- $customer->updateSingleMedia('avatar', $request);
-
- $this->flashSuccessMessage();
- }
-
- return $this->redirect($request, route('customer.account'));
- }
-}
diff --git a/tests/stubs/Controllers/Customer/HomeController.php b/tests/stubs/Controllers/Customer/HomeController.php
deleted file mode 100644
index d64aa4f..0000000
--- a/tests/stubs/Controllers/Customer/HomeController.php
+++ /dev/null
@@ -1,21 +0,0 @@
-user();
-
- return view('customer.home.show', compact('customer'));
- }
-}
diff --git a/tests/stubs/Controllers/CustomersController.php b/tests/stubs/Controllers/CustomersController.php
deleted file mode 100644
index 9389c8e..0000000
--- a/tests/stubs/Controllers/CustomersController.php
+++ /dev/null
@@ -1,358 +0,0 @@
-authorizeResource(Customer::class);
- }
-
- /**
- * Initialize orderbys
- */
- protected static function initOrderbys()
- {
- static::$orderbys = [
- 'id' => __('Id'),
- 'name' => __('Name'),
- 'email' => __('Email'),
- 'created_at' => __('Created At'),
- 'updated_at' => __('Updated At'),
- 'designation' => __('Designation'),
- 'address' => __('Address'),
- 'expire_at' => __('Expire At'),
- ];
- }
-
- /**
- * Display a listing of the resource.
- */
- public function index(Request $request, bool $trashed = false)
- {
- $title = $trashed ? __('All Deleted Customers') : __('All Customers');
- $orderby = $this->getOrderBy($request, 'created_at');
- $order = $this->getOrder($request, 'created_at', $orderby);
- $per_page = $this->getPerPage($request);
-
- $customers = Customer::orderBy($orderby, $order);
-
- $search = null;
- if ($search = $request->input('search')) {
- $customers->search($search);
- $title = __('Customers matching \':search\'', ['search' => $search]);
- }
-
- if ($status = $request->input('status')) {
- $customers->whereStatus($status);
- }
-
- if ($request->filled('locked_out')) {
- if ($request->input('locked_out')) {
- $customers->lockedOut();
- } else {
- $customers->notLockedOut();
- }
- }
-
- if ($date_field = $request->input('date_field')) {
- $customers->dateBetween($date_field, $request->input('date_from'), $request->input('date_to'));
- }
-
- if ($trashed) {
- $customers->onlyTrashed();
- }
-
- $customers->with('media', 'category');
-
- if ($request->download) {
- return (new CustomersExport($customers))->download('customers.xlsx');
- }
-
- $customers = $customers->paginate($per_page)
- ->appends($request->except('page'));
-
- return view('admin.customers.index', compact('customers', 'title', 'per_page', 'search', 'trashed'));
- }
-
- /**
- * Show the form for creating a new resource.
- */
- public function create(Request $request)
- {
- return view('admin.customers.create');
- }
-
- /**
- * Store a newly created resource in storage.
- */
- public function store(CustomersRequest $request)
- {
- $customer = new Customer($request->validated());
- $customer->email = $request->input('email');
- $customer->setEmailVerificationStatus($request->input('email_verified', false));
-
- if ($request->hasAny(['on_sale', 'sync_on_sale'])) {
- $customer->on_sale = $request->input('on_sale', false);
- }
-
- if ($request->has('category')) {
- $customer->category()->associate($request->input('category'));
- }
-
- $password = $request->input('password');
-
- if ($password) {
- $customer->password = $password;
- $customer->require_password_update = $request->input('require_password_update', false);
- }
-
- if ($request->user()->can('approve', Customer::class)) {
- $customer->approve();
- } else {
- $customer->markAsPending();
- }
-
- $customer->save();
-
- $customer->updateSingleMedia('avatar', $request);
-
- if ($password && $request->send_password) {
- $customer->notify(new NewPasswordNotification($password));
- }
-
- $this->flashSuccessMessage();
-
- return redirect()->action([CustomersController::class, 'edit'], $customer);
- }
-
- /**
- * Display the specified resource.
- */
- public function show(Customer $customer)
- {
- return view('admin.customers.show', compact('customer'));
- }
-
- /**
- * Show the form for editing the specified resource.
- */
- public function edit(Customer $customer)
- {
- return view('admin.customers.edit', compact('customer'));
- }
-
- /**
- * Update the specified resource in storage.
- */
- public function update(CustomersRequest $request, Customer $customer)
- {
- if ($action = $request->input('action')) {
- switch ($action) {
- case 'resend_verification':
- $this->authorize('resendVerification', $customer);
- $customer->resendEmailVerification();
-
- flash_push('alerts', [
- 'text' => __('Email verification resent successfully.'),
- 'type' => 'success',
- 'title' => __('Success!'),
- ]);
- break;
-
- case 'approve':
- $this->authorize('approve', Customer::class);
- $customer->approve();
- break;
-
- case 'ban':
- $this->authorize('approve', Customer::class);
- $customer->ban();
- break;
-
- case 'mark_pending':
- $this->authorize('approve', Customer::class);
- $customer->markAsPending();
- break;
-
- case 'update_password':
- // update password
- if ($password = $request->input('password')) {
- $customer->password = $password;
-
- if ($request->input('require_password_update')) {
- $customer->require_password_update = true;
- } else {
- $customer->require_password_update = false;
- }
-
- $customer->resetLoginAttempts();
-
- if ($password && $request->send_password) {
- $customer->notify(new NewPasswordNotification($password));
- }
-
- flash_push('alerts', [
- 'text' => __('Password changed successfully'),
- 'type' => 'success',
- 'title' => __('Success!'),
- ]);
- }
- break;
- }
- } else {
- $customer->fill($request->validated());
-
- if ($request->hasAny(['on_sale', 'sync_on_sale'])) {
- $customer->on_sale = $request->input('on_sale', false);
- }
-
- if ($request->has('category')) {
- $customer->category()->associate($request->input('category'));
- }
-
- if ($email = $request->input('email')) {
- $customer->email = $email;
- }
-
- if ($request->filled('email_verified')) {
- $customer->setEmailVerificationStatus($request->email_verified);
- }
-
- $customer->updateSingleMedia('avatar', $request);
- }
-
- $customer->save();
-
- $this->flashSuccessMessage();
-
- return redirect()->action([CustomersController::class, 'edit'], $customer);
- }
-
- /**
- * Remove the specified resource from storage.
- */
- public function destroy(Customer $customer, Request $request)
- {
- $customer->delete();
-
- if ($request->expectsJson()) {
- return response()->json(true);
- }
-
- return redirect()->action([CustomersController::class, 'index']);
- }
-
- /**
- * Display a listing of the deleted resources.
- */
- public function trash(Request $request)
- {
- $this->authorize('viewTrash', Customer::class);
-
- return $this->index($request, true);
- }
-
- /**
- * Force delete the resource
- */
- public function forceDelete(Customer $customer, Request $request)
- {
- // make sure it is trashed
- if (! $customer->trashed()) {
- abort(404);
- }
-
- $this->authorize('forceDelete', $customer);
-
- $customer->forceDelete();
-
- if ($request->expectsJson()) {
- return response()->json(true);
- }
-
- return redirect()->action([CustomersController::class, 'trash']);
- }
-
- /**
- * Restore deleted resource
- */
- public function restore(Customer $customer, Request $request)
- {
- // make sure it is trashed
- if (! $customer->trashed()) {
- abort(404);
- }
-
- $this->authorize('restore', $customer);
-
- $customer->restore();
-
- if ($request->expectsJson()) {
- return response()->json(true);
- }
-
- return redirect()->action([CustomersController::class, 'trash']);
- }
-
- /**
- * Perform bulk action on the resource
- */
- public function bulk(Request $request)
- {
- $this->authorize('viewAny', Customer::class);
-
- $this->validate($request, [
- 'action' => 'required|in:delete,approve,ban',
- 'customers' => 'required|array',
- 'customers.*' => 'exists:customers,id',
- ]);
-
- $action = $request->input('action');
- $ids = $request->input('customers', []);
-
- switch ($action) {
- case 'delete':
- // make sure allowed to delete
- $this->authorize('delete_customers');
-
- Customer::whereIn('id', $ids)
- ->get()
- ->each(function (Customer $customer) {
- $customer->delete();
- });
- break;
-
- case 'ban':
- case 'approve':
- // make sure allowed to approve
- $this->authorize('approve', Customer::class);
-
- Customer::whereIn('id', $ids)
- ->get()
- ->each(function (Customer $customer) use ($action) {
- $customer->{$action}();
- $customer->save();
- });
- break;
- }
-
- $this->flashSuccessMessage();
-
- return $this->redirect($request, action([CustomersController::class, 'index']));
- }
-}
diff --git a/tests/stubs/Controllers/OrdersController.php b/tests/stubs/Controllers/OrdersController.php
deleted file mode 100644
index 4888a23..0000000
--- a/tests/stubs/Controllers/OrdersController.php
+++ /dev/null
@@ -1,186 +0,0 @@
-authorizeResource(Order::class);
- }
-
- /**
- * Initialize orderbys
- */
- protected static function initOrderbys()
- {
- static::$orderbys = [
- 'id' => __('Id'),
- 'created_at' => __('Created At'),
- 'updated_at' => __('Updated At'),
- 'order_no' => __('Order No'),
- ];
- }
-
- /**
- * Display a listing of the resource.
- */
- public function index(Request $request)
- {
- $title = __('All Orders');
- $orderby = $this->getOrderBy($request, 'created_at');
- $order = $this->getOrder($request, 'created_at', $orderby);
- $per_page = $this->getPerPage($request);
-
- $orders = Order::orderBy($orderby, $order);
-
- $search = null;
- if ($search = $request->input('search')) {
- $orders->search($search);
- $title = __('Orders matching \':search\'', ['search' => $search]);
- }
-
- if ($date_field = $request->input('date_field')) {
- $orders->dateBetween($date_field, $request->input('date_from'), $request->input('date_to'));
- }
-
- $orders->with('category', 'productSlug');
-
- if ($request->download) {
- return (new OrdersExport($orders))->download('orders.xlsx');
- }
-
- $orders = $orders->paginate($per_page)
- ->appends($request->except('page'));
-
- return view('admin.orders.index', compact('orders', 'title', 'per_page', 'search'));
- }
-
- /**
- * Show the form for creating a new resource.
- */
- public function create(Request $request)
- {
- return view('admin.orders.create');
- }
-
- /**
- * Store a newly created resource in storage.
- */
- public function store(OrdersRequest $request)
- {
- $order = new Order($request->validated());
-
- if ($request->has('category')) {
- $order->category()->associate($request->input('category'));
- }
-
- if ($request->has('product_slug')) {
- $order->productSlug()->associate($request->input('product_slug'));
- }
-
- $order->save();
-
- $this->flashSuccessMessage();
-
- return redirect()->action([OrdersController::class, 'edit'], $order);
- }
-
- /**
- * Display the specified resource.
- */
- public function show(Order $order)
- {
- return view('admin.orders.show', compact('order'));
- }
-
- /**
- * Show the form for editing the specified resource.
- */
- public function edit(Order $order)
- {
- return view('admin.orders.edit', compact('order'));
- }
-
- /**
- * Update the specified resource in storage.
- */
- public function update(OrdersRequest $request, Order $order)
- {
- $order->fill($request->validated());
-
- if ($request->has('category')) {
- $order->category()->associate($request->input('category'));
- }
-
- if ($request->has('product_slug')) {
- $order->productSlug()->associate($request->input('product_slug'));
- }
-
- $order->save();
-
- $this->flashSuccessMessage();
-
- return redirect()->action([OrdersController::class, 'edit'], $order);
- }
-
- /**
- * Remove the specified resource from storage.
- */
- public function destroy(Order $order, Request $request)
- {
- $order->delete();
-
- if ($request->expectsJson()) {
- return response()->json(true);
- }
-
- return redirect()->action([OrdersController::class, 'index']);
- }
-
- /**
- * Perform bulk action on the resource
- */
- public function bulk(Request $request)
- {
- $this->authorize('viewAny', Order::class);
-
- $this->validate($request, [
- 'action' => 'required|in:delete',
- 'orders' => 'required|array',
- 'orders.*' => 'exists:orders,id',
- ]);
-
- $action = $request->input('action');
- $ids = $request->input('orders', []);
-
- switch ($action) {
- case 'delete':
- // make sure allowed to delete
- $this->authorize('delete_orders');
-
- Order::whereIn('id', $ids)
- ->get()
- ->each(function (Order $order) {
- $order->delete();
- });
- break;
- }
-
- $this->flashSuccessMessage();
-
- return $this->redirect($request, action([OrdersController::class, 'index']));
- }
-}
diff --git a/tests/stubs/Controllers/ProductsController.php b/tests/stubs/Controllers/ProductsController.php
deleted file mode 100644
index f9c99b3..0000000
--- a/tests/stubs/Controllers/ProductsController.php
+++ /dev/null
@@ -1,251 +0,0 @@
-authorizeResource(Product::class);
- }
-
- /**
- * Initialize orderbys
- */
- protected static function initOrderbys()
- {
- static::$orderbys = [
- 'id' => __('Id'),
- 'created_at' => __('Created At'),
- 'updated_at' => __('Updated At'),
- 'name' => __('Name'),
- 'address' => __('Address'),
- 'slug' => __('Slug'),
- 'price' => __('Price'),
- 'stock' => __('Stock'),
- 'published_at' => __('Published At'),
- 'expire_at' => __('Expire At'),
- 'released_on' => __('Released On'),
- 'sale_time' => __('Sale Time'),
- 'manufactured_year' => __('Manufactured Year'),
- ];
- }
-
- /**
- * Display a listing of the resource.
- */
- public function index(Request $request, bool $trashed = false)
- {
- $title = $trashed ? __('All Deleted Products') : __('All Products');
- $orderby = $this->getOrderBy($request, 'created_at');
- $order = $this->getOrder($request, 'created_at', $orderby);
- $per_page = $this->getPerPage($request);
-
- $products = Product::orderBy($orderby, $order);
-
- $search = null;
- if ($search = $request->input('search')) {
- $products->search($search);
- $title = __('Products matching \':search\'', ['search' => $search]);
- }
-
- if ($date_field = $request->input('date_field')) {
- $products->dateBetween($date_field, $request->input('date_from'), $request->input('date_to'));
- }
-
- if ($trashed) {
- $products->onlyTrashed();
- }
-
- $products->with('category');
-
- if ($request->download) {
- return (new ProductsExport($products))->download('products.xlsx');
- }
-
- $products = $products->paginate($per_page)
- ->appends($request->except('page'));
-
- return view('admin.products.index', compact('products', 'title', 'per_page', 'search', 'trashed'));
- }
-
- /**
- * Show the form for creating a new resource.
- */
- public function create(Request $request)
- {
- return view('admin.products.create');
- }
-
- /**
- * Store a newly created resource in storage.
- */
- public function store(ProductsRequest $request)
- {
- $product = new Product($request->validated());
-
- if ($request->hasAny(['on_sale', 'sync_on_sale'])) {
- $product->on_sale = $request->input('on_sale', false);
- }
-
- if ($request->has('category')) {
- $product->category()->associate($request->input('category'));
- }
-
- $product->save();
-
- $this->flashSuccessMessage();
-
- return redirect()->action([ProductsController::class, 'edit'], $product);
- }
-
- /**
- * Display the specified resource.
- */
- public function show(Product $product)
- {
- return view('admin.products.show', compact('product'));
- }
-
- /**
- * Show the form for editing the specified resource.
- */
- public function edit(Product $product)
- {
- return view('admin.products.edit', compact('product'));
- }
-
- /**
- * Update the specified resource in storage.
- */
- public function update(ProductsRequest $request, Product $product)
- {
- $product->fill($request->validated());
-
- if ($request->hasAny(['on_sale', 'sync_on_sale'])) {
- $product->on_sale = $request->input('on_sale', false);
- }
-
- if ($request->has('category')) {
- $product->category()->associate($request->input('category'));
- }
-
- $product->save();
-
- $this->flashSuccessMessage();
-
- return redirect()->action([ProductsController::class, 'edit'], $product);
- }
-
- /**
- * Remove the specified resource from storage.
- */
- public function destroy(Product $product, Request $request)
- {
- $product->delete();
-
- if ($request->expectsJson()) {
- return response()->json(true);
- }
-
- return redirect()->action([ProductsController::class, 'index']);
- }
-
- /**
- * Display a listing of the deleted resources.
- */
- public function trash(Request $request)
- {
- $this->authorize('viewTrash', Product::class);
-
- return $this->index($request, true);
- }
-
- /**
- * Force delete the resource
- */
- public function forceDelete(Product $product, Request $request)
- {
- // make sure it is trashed
- if (! $product->trashed()) {
- abort(404);
- }
-
- $this->authorize('forceDelete', $product);
-
- $product->forceDelete();
-
- if ($request->expectsJson()) {
- return response()->json(true);
- }
-
- return redirect()->action([ProductsController::class, 'trash']);
- }
-
- /**
- * Restore deleted resource
- */
- public function restore(Product $product, Request $request)
- {
- // make sure it is trashed
- if (! $product->trashed()) {
- abort(404);
- }
-
- $this->authorize('restore', $product);
-
- $product->restore();
-
- if ($request->expectsJson()) {
- return response()->json(true);
- }
-
- return redirect()->action([ProductsController::class, 'trash']);
- }
-
- /**
- * Perform bulk action on the resource
- */
- public function bulk(Request $request)
- {
- $this->authorize('viewAny', Product::class);
-
- $this->validate($request, [
- 'action' => 'required|in:delete',
- 'products' => 'required|array',
- 'products.*' => 'exists:products,id',
- ]);
-
- $action = $request->input('action');
- $ids = $request->input('products', []);
-
- switch ($action) {
- case 'delete':
- // make sure allowed to delete
- $this->authorize('delete_products');
-
- Product::whereIn('id', $ids)
- ->get()
- ->each(function (Product $product) {
- $product->delete();
- });
- break;
- }
-
- $this->flashSuccessMessage();
-
- return $this->redirect($request, action([ProductsController::class, 'index']));
- }
-}
diff --git a/tests/stubs/Controllers/_categoryForeignKey.stub b/tests/stubs/Controllers/_categoryForeignKey.stub
deleted file mode 100644
index d88e45f..0000000
--- a/tests/stubs/Controllers/_categoryForeignKey.stub
+++ /dev/null
@@ -1,3 +0,0 @@
- if ($request->has('category')) {
- $product->category()->associate($request->input('category'));
- }
diff --git a/tests/stubs/Controllers/_onSaleBoolean.stub b/tests/stubs/Controllers/_onSaleBoolean.stub
deleted file mode 100644
index 0425c2a..0000000
--- a/tests/stubs/Controllers/_onSaleBoolean.stub
+++ /dev/null
@@ -1,3 +0,0 @@
- if ($request->hasAny(['on_sale', 'sync_on_sale'])) {
- $product->on_sale = $request->input('on_sale', false);
- }
diff --git a/tests/stubs/Controllers/_onSaleBooleanIndented.stub b/tests/stubs/Controllers/_onSaleBooleanIndented.stub
deleted file mode 100644
index 8548b47..0000000
--- a/tests/stubs/Controllers/_onSaleBooleanIndented.stub
+++ /dev/null
@@ -1,3 +0,0 @@
- if ($request->hasAny(['on_sale', 'sync_on_sale'])) {
- $product->on_sale = $request->input('on_sale', false);
- }
diff --git a/tests/stubs/Controllers/_orderBy.stub b/tests/stubs/Controllers/_orderBy.stub
deleted file mode 100644
index 451c80f..0000000
--- a/tests/stubs/Controllers/_orderBy.stub
+++ /dev/null
@@ -1 +0,0 @@
- 'created_at' => __('Created At'),
diff --git a/tests/stubs/Controllers/_ordersEagerLoads.stub b/tests/stubs/Controllers/_ordersEagerLoads.stub
deleted file mode 100644
index 91699fb..0000000
--- a/tests/stubs/Controllers/_ordersEagerLoads.stub
+++ /dev/null
@@ -1,2 +0,0 @@
-
- $orders->with('category', 'productSlug');
diff --git a/tests/stubs/Exports/AllForeignsExport.php b/tests/stubs/Exports/AllForeignsExport.php
deleted file mode 100644
index ae7557c..0000000
--- a/tests/stubs/Exports/AllForeignsExport.php
+++ /dev/null
@@ -1,25 +0,0 @@
-icon('zmdi-view-quilt')
- ->route('admin.home'),
-
- MenuItem::make(__('Users'))
- ->controller(\App\Http\Controllers\Admin\UsersController::class)
- ->can('viewAny', \App\Models\User::class)
- ->icon('zmdi-accounts')
- ->count(\App\Models\User::userVisible()->pending(), 'approve_users'),
-
- MenuItem::make(__('Roles'))
- ->controller(\App\Http\Controllers\Admin\RolesController::class)
- ->can('viewAny', \Javaabu\Permissions\Models\Role::class)
- ->icon('zmdi-folder-person'),
-
- MenuItem::make(__('Activity Logs'))
- ->controller(\App\Http\Controllers\Admin\LogsController::class)
- ->can('viewAny', \Javaabu\Activitylog\Models\Activity::class)
- ->icon('zmdi-assignment'),
-
- MenuItem::make(__('OAuth2 Clients'))
- ->controller(\App\Http\Controllers\Admin\OAuthClientsController::class)
- ->can('viewAny', \Laravel\Passport\Client::class)
- ->icon('zmdi-key'),
-
- MenuItem::make(__('Settings'))
- ->controller(\App\Http\Controllers\Admin\SettingsController::class)
- ->permissions('edit_settings')
- ->icon('zmdi-settings'),
- ];
- }
-}
diff --git a/tests/stubs/Menus/ModelsAdminSidebar.php b/tests/stubs/Menus/ModelsAdminSidebar.php
deleted file mode 100644
index 1400eaa..0000000
--- a/tests/stubs/Menus/ModelsAdminSidebar.php
+++ /dev/null
@@ -1,57 +0,0 @@
-icon('zmdi-view-quilt')
- ->route('admin.home'),
-
- MenuItem::make(__('Products'))
- ->controller(\App\Http\Controllers\Admin\ProductsController::class)
- ->can('viewAny', \App\Models\Product::class)
- ->icon('zmdi-shopping-cart'),
-
- MenuItem::make(__('Categories'))
- ->controller(\App\Http\Controllers\Admin\CategoriesController::class)
- ->can('viewAny', \App\Models\Category::class)
- ->icon('zmdi-folder'),
-
- MenuItem::make(__('Users'))
- ->controller(\App\Http\Controllers\Admin\UsersController::class)
- ->can('viewAny', \App\Models\User::class)
- ->icon('zmdi-accounts')
- ->count(\App\Models\User::userVisible()->pending(), 'approve_users'),
-
- MenuItem::make(__('Roles'))
- ->controller(\App\Http\Controllers\Admin\RolesController::class)
- ->can('viewAny', \Javaabu\Permissions\Models\Role::class)
- ->icon('zmdi-folder-person'),
-
- MenuItem::make(__('Activity Logs'))
- ->controller(\App\Http\Controllers\Admin\LogsController::class)
- ->can('viewAny', \Javaabu\Activitylog\Models\Activity::class)
- ->icon('zmdi-assignment'),
-
- MenuItem::make(__('OAuth2 Clients'))
- ->controller(\App\Http\Controllers\Admin\OAuthClientsController::class)
- ->can('viewAny', \Laravel\Passport\Client::class)
- ->icon('zmdi-key'),
-
- MenuItem::make(__('Settings'))
- ->controller(\App\Http\Controllers\Admin\SettingsController::class)
- ->permissions('edit_settings')
- ->icon('zmdi-settings'),
- ];
- }
-}
diff --git a/tests/stubs/Menus/_categoriesSidebar.stub b/tests/stubs/Menus/_categoriesSidebar.stub
deleted file mode 100644
index 6dab88b..0000000
--- a/tests/stubs/Menus/_categoriesSidebar.stub
+++ /dev/null
@@ -1,4 +0,0 @@
- MenuItem::make(__('Categories'))
- ->controller(\App\Http\Controllers\Admin\CategoriesController::class)
- ->can('viewAny', \App\Models\Category::class)
- ->icon('zmdi-folder'),
diff --git a/tests/stubs/Models/Category.php b/tests/stubs/Models/Category.php
deleted file mode 100644
index d0ff569..0000000
--- a/tests/stubs/Models/Category.php
+++ /dev/null
@@ -1,71 +0,0 @@
-
- */
- protected function casts(): array
- {
- return [
- 'name' => 'string',
- 'slug' => 'string',
- ];
- }
-
- /**
- * Get the admin url attribute
- */
- public function getAdminUrlAttribute(): string
- {
- return route('admin.categories.show', $this);
- }
-}
diff --git a/tests/stubs/Models/Customer.php b/tests/stubs/Models/Customer.php
deleted file mode 100644
index 3bfb9ce..0000000
--- a/tests/stubs/Models/Customer.php
+++ /dev/null
@@ -1,141 +0,0 @@
-
- */
- protected function casts(): array
- {
- $casts = parent::casts();
-
- return $casts + [
- 'designation' => 'string',
- 'address' => 'string',
- 'on_sale' => 'boolean',
- 'expire_at' => 'datetime',
- ];
- }
-
- /**
- * Convert dates to Carbon
- */
- public function setExpireAtAttribute($value)
- {
- return $this->attributes['expire_at'] = $value ? Carbon::parse($value) : now();
- }
-
- /**
- * Get the admin url attribute
- */
- public function getAdminUrlAttribute(): string
- {
- return route('admin.customers.show', $this);
- }
-
- public function guardName(): string
- {
- return 'web_customer';
- }
-
- #[\Override]
- public function passwordUpdateUrl(): string
- {
- return route('customer.password.new-password');
- }
-
- #[\Override]
- public function homeUrl(): string
- {
- return route('customer.home');
- }
-
- #[\Override]
- public function loginUrl(): string
- {
- return route('customer.login');
- }
-
- #[\Override]
- public function getRouteForPasswordReset(): string
- {
- return 'customer.password.reset';
- }
-
- #[\Override]
- public function getRouteForEmailVerification(): string
- {
- return 'customer.verification.verify';
- }
-
- #[\Override]
- public function inactiveNoticeUrl(): string
- {
- return route('customer.verification.notice');
- }
-
- /**
- * User visible scope
- *
- * @param $query
- * @return mixed
- */
- public function scopeUserVisible($query)
- {
- // try customer
- $customer = auth()->user() instanceof Customer ?
- auth()->user() :
- auth()->guard('web_customer')->user();
-
- if ($customer) {
- return $query->where($this->getTable().'.id', $customer->id);
- }
-
- // then try admin
- $admin = auth()->user() instanceof User ?
- auth()->user() :
- auth()->guard('web_admin')->user();
-
- if ($admin) {
- if ($admin->can('viewAny', static::class)) {
- // can view all
- return $query;
- }
- }
-
- // can't view any
- return $query->where($this->getTable().'.id', -1);
- }
-
- /**
- * A customer belongs to a category
- */
- public function category(): BelongsTo
- {
- return $this->belongsTo(Category::class);
- }
-}
diff --git a/tests/stubs/Models/Order.php b/tests/stubs/Models/Order.php
deleted file mode 100644
index 7e75816..0000000
--- a/tests/stubs/Models/Order.php
+++ /dev/null
@@ -1,98 +0,0 @@
-
- */
- protected function casts(): array
- {
- return [
- 'order_no' => 'string',
- 'status' => OrderStatuses::class,
- ];
- }
-
- /**
- * Get the admin url attribute
- */
- public function getAdminUrlAttribute(): string
- {
- return route('admin.orders.show', $this);
- }
-
- /**
- * Get the name for the admin link
- *
- * @return string
- */
- public function getAdminLinkNameAttribute(): string
- {
- return $this->order_no;
- }
-
- /**
- * A order belongs to a category
- */
- public function category(): BelongsTo
- {
- return $this->belongsTo(Category::class);
- }
-
- /**
- * A order belongs to a product
- */
- public function productSlug(): BelongsTo
- {
- return $this->belongsTo(Product::class, 'product_slug', 'slug');
- }
-}
diff --git a/tests/stubs/Models/Product.php b/tests/stubs/Models/Product.php
deleted file mode 100644
index ca52e7a..0000000
--- a/tests/stubs/Models/Product.php
+++ /dev/null
@@ -1,139 +0,0 @@
-
- */
- protected function casts(): array
- {
- return [
- 'name' => 'string',
- 'address' => 'string',
- 'slug' => 'string',
- 'description' => 'string',
- 'price' => 'decimal:2',
- 'stock' => 'integer',
- 'on_sale' => 'boolean',
- 'features' => 'array',
- 'published_at' => 'datetime',
- 'expire_at' => 'datetime',
- 'released_on' => 'date',
- 'sale_time' => 'datetime',
- 'manufactured_year' => 'integer',
- ];
- }
-
- /**
- * Convert dates to Carbon
- */
- public function setPublishedAtAttribute($value)
- {
- return $this->attributes['published_at'] = $value ? Carbon::parse($value) : now();
- }
-
- /**
- * Convert dates to Carbon
- */
- public function setExpireAtAttribute($value)
- {
- return $this->attributes['expire_at'] = $value ? Carbon::parse($value) : now();
- }
-
- /**
- * Convert dates to Carbon
- */
- public function setReleasedOnAttribute($value)
- {
- return $this->attributes['released_on'] = $value ? Carbon::parse($value) : now();
- }
-
- /**
- * Convert dates to Carbon
- */
- public function setSaleTimeAttribute($value)
- {
- return $this->attributes['sale_time'] = $value ? Carbon::parse($value) : now();
- }
-
- /**
- * Get the admin url attribute
- */
- public function getAdminUrlAttribute(): string
- {
- return route('admin.products.show', $this);
- }
-
- /**
- * A product belongs to a category
- */
- public function category(): BelongsTo
- {
- return $this->belongsTo(Category::class);
- }
-}
diff --git a/tests/stubs/Models/PublicUser.php b/tests/stubs/Models/PublicUser.php
deleted file mode 100644
index 3fa00c0..0000000
--- a/tests/stubs/Models/PublicUser.php
+++ /dev/null
@@ -1,102 +0,0 @@
-user() instanceof PublicUser ?
- auth()->user() :
- auth()->guard('web_public_user')->user();
-
- if ($public_user) {
- return $query->where($this->getTable().'.id', $public_user->id);
- }
-
- // then try admin
- $admin = auth()->user() instanceof User ?
- auth()->user() :
- auth()->guard('web_admin')->user();
-
- if ($admin) {
- if ($admin->can('viewAny', static::class)) {
- // can view all
- return $query;
- }
- }
-
- // can't view any
- return $query->where($this->getTable().'.id', -1);
- }
-}
diff --git a/tests/stubs/Models/_categoryForeignKey.stub b/tests/stubs/Models/_categoryForeignKey.stub
deleted file mode 100644
index 7b75027..0000000
--- a/tests/stubs/Models/_categoryForeignKey.stub
+++ /dev/null
@@ -1,7 +0,0 @@
- /**
- * A product belongs to a category
- */
- public function category(): BelongsTo
- {
- return $this->belongsTo(Category::class);
- }
diff --git a/tests/stubs/Models/_dateMutator.stub b/tests/stubs/Models/_dateMutator.stub
deleted file mode 100644
index 56f934c..0000000
--- a/tests/stubs/Models/_dateMutator.stub
+++ /dev/null
@@ -1,7 +0,0 @@
- /**
- * Convert dates to Carbon
- */
- public function setPublishedAtAttribute($value)
- {
- return $this->attributes['published_at'] = $value ? Carbon::parse($value) : now();
- }
diff --git a/tests/stubs/Models/_nullableDateMutator.stub b/tests/stubs/Models/_nullableDateMutator.stub
deleted file mode 100644
index 377215b..0000000
--- a/tests/stubs/Models/_nullableDateMutator.stub
+++ /dev/null
@@ -1,7 +0,0 @@
- /**
- * Convert dates to Carbon
- */
- public function setPublishedAtAttribute($value)
- {
- return $this->attributes['published_at'] = $value ? Carbon::parse($value) : null;
- }
diff --git a/tests/stubs/MultipleNameCases.php b/tests/stubs/MultipleNameCases.php
deleted file mode 100644
index f1b8465..0000000
--- a/tests/stubs/MultipleNameCases.php
+++ /dev/null
@@ -1,19 +0,0 @@
-can('view_categories');
- }
-
- /**
- * Determine whether the user can view the model.
- */
- public function view(User $user, Category $category): bool
- {
- return $user->can('view_categories');
- }
-
- /**
- * Determine whether the user can create models.
- */
- public function create(User $user): bool
- {
- return $user->can('edit_categories');
- }
-
- /**
- * Determine whether the user can update the model.
- */
- public function update(User $user, Category $category): bool
- {
- return $user->can('edit_categories');
- }
-
- /**
- * Determine whether the user can delete the model.
- */
- public function delete(User $user, Category $category): bool
- {
- return $user->can('delete_categories');
- }
-
- /**
- * Determine whether the user can view the model logs.
- */
- public function viewLogs(User $user, Category $category): bool
- {
- return $user->can('viewAny', Activity::class) && $this->update($user, $category);
- }
-}
diff --git a/tests/stubs/Policies/CustomerPolicy.php b/tests/stubs/Policies/CustomerPolicy.php
deleted file mode 100644
index c1c3473..0000000
--- a/tests/stubs/Policies/CustomerPolicy.php
+++ /dev/null
@@ -1,99 +0,0 @@
-can('view_customers');
- }
-
- /**
- * Determine whether the user can view the model.
- */
- public function view(User $user, Customer $customer): bool
- {
- return $user->can('view_customers');
- }
-
- /**
- * Determine whether the user can create models.
- */
- public function create(User $user): bool
- {
- return $user->can('edit_customers');
- }
-
- /**
- * Determine whether the user can update the model.
- */
- public function update(User $user, Customer $customer): bool
- {
- return $user->can('edit_customers');
- }
-
- /**
- * Determine whether the user can delete the model.
- */
- public function delete(User $user, Customer $customer): bool
- {
- return $user->can('delete_customers');
- }
-
- /**
- * Determine whether the user can view trashed models.
- */
- public function viewTrash(User $user): bool
- {
- return $user->can('delete_customers') || $user->can('force_delete_customers');
- }
-
- /**
- * Determine whether the user can restore the model.
- */
- public function restore(User $user, Customer $customer): bool
- {
- return $user->can('delete_customers') || $user->can('force_delete_customers');
- }
-
- /**
- * Determine whether the user can permanently delete the model.
- */
- public function forceDelete(User $user, Customer $customer): bool
- {
- return $user->can('force_delete_customers');
- }
-
- /**
- * Determine whether the user can view the model logs.
- */
- public function viewLogs(User $user, Customer $customer): bool
- {
- return $user->can('viewAny', Activity::class) && $this->update($user, $customer);
- }
-
- /**
- * Determine whether the user can approve customers.
- */
- public function approve(User $user): bool
- {
- return $user->can('approve_customers') && $this->create($user);
- }
-
- /**
- * Determine whether the user can resend the verification for the customer.
- */
- public function resendVerification(User $user, Customer $customer): bool
- {
- return $customer->needsEmailVerification() && $this->update($user, $customer);
- }
-}
diff --git a/tests/stubs/Policies/ProductPolicy.php b/tests/stubs/Policies/ProductPolicy.php
deleted file mode 100644
index bde7d50..0000000
--- a/tests/stubs/Policies/ProductPolicy.php
+++ /dev/null
@@ -1,83 +0,0 @@
-can('view_products');
- }
-
- /**
- * Determine whether the user can view the model.
- */
- public function view(User $user, Product $product): bool
- {
- return $user->can('view_products');
- }
-
- /**
- * Determine whether the user can create models.
- */
- public function create(User $user): bool
- {
- return $user->can('edit_products');
- }
-
- /**
- * Determine whether the user can update the model.
- */
- public function update(User $user, Product $product): bool
- {
- return $user->can('edit_products');
- }
-
- /**
- * Determine whether the user can delete the model.
- */
- public function delete(User $user, Product $product): bool
- {
- return $user->can('delete_products');
- }
-
- /**
- * Determine whether the user can view trashed models.
- */
- public function viewTrash(User $user): bool
- {
- return $user->can('delete_products') || $user->can('force_delete_products');
- }
-
- /**
- * Determine whether the user can restore the model.
- */
- public function restore(User $user, Product $product): bool
- {
- return $user->can('delete_products') || $user->can('force_delete_products');
- }
-
- /**
- * Determine whether the user can permanently delete the model.
- */
- public function forceDelete(User $user, Product $product): bool
- {
- return $user->can('force_delete_products');
- }
-
- /**
- * Determine whether the user can view the model logs.
- */
- public function viewLogs(User $user, Product $product): bool
- {
- return $user->can('viewAny', Activity::class) && $this->update($user, $product);
- }
-}
diff --git a/tests/stubs/Providers/AuthModelsAppServiceProvider.php b/tests/stubs/Providers/AuthModelsAppServiceProvider.php
deleted file mode 100644
index 421ef38..0000000
--- a/tests/stubs/Providers/AuthModelsAppServiceProvider.php
+++ /dev/null
@@ -1,46 +0,0 @@
- \App\Models\Customer::class,
- 'public_user' => \App\Models\PublicUser::class,
- 'user' => \App\Models\User::class,
- 'oauth_client' => \Laravel\Passport\Client::class,
- 'role' => \Javaabu\Permissions\Models\Role::class,
- ]);
-
- SubjectTypes::register([
- \App\Models\Customer::class,
- \App\Models\PublicUser::class,
- \App\Models\User::class,
- \Javaabu\Permissions\Models\Role::class,
- ]);
-
- CauserTypes::register([
- \App\Models\Customer::class,
- \App\Models\PublicUser::class,
- \App\Models\User::class,
- ]);
- }
-}
diff --git a/tests/stubs/Providers/ModelsAppServiceProvider.php b/tests/stubs/Providers/ModelsAppServiceProvider.php
deleted file mode 100644
index 8ad6914..0000000
--- a/tests/stubs/Providers/ModelsAppServiceProvider.php
+++ /dev/null
@@ -1,44 +0,0 @@
- \App\Models\Product::class,
- 'category' => \App\Models\Category::class,
- 'user' => \App\Models\User::class,
- 'oauth_client' => \Laravel\Passport\Client::class,
- 'role' => \Javaabu\Permissions\Models\Role::class,
- ]);
-
- SubjectTypes::register([
- \App\Models\Product::class,
- \App\Models\Category::class,
- \App\Models\User::class,
- \Javaabu\Permissions\Models\Role::class,
- ]);
-
- CauserTypes::register([
- \App\Models\User::class,
- ]);
- }
-}
diff --git a/tests/stubs/Providers/SkeletonAppServiceProvider.php b/tests/stubs/Providers/SkeletonAppServiceProvider.php
deleted file mode 100644
index dca68a1..0000000
--- a/tests/stubs/Providers/SkeletonAppServiceProvider.php
+++ /dev/null
@@ -1,40 +0,0 @@
- \App\Models\User::class,
- 'oauth_client' => \Laravel\Passport\Client::class,
- 'role' => \Javaabu\Permissions\Models\Role::class,
- ]);
-
- SubjectTypes::register([
- \App\Models\User::class,
- \Javaabu\Permissions\Models\Role::class,
- ]);
-
- CauserTypes::register([
- \App\Models\User::class,
- ]);
- }
-}
diff --git a/tests/stubs/Requests/CategoriesRequest.php b/tests/stubs/Requests/CategoriesRequest.php
deleted file mode 100644
index f4d7883..0000000
--- a/tests/stubs/Requests/CategoriesRequest.php
+++ /dev/null
@@ -1,46 +0,0 @@
-|string>
- */
- public function rules(): array
- {
- $rules = [
- 'name' => ['string', 'max:255'],
- 'slug' => ['string', 'max:255'],
- ];
-
- $unique_name = Rule::unique('categories', 'name');
- $unique_slug = Rule::unique('categories', 'slug');
-
- if ($category = $this->route('category')) {
- $unique_name->ignore($category->getKey());
- $unique_slug->ignore($category->getKey());
- } else {
- $rules['name'][] = 'required';
- $rules['slug'][] = 'required';
- }
-
- $rules['name'][] = $unique_name;
- $rules['slug'][] = $unique_slug;
-
- return $rules;
- }
-}
diff --git a/tests/stubs/Requests/CustomersRequest.php b/tests/stubs/Requests/CustomersRequest.php
deleted file mode 100644
index a6b2db5..0000000
--- a/tests/stubs/Requests/CustomersRequest.php
+++ /dev/null
@@ -1,76 +0,0 @@
-user() instanceof Customer) {
- if ($customer = $this->getRouteUser()) {
- return $customer->id == $this->user()->id;
- } else {
- return if_route_pattern(['customer.*', 'api.customer.update']);
- }
- }
-
- return false;
- }
-
- /**
- * Get the route user
- */
- protected function getRouteUser(): ?\Javaabu\Auth\User
- {
- return $this->route('customer');
- }
-
- /**
- * Get the validation rules that apply to the request.
- *
- * @return array|string>
- */
- public function rules(): array
- {
- $rules = $this->baseRules();
-
- $rules += [
- 'designation' => ['string', 'max:255'],
- 'address' => ['string', 'max:255'],
- 'on_sale' => ['boolean'],
- 'expire_at' => ['date'],
- 'category' => ['nullable', 'exists:categories,id'],
- ];
-
- $unique_designation = Rule::unique('customers', 'designation');
-
- if ($customer = $this->getUserBeingEdited()) {
- $unique_designation->ignore($customer->getKey());
- } else {
- $rules['designation'][] = 'required';
- $rules['address'][] = 'required';
- $rules['expire_at'][] = 'required';
- }
-
- $rules['designation'][] = $unique_designation;
-
- return $rules;
- }
-}
diff --git a/tests/stubs/Requests/OrdersRequest.php b/tests/stubs/Requests/OrdersRequest.php
deleted file mode 100644
index 8139f12..0000000
--- a/tests/stubs/Requests/OrdersRequest.php
+++ /dev/null
@@ -1,44 +0,0 @@
-|string>
- */
- public function rules(): array
- {
- $rules = [
- 'order_no' => ['string', 'max:4'],
- 'category' => ['exists:categories,id'],
- 'product_slug' => ['exists:products,slug'],
- 'status' => [Rule::enum(OrderStatuses::class)],
- ];
-
- if ($order = $this->route('order')) {
- //
- } else {
- $rules['order_no'][] = 'required';
- $rules['category'][] = 'required';
- $rules['product_slug'][] = 'required';
- $rules['status'][] = 'required';
- }
-
- return $rules;
- }
-}
diff --git a/tests/stubs/Requests/ProductsRequest.php b/tests/stubs/Requests/ProductsRequest.php
deleted file mode 100644
index ff0efc0..0000000
--- a/tests/stubs/Requests/ProductsRequest.php
+++ /dev/null
@@ -1,66 +0,0 @@
-|string>
- */
- public function rules(): array
- {
- $rules = [
- 'name' => ['string', 'max:255'],
- 'address' => ['string', 'max:255'],
- 'slug' => ['string', 'max:255'],
- 'description' => ['nullable', 'string'],
- 'price' => ['decimal:0,2', 'min:0', 'max:999999999999'],
- 'stock' => ['integer', 'min:0', 'max:4294967295'],
- 'on_sale' => ['boolean'],
- 'features' => ['array'],
- 'published_at' => ['date'],
- 'expire_at' => ['date'],
- 'released_on' => ['date'],
- 'sale_time' => ['date'],
- 'status' => ['in:draft,published'],
- 'category' => ['nullable', 'exists:categories,id'],
- 'manufactured_year' => ['integer', 'min:1900', 'max:2100'],
- ];
-
- $unique_slug = Rule::unique('products', 'slug');
-
- if ($product = $this->route('product')) {
- $unique_slug->ignore($product->getKey());
- } else {
- $rules['name'][] = 'required';
- $rules['address'][] = 'required';
- $rules['slug'][] = 'required';
- $rules['price'][] = 'required';
- $rules['stock'][] = 'required';
- $rules['features'][] = 'required';
- $rules['published_at'][] = 'required';
- $rules['expire_at'][] = 'required';
- $rules['released_on'][] = 'required';
- $rules['sale_time'][] = 'required';
- $rules['status'][] = 'required';
- $rules['manufactured_year'][] = 'required';
- }
-
- $rules['slug'][] = $unique_slug;
-
- return $rules;
- }
-}
diff --git a/tests/stubs/Requests/PublicUsersRequest.php b/tests/stubs/Requests/PublicUsersRequest.php
deleted file mode 100644
index c8e0e31..0000000
--- a/tests/stubs/Requests/PublicUsersRequest.php
+++ /dev/null
@@ -1,55 +0,0 @@
-user() instanceof PublicUser) {
- if ($public_user = $this->getRouteUser()) {
- return $public_user->id == $this->user()->id;
- } else {
- return if_route_pattern(['portal.*', 'api.public-user.update']);
- }
- }
-
- return false;
- }
-
- /**
- * Get the route user
- */
- protected function getRouteUser(): ?\Javaabu\Auth\User
- {
- return $this->route('public_user');
- }
-
- /**
- * Get the validation rules that apply to the request.
- *
- * @return array|string>
- */
- public function rules(): array
- {
- $rules = $this->baseRules(false, false);
-
- return $rules;
- }
-}
diff --git a/tests/stubs/SingleNameCases.php b/tests/stubs/SingleNameCases.php
deleted file mode 100644
index 7a375bb..0000000
--- a/tests/stubs/SingleNameCases.php
+++ /dev/null
@@ -1,19 +0,0 @@
- env('AUTH_GUARD', 'web_customer'),
diff --git a/tests/stubs/config/auth/_default-passwords.stub b/tests/stubs/config/auth/_default-passwords.stub
deleted file mode 100644
index 474954b..0000000
--- a/tests/stubs/config/auth/_default-passwords.stub
+++ /dev/null
@@ -1 +0,0 @@
-'passwords' => env('AUTH_PASSWORD_BROKER', 'customers'),
diff --git a/tests/stubs/config/auth/_guards.stub b/tests/stubs/config/auth/_guards.stub
deleted file mode 100644
index 369ffda..0000000
--- a/tests/stubs/config/auth/_guards.stub
+++ /dev/null
@@ -1,9 +0,0 @@
- 'web_customer' => [
- 'driver' => 'session',
- 'provider' => 'customers',
- ],
-
- 'api_customer' => [
- 'driver' => 'passport',
- 'provider' => 'customers',
- ],
diff --git a/tests/stubs/config/auth/_passport-guards.stub b/tests/stubs/config/auth/_passport-guards.stub
deleted file mode 100644
index 6f8a82e..0000000
--- a/tests/stubs/config/auth/_passport-guards.stub
+++ /dev/null
@@ -1 +0,0 @@
- 'api_customer',
diff --git a/tests/stubs/config/auth/_passwords.stub b/tests/stubs/config/auth/_passwords.stub
deleted file mode 100644
index 8324573..0000000
--- a/tests/stubs/config/auth/_passwords.stub
+++ /dev/null
@@ -1,6 +0,0 @@
- 'customers' => [
- 'provider' => 'customers',
- 'table' => 'customer_password_reset_tokens',
- 'expire' => 60,
- 'throttle' => 60,
- ],
diff --git a/tests/stubs/config/auth/_providers.stub b/tests/stubs/config/auth/_providers.stub
deleted file mode 100644
index e35c985..0000000
--- a/tests/stubs/config/auth/_providers.stub
+++ /dev/null
@@ -1,4 +0,0 @@
- 'customers' => [
- 'driver' => 'eloquent',
- 'model' => App\Models\Customer::class,
- ],
diff --git a/tests/stubs/config/auth/models-auth.php b/tests/stubs/config/auth/models-auth.php
deleted file mode 100644
index d83b5bd..0000000
--- a/tests/stubs/config/auth/models-auth.php
+++ /dev/null
@@ -1,199 +0,0 @@
- [
- 'guard' => env('AUTH_GUARD', 'web_customer'),
- 'passwords' => env('AUTH_PASSWORD_BROKER', 'customers'),
- ],
-
- /*
- |--------------------------------------------------------------------------
- | Authentication Guards
- |--------------------------------------------------------------------------
- |
- | Next, you may define every authentication guard for your application.
- | Of course, a great default configuration has been defined for you
- | which utilizes session storage plus the Eloquent user provider.
- |
- | All authentication guards have a user provider, which defines how the
- | users are actually retrieved out of your database or other storage
- | system used by the application. Typically, Eloquent is utilized.
- |
- | Supported: "session"
- |
- */
-
- 'guards' => [
- 'web_public_user' => [
- 'driver' => 'session',
- 'provider' => 'public_users',
- ],
-
- 'api_public_user' => [
- 'driver' => 'passport',
- 'provider' => 'public_users',
- ],
-
- 'web_customer' => [
- 'driver' => 'session',
- 'provider' => 'customers',
- ],
-
- 'api_customer' => [
- 'driver' => 'passport',
- 'provider' => 'customers',
- ],
-
- 'web_admin' => [
- 'driver' => 'session',
- 'provider' => 'users',
- ],
-
- 'api_admin' => [
- 'driver' => 'passport',
- 'provider' => 'users',
- ],
-
- 'api' => [
- 'driver' => 'passport',
- 'provider' => 'users',
- // 'hash' => false,
- ],
- ],
-
- /*
- |--------------------------------------------------------------------------
- | User Providers
- |--------------------------------------------------------------------------
- |
- | All authentication guards have a user provider, which defines how the
- | users are actually retrieved out of your database or other storage
- | system used by the application. Typically, Eloquent is utilized.
- |
- | If you have multiple user tables or models you may configure multiple
- | providers to represent the model / table. These providers may then
- | be assigned to any extra authentication guards you have defined.
- |
- | Supported: "database", "eloquent"
- |
- */
-
- 'providers' => [
- 'public_users' => [
- 'driver' => 'eloquent',
- 'model' => App\Models\PublicUser::class,
- ],
-
- 'customers' => [
- 'driver' => 'eloquent',
- 'model' => App\Models\Customer::class,
- ],
-
- 'users' => [
- 'driver' => 'eloquent',
- 'model' => env('AUTH_MODEL', App\Models\User::class),
- ],
-
- // 'users' => [
- // 'driver' => 'database',
- // 'table' => 'users',
- // ],
- ],
-
- /*
- |--------------------------------------------------------------------------
- | Resetting Passwords
- |--------------------------------------------------------------------------
- |
- | These configuration options specify the behavior of Laravel's password
- | reset functionality, including the table utilized for token storage
- | and the user provider that is invoked to actually retrieve users.
- |
- | The expiry time is the number of minutes that each reset token will be
- | considered valid. This security feature keeps tokens short-lived so
- | they have less time to be guessed. You may change this as needed.
- |
- | The throttle setting is the number of seconds a user must wait before
- | generating more password reset tokens. This prevents the user from
- | quickly generating a very large amount of password reset tokens.
- |
- */
-
- 'passwords' => [
- 'public_users' => [
- 'provider' => 'public_users',
- 'table' => 'public_user_password_reset_tokens',
- 'expire' => 60,
- 'throttle' => 60,
- ],
-
- 'customers' => [
- 'provider' => 'customers',
- 'table' => 'customer_password_reset_tokens',
- 'expire' => 60,
- 'throttle' => 60,
- ],
-
- 'users' => [
- 'provider' => 'users',
- 'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
- 'expire' => 60,
- 'throttle' => 60,
- ],
- ],
-
- /*
- |--------------------------------------------------------------------------
- | Password Confirmation Timeout
- |--------------------------------------------------------------------------
- |
- | Here you may define the amount of seconds before a password confirmation
- | window expires and users are asked to re-enter their password via the
- | confirmation screen. By default, the timeout lasts for three hours.
- |
- */
-
- 'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
-
- /*
- |--------------------------------------------------------------------------
- | Max login attempts
- |--------------------------------------------------------------------------
- |
- | The maximum login attempts allowed before a user gets locked out.
- | This is a custom configuration.
- |
- */
-
- 'max_login_attempts' => env('MAX_LOGIN_ATTEMPTS', 5),
-
- /*
- |--------------------------------------------------------------------------
- | Passport API Guards
- |--------------------------------------------------------------------------
- |
- | Guards used by Passport for OAuthClientAuth. Put the guards in priority
- | order of least authorized first, then most authorized
- |
- */
-
- 'passport_guards' => [
- 'api_public_user',
- 'api_customer',
- 'api_admin',
- 'api',
- ],
-
-];
diff --git a/tests/stubs/config/auth/skeleton-auth.php b/tests/stubs/config/auth/skeleton-auth.php
deleted file mode 100644
index 0974add..0000000
--- a/tests/stubs/config/auth/skeleton-auth.php
+++ /dev/null
@@ -1,153 +0,0 @@
- [
- 'guard' => env('AUTH_GUARD', 'web_admin'),
- 'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
- ],
-
- /*
- |--------------------------------------------------------------------------
- | Authentication Guards
- |--------------------------------------------------------------------------
- |
- | Next, you may define every authentication guard for your application.
- | Of course, a great default configuration has been defined for you
- | which utilizes session storage plus the Eloquent user provider.
- |
- | All authentication guards have a user provider, which defines how the
- | users are actually retrieved out of your database or other storage
- | system used by the application. Typically, Eloquent is utilized.
- |
- | Supported: "session"
- |
- */
-
- 'guards' => [
- 'web_admin' => [
- 'driver' => 'session',
- 'provider' => 'users',
- ],
-
- 'api_admin' => [
- 'driver' => 'passport',
- 'provider' => 'users',
- ],
-
- 'api' => [
- 'driver' => 'passport',
- 'provider' => 'users',
- // 'hash' => false,
- ],
- ],
-
- /*
- |--------------------------------------------------------------------------
- | User Providers
- |--------------------------------------------------------------------------
- |
- | All authentication guards have a user provider, which defines how the
- | users are actually retrieved out of your database or other storage
- | system used by the application. Typically, Eloquent is utilized.
- |
- | If you have multiple user tables or models you may configure multiple
- | providers to represent the model / table. These providers may then
- | be assigned to any extra authentication guards you have defined.
- |
- | Supported: "database", "eloquent"
- |
- */
-
- 'providers' => [
- 'users' => [
- 'driver' => 'eloquent',
- 'model' => env('AUTH_MODEL', App\Models\User::class),
- ],
-
- // 'users' => [
- // 'driver' => 'database',
- // 'table' => 'users',
- // ],
- ],
-
- /*
- |--------------------------------------------------------------------------
- | Resetting Passwords
- |--------------------------------------------------------------------------
- |
- | These configuration options specify the behavior of Laravel's password
- | reset functionality, including the table utilized for token storage
- | and the user provider that is invoked to actually retrieve users.
- |
- | The expiry time is the number of minutes that each reset token will be
- | considered valid. This security feature keeps tokens short-lived so
- | they have less time to be guessed. You may change this as needed.
- |
- | The throttle setting is the number of seconds a user must wait before
- | generating more password reset tokens. This prevents the user from
- | quickly generating a very large amount of password reset tokens.
- |
- */
-
- 'passwords' => [
- 'users' => [
- 'provider' => 'users',
- 'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
- 'expire' => 60,
- 'throttle' => 60,
- ],
- ],
-
- /*
- |--------------------------------------------------------------------------
- | Password Confirmation Timeout
- |--------------------------------------------------------------------------
- |
- | Here you may define the amount of seconds before a password confirmation
- | window expires and users are asked to re-enter their password via the
- | confirmation screen. By default, the timeout lasts for three hours.
- |
- */
-
- 'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
-
- /*
- |--------------------------------------------------------------------------
- | Max login attempts
- |--------------------------------------------------------------------------
- |
- | The maximum login attempts allowed before a user gets locked out.
- | This is a custom configuration.
- |
- */
-
- 'max_login_attempts' => env('MAX_LOGIN_ATTEMPTS', 5),
-
- /*
- |--------------------------------------------------------------------------
- | Passport API Guards
- |--------------------------------------------------------------------------
- |
- | Guards used by Passport for OAuthClientAuth. Put the guards in priority
- | order of least authorized first, then most authorized
- |
- */
-
- 'passport_guards' => [
- 'api_admin',
- 'api',
- ],
-
-];
diff --git a/tests/stubs/factories/CategoryFactory.php b/tests/stubs/factories/CategoryFactory.php
deleted file mode 100644
index eb6da79..0000000
--- a/tests/stubs/factories/CategoryFactory.php
+++ /dev/null
@@ -1,25 +0,0 @@
-
- */
-class CategoryFactory extends Factory
-{
- /**
- * Define the model's default state.
- *
- * @return array
- */
- public function definition(): array
- {
- return [
- 'name' => fake()->unique()->text(fake()->numberBetween(5, 255)),
- 'slug' => fake()->unique()->slug(),
- ];
- }
-
-}
diff --git a/tests/stubs/factories/CountryFactory.php b/tests/stubs/factories/CountryFactory.php
deleted file mode 100644
index 73f10b2..0000000
--- a/tests/stubs/factories/CountryFactory.php
+++ /dev/null
@@ -1,25 +0,0 @@
-
- */
-class CountryFactory extends Factory
-{
- /**
- * Define the model's default state.
- *
- * @return array
- */
- public function definition(): array
- {
- return [
- 'name' => fake()->text(fake()->numberBetween(5, 255)),
- 'code' => fake()->optional()->unique()?->regexify('[a-z]{4}'),
- ];
- }
-
-}
diff --git a/tests/stubs/factories/CustomerFactory.php b/tests/stubs/factories/CustomerFactory.php
deleted file mode 100644
index fcf390d..0000000
--- a/tests/stubs/factories/CustomerFactory.php
+++ /dev/null
@@ -1,41 +0,0 @@
-
- */
-class CustomerFactory extends \Javaabu\Auth\Factories\UserFactory
-{
- /**
- * Define the model's default state.
- *
- * @return array
- */
- public function definition(): array
- {
- $definitions = parent::definition();
-
- $definitions['password'] = 'Customer@123456';
-
- $definitions += [
- 'designation' => fake()->unique()->text(fake()->numberBetween(5, 255)),
- 'address' => fake()->address(),
- 'on_sale' => fake()->boolean(),
- 'expire_at' => fake()->dateTime()?->format('Y-m-d H:i'),
- ];
-
- return $definitions;
- }
-
- public function withCategory(): CustomerFactory
- {
- return $this->state(function (array $attributes) {
- return [
- 'category_id' => fake()->passThrough(random_id_or_generate(\App\Models\Category::class, 'id', generate: true)),
- ];
- });
- }
-}
diff --git a/tests/stubs/factories/OrderFactory.php b/tests/stubs/factories/OrderFactory.php
deleted file mode 100644
index eaf7141..0000000
--- a/tests/stubs/factories/OrderFactory.php
+++ /dev/null
@@ -1,48 +0,0 @@
-
- */
-class OrderFactory extends Factory
-{
- /**
- * Define the model's default state.
- *
- * @return array
- */
- public function definition(): array
- {
- return [
- 'order_no' => fake()->regexify('[a-z]{4}'),
- 'status' => fake()->randomElement(array_column(\Javaabu\Generators\Tests\TestSupport\Enums\OrderStatuses::cases(), 'value')),
- ];
- }
-
- public function withCategory(): OrderFactory
- {
- return $this->state(function (array $attributes) {
- return [
- 'category_id' => fake()->passThrough(random_id_or_generate(\App\Models\Category::class, 'id', generate: true)),
- ];
- });
- }
-
- public function withProductSlug(): OrderFactory
- {
- return $this->state(function (array $attributes) {
- return [
- 'product_slug' => fake()->passThrough(random_id_or_generate(\App\Models\Product::class, 'slug', generate: true)),
- ];
- });
- }
-
- public function withRequiredRelations(): OrderFactory
- {
- return $this->withCategory()
- ->withProduct();
- }
-}
diff --git a/tests/stubs/factories/PaymentFactory.php b/tests/stubs/factories/PaymentFactory.php
deleted file mode 100644
index 7a9a86d..0000000
--- a/tests/stubs/factories/PaymentFactory.php
+++ /dev/null
@@ -1,38 +0,0 @@
-
- */
-class PaymentFactory extends Factory
-{
- /**
- * Define the model's default state.
- *
- * @return array
- */
- public function definition(): array
- {
- return [
- 'amount' => fake()->randomFloat(2, -99999999999, 99999999999),
- 'status' => fake()->optional()->randomElement(array_column(\App\Enums\PaymentStatuses::cases(), 'value')),
- ];
- }
-
- public function withOrder(): PaymentFactory
- {
- return $this->state(function (array $attributes) {
- return [
- 'order_id' => fake()->unique()->passThrough(random_id_or_generate(\App\Models\Order::class, 'id', generate: true, unique: true)),
- ];
- });
- }
-
- public function withRequiredRelations(): PaymentFactory
- {
- return $this->withOrder();
- }
-}
diff --git a/tests/stubs/factories/ProductFactory.php b/tests/stubs/factories/ProductFactory.php
deleted file mode 100644
index 99f6e49..0000000
--- a/tests/stubs/factories/ProductFactory.php
+++ /dev/null
@@ -1,45 +0,0 @@
-
- */
-class ProductFactory extends Factory
-{
- /**
- * Define the model's default state.
- *
- * @return array
- */
- public function definition(): array
- {
- return [
- 'name' => fake()->text(fake()->numberBetween(5, 255)),
- 'address' => fake()->address(),
- 'slug' => fake()->unique()->slug(),
- 'description' => fake()->optional()->sentences(3, true),
- 'price' => fake()->randomFloat(2, 0, 999999999999),
- 'stock' => fake()->numberBetween(0, 4294967295),
- 'on_sale' => fake()->boolean(),
- 'features' => fake()->words(),
- 'published_at' => fake()->dateTime()?->format('Y-m-d H:i'),
- 'expire_at' => fake()->dateTime()?->format('Y-m-d H:i'),
- 'released_on' => fake()->date(),
- 'sale_time' => fake()->time(),
- 'status' => fake()->randomElement(['draft', 'published']),
- 'manufactured_year' => fake()->year(2100),
- ];
- }
-
- public function withCategory(): ProductFactory
- {
- return $this->state(function (array $attributes) {
- return [
- 'category_id' => fake()->passThrough(random_id_or_generate(\App\Models\Category::class, 'id', generate: true)),
- ];
- });
- }
-}
diff --git a/tests/stubs/migrations/create_customer_password_reset_tokens_table.php b/tests/stubs/migrations/create_customer_password_reset_tokens_table.php
deleted file mode 100644
index 9622ce4..0000000
--- a/tests/stubs/migrations/create_customer_password_reset_tokens_table.php
+++ /dev/null
@@ -1,32 +0,0 @@
-string('email')->index();
- $table->string('token');
- $table->timestamp('created_at')->nullable();
- });
- }
-
- /**
- * Reverse the migrations.
- *
- * @return void
- */
- public function down()
- {
- Schema::dropIfExists('customer_password_reset_tokens');
- }
-};
diff --git a/tests/stubs/routes/_admin.stub b/tests/stubs/routes/_admin.stub
deleted file mode 100644
index 72651e9..0000000
--- a/tests/stubs/routes/_admin.stub
+++ /dev/null
@@ -1,5 +0,0 @@
- /**
- * Categories
- */
- Route::match(['PUT', 'PATCH'], 'categories', [CategoriesController::class, 'bulk'])->name('categories.bulk');
- Route::resource('categories', CategoriesController::class);
diff --git a/tests/stubs/routes/_adminSoftDeletes.stub b/tests/stubs/routes/_adminSoftDeletes.stub
deleted file mode 100644
index 5a522c4..0000000
--- a/tests/stubs/routes/_adminSoftDeletes.stub
+++ /dev/null
@@ -1,8 +0,0 @@
- /**
- * Products
- */
- Route::match(['PUT', 'PATCH'], 'products', [ProductsController::class, 'bulk'])->name('products.bulk');
- Route::get('products/trash', [ProductsController::class, 'trash'])->name('products.trash');
- Route::post('products/{product}/restore', [ProductsController::class, 'restore'])->name('products.restore')->withTrashed();
- Route::delete('products/{product}/force-delete', [ProductsController::class, 'forceDelete'])->name('products.force-delete')->withTrashed();
- Route::resource('products', ProductsController::class);
diff --git a/tests/stubs/routes/_api.stub b/tests/stubs/routes/_api.stub
deleted file mode 100644
index c3182be..0000000
--- a/tests/stubs/routes/_api.stub
+++ /dev/null
@@ -1,5 +0,0 @@
- /**
- * Categories
- */
- Route::get('categories', [CategoriesController::class, 'index'])->name('categories.index');
- Route::get('categories/{id}', [CategoriesController::class, 'show'])->name('categories.show');
diff --git a/tests/stubs/routes/modelsAdmin.php b/tests/stubs/routes/modelsAdmin.php
deleted file mode 100644
index 9371a03..0000000
--- a/tests/stubs/routes/modelsAdmin.php
+++ /dev/null
@@ -1,131 +0,0 @@
- 'Auth',
-], function () {
- Route::get('login', [LoginController::class, 'showLoginForm'])->name('login');
- Route::post('login', [LoginController::class, 'login'])->name('login-post');
- Route::post('logout', [LoginController::class, 'logout'])->name('logout');
-
- // Registration Routes...
- // Route::get('register', [RegisterController::class, 'showRegistrationForm'])->name( 'register');
- // Route::post('register', [RegisterController::class, 'register'])->name( 'register-post');
-
- // Password
- Route::group([
- 'prefix' => 'password',
- 'as' => 'password.'
- ], function () {
- // Forgot Password
- Route::get('reset', [ForgotPasswordController::class, 'showLinkRequestForm'])->name('request');
- Route::post('email', [ForgotPasswordController::class, 'sendResetLinkEmail'])->name('email');
- Route::get('reset/{token}', [ResetPasswordController::class, 'showResetForm'])->name('reset');
- Route::post('reset', [ResetPasswordController::class, 'reset'])->name('update');
-
- // Confirm Password
- Route::get('confirm', [ConfirmPasswordController::class, 'showConfirmForm'])->name('confirm');
- Route::post('confirm', [ConfirmPasswordController::class, 'confirm'])->name('confirm-post');
-
- // Password Update
- Route::get('update', [UpdatePasswordController::class, 'showPasswordUpdateForm'])->name('new-password');
- Route::post('update', [UpdatePasswordController::class, 'updatePassword'])->name('new-password-post');
- });
-
- // Email Verification
- Route::group([
- 'prefix' => 'verify',
- 'as' => 'verification.'
- ], function () {
- Route::get('/', [VerificationController::class, 'show'])->name('notice');
- Route::post('email/resend', [VerificationController::class, 'resend'])->name('resend');
- Route::get('email/{id}/{hash}', [VerificationController::class, 'verify'])->name('verify');
- });
-});
-
-
-/**
- * Protected routes
- */
-Route::group([
- 'middleware' => ['auth:web_admin', 'active:web_admin', 'password-update-not-required:web_admin'],
-], function () {
- Route::get('/', [HomeController::class, 'index'])->name('home');
-
- if (app()->runningUnitTests()) {
- Route::get('test', function () {
- return response()->json('It works');
- })->middleware('password.confirm:web_admin,admin.password.confirm');
- }
-
- // Generator Routes - DONOT REMOVE
-
- /**
- * Products
- */
- Route::match(['PUT', 'PATCH'], 'products', [ProductsController::class, 'bulk'])->name('products.bulk');
- Route::get('products/trash', [ProductsController::class, 'trash'])->name('products.trash');
- Route::post('products/{product}/restore', [ProductsController::class, 'restore'])->name('products.restore')->withTrashed();
- Route::delete('products/{product}/force-delete', [ProductsController::class, 'forceDelete'])->name('products.force-delete')->withTrashed();
- Route::resource('products', ProductsController::class);
-
- /**
- * Categories
- */
- Route::match(['PUT', 'PATCH'], 'categories', [CategoriesController::class, 'bulk'])->name('categories.bulk');
- Route::resource('categories', CategoriesController::class);
-
- /**
- * Users
- */
-
- // profile
- Route::get('profile', [UsersController::class, 'profile'])->name('users.profile');
- Route::match(['PUT', 'PATCH'], 'profile', [UsersController::class, 'updateProfile'])->name('users.profile.update');
- Route::delete('profile', [UsersController::class, 'deleteProfile'])->name('users.profile.destroy');
-
- Route::match(['PUT', 'PATCH'], 'users', [UsersController::class, 'bulk'])->name('users.bulk');
- Route::get('users/trash', [UsersController::class, 'trash'])->name('users.trash');
- Route::post('users/{user}/restore', [UsersController::class, 'restore'])->name('users.restore')->withTrashed();
- Route::delete('users/{user}/force-delete', [UsersController::class, 'forceDelete'])->name('users.force-delete')->withTrashed();
- Route::resource('users', UsersController::class);
-
- /**
- * Roles
- */
- Route::match(['PUT', 'PATCH'], 'roles', [RolesController::class, 'bulk'])->name('roles.bulk');
- Route::resource('roles', RolesController::class);
-
- /**
- * Logs
- */
- Route::resource('logs', LogsController::class)->only(['index', 'show']);
-
- /**
- * OAuth Clients
- */
- Route::match(['PUT', 'PATCH'], 'oauth-clients', [OAuthClientsController::class, 'bulk'])->name('oauth-clients.bulk');
- Route::resource('oauth-clients', OAuthClientsController::class);
-
- /**
- * Routes for settings
- */
- Route::get('settings', [SettingsController::class, 'index'])->name('settings.index');
- Route::patch('settings', [SettingsController::class, 'update'])->name('settings.update');
- Route::post('settings', [SettingsController::class, 'reset'])->name('settings.reset');
-});
diff --git a/tests/stubs/routes/modelsApi.php b/tests/stubs/routes/modelsApi.php
deleted file mode 100644
index c99a204..0000000
--- a/tests/stubs/routes/modelsApi.php
+++ /dev/null
@@ -1,105 +0,0 @@
- 'passport.',
- 'prefix' => config('passport.path', 'oauth'),
- 'namespace' => '\Laravel\Passport\Http\Controllers',
-], function () {
- Route::post('/token', [
- 'uses' => 'AccessTokenController@issueToken',
- 'as' => 'token',
- 'middleware' => 'throttle',
- ]);
-});
-
-
-/**
- * Public routes
- */
-Route::group([
- 'middleware' => ['oauth.client:read'],
-], function () {
-
- /**
- * Public upload routes
- */
- //Route::post('users', ['uses' => 'UsersController@store', 'as' => 'users.store']);
-
- /**
- * Public JSON routes
- */
- Route::group([
- 'middleware' => ['json'],
- ], function () {
-
- if (app()->runningUnitTests()) {
- Route::get('test', function () {
- return response()->json('It works');
- });
- }
-
- // Api Public Routes - DONOT REMOVE
-
- /**
- * Products
- */
- Route::get('products', [ProductsController::class, 'index'])->name('products.index');
- Route::get('products/{id}', [ProductsController::class, 'show'])->name('products.show');
-
- /**
- * Categories
- */
- Route::get('categories', [CategoriesController::class, 'index'])->name('categories.index');
- Route::get('categories/{id}', [CategoriesController::class, 'show'])->name('categories.show');
- });
-});
-
-/**
- * Protected routes
- */
-Route::group([
- 'middleware' => ['auth:api', 'active:api'],
-], function () {
-
- /**
- * User avatar
- */
- //Route::match(['PUT', 'PATCH'], 'users/profile', ['uses' => 'UsersController@update', 'as' => 'users.update']);
-
- /**
- * Protected JSON routes
- */
- Route::group([
- 'middleware' => ['json'],
- ], function () {
-
- /**
- * Auth
- */
- Route::post('oauth/revoke', [UsersController::class, 'revoke']);
-
- /**
- * Users
- */
- Route::get('users/profile', [UsersController::class, 'profile'])->name('users.profile');
- Route::get('users', [UsersController::class, 'index'])->name('users.index');
- Route::get('users/{id}', [UsersController::class, 'show'])->name('users.show');
-
- });
-});
diff --git a/tests/stubs/routes/skeletonAdmin.php b/tests/stubs/routes/skeletonAdmin.php
deleted file mode 100644
index 3e231aa..0000000
--- a/tests/stubs/routes/skeletonAdmin.php
+++ /dev/null
@@ -1,114 +0,0 @@
- 'Auth',
-], function () {
- Route::get('login', [LoginController::class, 'showLoginForm'])->name('login');
- Route::post('login', [LoginController::class, 'login'])->name('login-post');
- Route::post('logout', [LoginController::class, 'logout'])->name('logout');
-
- // Registration Routes...
- // Route::get('register', [RegisterController::class, 'showRegistrationForm'])->name( 'register');
- // Route::post('register', [RegisterController::class, 'register'])->name( 'register-post');
-
- // Password
- Route::group([
- 'prefix' => 'password',
- 'as' => 'password.'
- ], function () {
- // Forgot Password
- Route::get('reset', [ForgotPasswordController::class, 'showLinkRequestForm'])->name('request');
- Route::post('email', [ForgotPasswordController::class, 'sendResetLinkEmail'])->name('email');
- Route::get('reset/{token}', [ResetPasswordController::class, 'showResetForm'])->name('reset');
- Route::post('reset', [ResetPasswordController::class, 'reset'])->name('update');
-
- // Confirm Password
- Route::get('confirm', [ConfirmPasswordController::class, 'showConfirmForm'])->name('confirm');
- Route::post('confirm', [ConfirmPasswordController::class, 'confirm'])->name('confirm-post');
-
- // Password Update
- Route::get('update', [UpdatePasswordController::class, 'showPasswordUpdateForm'])->name('new-password');
- Route::post('update', [UpdatePasswordController::class, 'updatePassword'])->name('new-password-post');
- });
-
- // Email Verification
- Route::group([
- 'prefix' => 'verify',
- 'as' => 'verification.'
- ], function () {
- Route::get('/', [VerificationController::class, 'show'])->name('notice');
- Route::post('email/resend', [VerificationController::class, 'resend'])->name('resend');
- Route::get('email/{id}/{hash}', [VerificationController::class, 'verify'])->name('verify');
- });
-});
-
-
-/**
- * Protected routes
- */
-Route::group([
- 'middleware' => ['auth:web_admin', 'active:web_admin', 'password-update-not-required:web_admin'],
-], function () {
- Route::get('/', [HomeController::class, 'index'])->name('home');
-
- if (app()->runningUnitTests()) {
- Route::get('test', function () {
- return response()->json('It works');
- })->middleware('password.confirm:web_admin,admin.password.confirm');
- }
-
- // Generator Routes - DONOT REMOVE
-
- /**
- * Users
- */
-
- // profile
- Route::get('profile', [UsersController::class, 'profile'])->name('users.profile');
- Route::match(['PUT', 'PATCH'], 'profile', [UsersController::class, 'updateProfile'])->name('users.profile.update');
- Route::delete('profile', [UsersController::class, 'deleteProfile'])->name('users.profile.destroy');
-
- Route::match(['PUT', 'PATCH'], 'users', [UsersController::class, 'bulk'])->name('users.bulk');
- Route::get('users/trash', [UsersController::class, 'trash'])->name('users.trash');
- Route::post('users/{user}/restore', [UsersController::class, 'restore'])->name('users.restore')->withTrashed();
- Route::delete('users/{user}/force-delete', [UsersController::class, 'forceDelete'])->name('users.force-delete')->withTrashed();
- Route::resource('users', UsersController::class);
-
- /**
- * Roles
- */
- Route::match(['PUT', 'PATCH'], 'roles', [RolesController::class, 'bulk'])->name('roles.bulk');
- Route::resource('roles', RolesController::class);
-
- /**
- * Logs
- */
- Route::resource('logs', LogsController::class)->only(['index', 'show']);
-
- /**
- * OAuth Clients
- */
- Route::match(['PUT', 'PATCH'], 'oauth-clients', [OAuthClientsController::class, 'bulk'])->name('oauth-clients.bulk');
- Route::resource('oauth-clients', OAuthClientsController::class);
-
- /**
- * Routes for settings
- */
- Route::get('settings', [SettingsController::class, 'index'])->name('settings.index');
- Route::patch('settings', [SettingsController::class, 'update'])->name('settings.update');
- Route::post('settings', [SettingsController::class, 'reset'])->name('settings.reset');
-});
diff --git a/tests/stubs/routes/skeletonApi.php b/tests/stubs/routes/skeletonApi.php
deleted file mode 100644
index a45c5d1..0000000
--- a/tests/stubs/routes/skeletonApi.php
+++ /dev/null
@@ -1,91 +0,0 @@
- 'passport.',
- 'prefix' => config('passport.path', 'oauth'),
- 'namespace' => '\Laravel\Passport\Http\Controllers',
-], function () {
- Route::post('/token', [
- 'uses' => 'AccessTokenController@issueToken',
- 'as' => 'token',
- 'middleware' => 'throttle',
- ]);
-});
-
-
-/**
- * Public routes
- */
-Route::group([
- 'middleware' => ['oauth.client:read'],
-], function () {
-
- /**
- * Public upload routes
- */
- //Route::post('users', ['uses' => 'UsersController@store', 'as' => 'users.store']);
-
- /**
- * Public JSON routes
- */
- Route::group([
- 'middleware' => ['json'],
- ], function () {
-
- if (app()->runningUnitTests()) {
- Route::get('test', function () {
- return response()->json('It works');
- });
- }
-
- // Api Public Routes - DONOT REMOVE
- });
-});
-
-/**
- * Protected routes
- */
-Route::group([
- 'middleware' => ['auth:api', 'active:api'],
-], function () {
-
- /**
- * User avatar
- */
- //Route::match(['PUT', 'PATCH'], 'users/profile', ['uses' => 'UsersController@update', 'as' => 'users.update']);
-
- /**
- * Protected JSON routes
- */
- Route::group([
- 'middleware' => ['json'],
- ], function () {
-
- /**
- * Auth
- */
- Route::post('oauth/revoke', [UsersController::class, 'revoke']);
-
- /**
- * Users
- */
- Route::get('users/profile', [UsersController::class, 'profile'])->name('users.profile');
- Route::get('users', [UsersController::class, 'index'])->name('users.index');
- Route::get('users/{id}', [UsersController::class, 'show'])->name('users.show');
-
- });
-});
diff --git a/tests/stubs/seeders/AuthPermissionsSeederExisting.php b/tests/stubs/seeders/AuthPermissionsSeederExisting.php
deleted file mode 100644
index ff0e49b..0000000
--- a/tests/stubs/seeders/AuthPermissionsSeederExisting.php
+++ /dev/null
@@ -1,93 +0,0 @@
- [
- 'view_public_users' => 'View public users',
- 'edit_public_users' => 'Edit public users',
- 'delete_public_users' => 'Delete public users',
- 'force_delete_public_users' => 'Permanently delete public users',
- 'approve_public_users' => 'Approve public users',
- ],
-
- /**
- * Customers permissions
- */
- 'customers' => [
- 'view_customers' => 'View customers',
- 'edit_customers' => 'Edit customers',
- 'delete_customers' => 'Delete customers',
- 'force_delete_customers' => 'Permanently delete customers',
- 'approve_customers' => 'Approve customers',
- ],
-
- /**
- * Users permissions
- */
- 'users' => [
- 'view_users' => 'View users',
- 'edit_users' => 'Edit users',
- 'approve_users' => 'Approve users',
- 'delete_users' => 'Delete users',
- 'force_delete_users' => 'Permanently delete users',
- ],
-
- /**
- * OAuth Clients permissions
- */
- 'oauth_clients' => [
- 'view_oauth_clients' => 'View OAuth2 clients',
- 'edit_oauth_clients' => 'Edit own OAuth2 clients',
- 'edit_others_oauth_clients' => 'Edit all OAuth2 clients',
- 'delete_oauth_clients' => 'Delete own OAuth2 clients',
- 'delete_others_oauth_clients' => 'Delete all OAuth2 clients',
- ],
-
- /**
- * Roles permissions
- */
- 'roles' => [
- 'view_roles' => 'View roles',
- 'edit_roles' => 'Edit roles',
- 'delete_roles' => 'Delete roles',
- ],
-
- /**
- * Activity log permissions
- */
- 'logs' => [
- 'view_logs' => 'View logs of authorized models',
- 'view_all_logs' => 'View all logs',
- ],
-
- /**
- * Settings permissions
- */
- 'settings' => [
- 'edit_settings' => 'Edit settings',
- ],
- ];
-
- /**
- * Run the database seeds.
- */
- public function run(): void
- {
- foreach ($this->data as $model => $permissions) {
- foreach ($permissions as $name => $desc) {
- $permission = Permission::firstOrCreate(['name' => $name, 'guard_name' => 'web_admin']);
- $permission->update(['description' => $desc, 'model' => $model]);
- $permission->save();
- }
- }
- }
-}
diff --git a/tests/stubs/seeders/AuthPermissionsSeederNew.php b/tests/stubs/seeders/AuthPermissionsSeederNew.php
deleted file mode 100644
index 815f6a0..0000000
--- a/tests/stubs/seeders/AuthPermissionsSeederNew.php
+++ /dev/null
@@ -1,48 +0,0 @@
- [
- 'view_public_users' => 'View public users',
- 'edit_public_users' => 'Edit public users',
- 'delete_public_users' => 'Delete public users',
- 'force_delete_public_users' => 'Permanently delete public users',
- 'approve_public_users' => 'Approve public users',
- ],
-
- /**
- * Customers permissions
- */
- 'customers' => [
- 'view_customers' => 'View customers',
- 'edit_customers' => 'Edit customers',
- 'delete_customers' => 'Delete customers',
- 'force_delete_customers' => 'Permanently delete customers',
- 'approve_customers' => 'Approve customers',
- ],
-
- ];
-
- /**
- * Run the database seeds.
- */
- public function run(): void
- {
- foreach ($this->data as $model => $permissions) {
- foreach ($permissions as $name => $desc) {
- $permission = Permission::firstOrCreate(['name' => $name, 'guard_name' => 'web_admin']);
- $permission->update(['description' => $desc, 'model' => $model]);
- $permission->save();
- }
- }
- }
-}
diff --git a/tests/stubs/seeders/DefaultUsersSeeder.php b/tests/stubs/seeders/DefaultUsersSeeder.php
deleted file mode 100644
index 5da2306..0000000
--- a/tests/stubs/seeders/DefaultUsersSeeder.php
+++ /dev/null
@@ -1,44 +0,0 @@
- 'Admin User', 'role' => 'super_admin', 'email' => 'demo-admin@javaabu.com', 'password' => 'RandomPassword12345'],
- ['name' => 'Guest User', 'role' => 'guest', 'email' => 'demo-guest@javaabu.com', 'password' => 'RandomPassword12345'],
- ];
-
- /**
- * Run the database seeds.
- *
- * @return void
- */
- public function run()
- {
- foreach ($this->data as $user_data) {
- $user = User::whereEmail($user_data['email'])->first();
- if ($user) {
- $user->update(Arr::except($user_data, 'role'));
- } else {
- $user = new User(Arr::except($user_data, 'role'));
- }
-
- $user->status = \Javaabu\Auth\Enums\UserStatuses::APPROVED;
- $user->email = $user_data['email'];
- $user->password = $user_data['password'];
- $user->resetLoginAttempts();
-
- // save and verify
- $user->markEmailAsVerified();
-
- if (! empty($user_data['role'])) {
- $user->syncRoles($user_data['role']);
- }
- }
- }
-}
diff --git a/tests/stubs/seeders/PermissionsSeeder.php b/tests/stubs/seeders/PermissionsSeeder.php
deleted file mode 100644
index d33548f..0000000
--- a/tests/stubs/seeders/PermissionsSeeder.php
+++ /dev/null
@@ -1,71 +0,0 @@
- [
- 'view_users' => 'View users',
- 'edit_users' => 'Edit users',
- 'approve_users' => 'Approve users',
- 'delete_users' => 'Delete users',
- 'force_delete_users' => 'Permanently delete users',
- ],
-
- /**
- * OAuth Clients permissions
- */
- 'oauth_clients' => [
- 'view_oauth_clients' => 'View OAuth2 clients',
- 'edit_oauth_clients' => 'Edit own OAuth2 clients',
- 'edit_others_oauth_clients' => 'Edit all OAuth2 clients',
- 'delete_oauth_clients' => 'Delete own OAuth2 clients',
- 'delete_others_oauth_clients' => 'Delete all OAuth2 clients',
- ],
-
- /**
- * Roles permissions
- */
- 'roles' => [
- 'view_roles' => 'View roles',
- 'edit_roles' => 'Edit roles',
- 'delete_roles' => 'Delete roles',
- ],
-
- /**
- * Activity log permissions
- */
- 'logs' => [
- 'view_logs' => 'View logs of authorized models',
- 'view_all_logs' => 'View all logs',
- ],
-
- /**
- * Settings permissions
- */
- 'settings' => [
- 'edit_settings' => 'Edit settings',
- ],
- ];
-
- /**
- * Run the database seeds.
- */
- public function run(): void
- {
- foreach ($this->data as $model => $permissions) {
- foreach ($permissions as $name => $desc) {
- $permission = Permission::firstOrCreate(['name' => $name, 'guard_name' => 'web_admin']);
- $permission->update(['description' => $desc, 'model' => $model]);
- $permission->save();
- }
- }
- }
-}
diff --git a/tests/stubs/seeders/PermissionsSeederExisting.php b/tests/stubs/seeders/PermissionsSeederExisting.php
deleted file mode 100644
index c949412..0000000
--- a/tests/stubs/seeders/PermissionsSeederExisting.php
+++ /dev/null
@@ -1,90 +0,0 @@
- [
- 'view_categories' => 'View categories',
- 'edit_categories' => 'Edit categories',
- 'delete_categories' => 'Delete categories',
- ],
-
- /**
- * Products permissions
- */
- 'products' => [
- 'view_products' => 'View products',
- 'edit_products' => 'Edit products',
- 'delete_products' => 'Delete products',
- 'force_delete_products' => 'Permanently delete products',
- ],
-
- /**
- * Users permissions
- */
- 'users' => [
- 'view_users' => 'View users',
- 'edit_users' => 'Edit users',
- 'approve_users' => 'Approve users',
- 'delete_users' => 'Delete users',
- 'force_delete_users' => 'Permanently delete users',
- ],
-
- /**
- * OAuth Clients permissions
- */
- 'oauth_clients' => [
- 'view_oauth_clients' => 'View OAuth2 clients',
- 'edit_oauth_clients' => 'Edit own OAuth2 clients',
- 'edit_others_oauth_clients' => 'Edit all OAuth2 clients',
- 'delete_oauth_clients' => 'Delete own OAuth2 clients',
- 'delete_others_oauth_clients' => 'Delete all OAuth2 clients',
- ],
-
- /**
- * Roles permissions
- */
- 'roles' => [
- 'view_roles' => 'View roles',
- 'edit_roles' => 'Edit roles',
- 'delete_roles' => 'Delete roles',
- ],
-
- /**
- * Activity log permissions
- */
- 'logs' => [
- 'view_logs' => 'View logs of authorized models',
- 'view_all_logs' => 'View all logs',
- ],
-
- /**
- * Settings permissions
- */
- 'settings' => [
- 'edit_settings' => 'Edit settings',
- ],
- ];
-
- /**
- * Run the database seeds.
- */
- public function run(): void
- {
- foreach ($this->data as $model => $permissions) {
- foreach ($permissions as $name => $desc) {
- $permission = Permission::firstOrCreate(['name' => $name, 'guard_name' => 'web_admin']);
- $permission->update(['description' => $desc, 'model' => $model]);
- $permission->save();
- }
- }
- }
-}
diff --git a/tests/stubs/seeders/PermissionsSeederNew.php b/tests/stubs/seeders/PermissionsSeederNew.php
deleted file mode 100644
index d4ce2f2..0000000
--- a/tests/stubs/seeders/PermissionsSeederNew.php
+++ /dev/null
@@ -1,45 +0,0 @@
- [
- 'view_categories' => 'View categories',
- 'edit_categories' => 'Edit categories',
- 'delete_categories' => 'Delete categories',
- ],
-
- /**
- * Products permissions
- */
- 'products' => [
- 'view_products' => 'View products',
- 'edit_products' => 'Edit products',
- 'delete_products' => 'Delete products',
- 'force_delete_products' => 'Permanently delete products',
- ],
-
- ];
-
- /**
- * Run the database seeds.
- */
- public function run(): void
- {
- foreach ($this->data as $model => $permissions) {
- foreach ($permissions as $name => $desc) {
- $permission = Permission::firstOrCreate(['name' => $name, 'guard_name' => 'web_admin']);
- $permission->update(['description' => $desc, 'model' => $model]);
- $permission->save();
- }
- }
- }
-}
diff --git a/tests/stubs/seeders/_authPermissions.stub b/tests/stubs/seeders/_authPermissions.stub
deleted file mode 100644
index e11be04..0000000
--- a/tests/stubs/seeders/_authPermissions.stub
+++ /dev/null
@@ -1,10 +0,0 @@
- /**
- * Customers permissions
- */
- 'customers' => [
- 'view_customers' => 'View customers',
- 'edit_customers' => 'Edit customers',
- 'delete_customers' => 'Delete customers',
- 'force_delete_customers' => 'Permanently delete customers',
- 'approve_customers' => 'Approve customers',
- ],
diff --git a/tests/stubs/seeders/_permissions.stub b/tests/stubs/seeders/_permissions.stub
deleted file mode 100644
index 313c8c9..0000000
--- a/tests/stubs/seeders/_permissions.stub
+++ /dev/null
@@ -1,8 +0,0 @@
- /**
- * Categories permissions
- */
- 'categories' => [
- 'view_categories' => 'View categories',
- 'edit_categories' => 'Edit categories',
- 'delete_categories' => 'Delete categories',
- ],
diff --git a/tests/stubs/seeders/_permissionsSoftDeletes.stub b/tests/stubs/seeders/_permissionsSoftDeletes.stub
deleted file mode 100644
index c6560f1..0000000
--- a/tests/stubs/seeders/_permissionsSoftDeletes.stub
+++ /dev/null
@@ -1,9 +0,0 @@
- /**
- * Products permissions
- */
- 'products' => [
- 'view_products' => 'View products',
- 'edit_products' => 'Edit products',
- 'delete_products' => 'Delete products',
- 'force_delete_products' => 'Permanently delete products',
- ],
diff --git a/tests/stubs/tests/Api/CategoriesControllerTest.stub b/tests/stubs/tests/Api/CategoriesControllerTest.stub
deleted file mode 100644
index e5adddc..0000000
--- a/tests/stubs/tests/Api/CategoriesControllerTest.stub
+++ /dev/null
@@ -1,78 +0,0 @@
-withoutExceptionHandling();
-
- $access_cookie = $this->getOAuthCookie(null);
-
- $categories = $this->getFactory(Category::class)->count(10)->create();
-
- $this->jsonApi('get', '/api/v1/categories', access_cookie: $access_cookie)
- ->assertSuccessful()
- ->assertJsonFragment([
- 'name' => $categories->first()->name,
- ]);
-
- }
-
- #[Test]
- public function it_can_show_api_categories_for_public_users(): void
- {
- $this->withoutExceptionHandling();
-
- $access_cookie = $this->getOAuthCookie(null);
-
- $category = $this->getFactory(Category::class)->create();
-
- $this->jsonApi('get', "/api/v1/categories/{$category->id}", access_cookie: $access_cookie)
- ->assertSuccessful()
- ->assertJsonFragment([
- 'name' => $category->name,
- ]);
- }
-
- #[Test]
- public function it_can_list_api_categories_for_admin_users(): void
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsApiAdmin('view_categories');
-
- $categories = $this->getFactory(Category::class)->count(10)->create();
-
- $this->getJson('/api/v1/categories')
- ->assertSuccessful()
- ->assertJsonFragment([
- 'name' => $categories->first()->name,
- ]);
-
- }
-
- #[Test]
- public function it_can_show_api_categories_for_admin_users(): void
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsApiAdmin('view_categories');
-
- $category = $this->getFactory(Category::class)->create();
-
- $this->getJson("/api/v1/categories/{$category->id}")
- ->assertSuccessful()
- ->assertJsonFragment([
- 'name' => $category->name,
- ]);
- }
-}
diff --git a/tests/stubs/tests/CategoriesControllerTest.stub b/tests/stubs/tests/CategoriesControllerTest.stub
deleted file mode 100644
index 3bfa0bb..0000000
--- a/tests/stubs/tests/CategoriesControllerTest.stub
+++ /dev/null
@@ -1,369 +0,0 @@
-withoutExceptionHandling();
-
- $this->actingAsAdmin('view_categories');
-
- $category = $this->getFactory(Category::class)->create();
-
- $this->adminGet('/categories')
- ->assertStatus(200)
- ->assertSee($category->name);
- }
-
- #[Test]
- public function it_can_filter_categories()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('view_categories');
-
- $category = $this->getFactory(Category::class)->create();
-
- $this->adminGet(add_query_arg([
- 'search' => $category->name,
- 'date_field' => 'created_at',
- 'date_from' => now()->subDay()->toDateTimeString(),
- 'date_to' => now()->addDay()->toDateTimeString(),
- ], '/categories'))
- ->assertStatus(200)
- ->assertSee($category->name);
- }
-
- #[Test]
- public function it_can_export_categories()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('view_categories');
-
- $category = $this->getFactory(Category::class)->create();
-
- Excel::fake();
-
- $this->adminGet(add_query_arg(['download' => 1], '/categories'))
- ->assertSuccessful();
-
- Excel::assertDownloaded('categories.xlsx', function (CategoriesExport $export) {
- return $export->modelClass() == Category::class;
- });
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_list_categories()
- {
- $this->actingAsAdmin();
-
- $category = $this->getFactory(Category::class)->create();
-
- $this->adminGet('/categories')
- ->assertStatus(403)
- ->assertDontSee($category->name);
- }
-
- #[Test]
- public function it_can_show_the_create_new_category_page()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('edit_categories');
-
- $this->adminGet('/categories/create')
- ->assertStatus(200)
- ->assertSee('New Category');
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_see_the_create_new_category_page()
- {
- $this->actingAsAdmin();
-
- $this->adminGet('/categories/create')
- ->assertStatus(403)
- ->assertDontSee('New Category');
- }
-
- #[Test]
- public function it_can_create_a_new_category()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('edit_categories');
-
- $category = $this->getFactory(Category::class)->make();
-
- $this->adminPost('/categories', [
- 'name' => $category->name,
- 'slug' => $category->slug,
- ])
- ->assertRedirect()
- ->assertSessionMissing('errors');
-
- $this->assertDatabaseHas('categories', [
- 'name' => $category->name,
- 'slug' => $category->slug,
- ]);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_add_a_category()
- {
- $this->actingAsAdmin();
-
- $category = $this->getFactory(Category::class)->make();
-
- $this->adminPost('/categories', [
- 'name' => $category->name,
- 'slug' => $category->slug,
- ])
- ->assertSessionMissing('errors')
- ->assertStatus(403);
-
- $this->assertDatabaseMissing('categories', [
- 'name' => $category->name,
- 'slug' => $category->slug,
- ]);
- }
-
- #[Test]
- public function it_can_show_the_edit_category_page()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('edit_categories');
-
- $category = $this->getFactory(Category::class)->create();
-
- $this->adminGet("/categories/{$category->id}/edit")
- ->assertStatus(200)
- ->assertSee($category->name);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_see_the_edit_category_page()
- {
- $this->actingAsAdmin();
-
- $category = $this->getFactory(Category::class)->create();
-
- $this->adminGet("/categories/{$category->id}/edit")
- ->assertStatus(403)
- ->assertDontSee($category->name);
- }
-
- #[Test]
- public function it_can_update_a_category()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('edit_categories');
-
- $category = Category::factory()->create([
- 'name' => 'foo',
- 'slug' => 'foo',
- ]);
-
- $this->adminPatch("/categories/{$category->id}", [
- 'name' => 'bar',
- 'slug' => 'bar',
- ])
- ->assertRedirect()
- ->assertSessionMissing('errors');
-
- $this->assertDatabaseHas('categories', [
- 'id' => $category->id,
- 'name' => 'bar',
- 'slug' => 'bar',
- ]);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_update_a_category()
- {
- $this->actingAsAdmin();
-
- $category = Category::factory()->create([
- 'name' => 'foo',
- 'slug' => 'foo',
- ]);
-
- $this->adminPatch("/categories/{$category->id}", [
- 'name' => 'bar',
- 'slug' => 'bar',
- ])
- ->assertSessionMissing('errors')
- ->assertStatus(403);
-
- $this->assertDatabaseHas('categories', [
- 'id' => $category->id,
- 'name' => 'foo',
- 'slug' => 'foo',
- ]);
- }
-
- #[Test]
- public function it_can_show_a_category()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('view_categories');
-
- $category = $this->getFactory(Category::class)->create();
-
- $this->adminGet("/categories/{$category->id}")
- ->assertStatus(200)
- ->assertSee($category->name);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_view_a_category()
- {
- $this->actingAsAdmin();
-
- $category = $this->getFactory(Category::class)->create();
-
- $this->adminGet("/categories/{$category->id}")
- ->assertStatus(403)
- ->assertDontSee($category->name);
- }
-
- #[Test]
- public function it_can_delete_a_category()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('delete_categories');
-
- $category = $this->getFactory(Category::class)->create();
-
- $this->assertDatabaseHas('categories', [
- 'id' => $category->id,
- ]);
-
- $this->adminDelete("/categories/{$category->id}")
- ->assertRedirect();
-
- $this->assertDatabaseMissing('categories', [
- 'id' => $category->id,
- ]);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_delete_a_category()
- {
- $this->actingAsAdmin();
-
- $category = $this->getFactory(Category::class)->create();
-
- $this->assertDatabaseHas('categories', [
- 'id' => $category->id,
- ]);
-
- $this->adminDelete("/categories/{$category->id}")
- ->assertStatus(403);
-
- $this->assertDatabaseHas('categories', [
- 'id' => $category->id,
- ]);
- }
-
- #[Test]
- public function it_can_bulk_delete_categories()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin(['view_categories', 'delete_categories']);
-
- $categories = $this->getFactory(Category::class)->count(3)->create();
-
- $categories = $categories->map(function ($category) {
- return collect($category->toArray())
- ->only('id')
- ->all();
- });
-
- $categories->each(function ($category) {
- $this->assertDatabaseHas('categories', $category);
- });
-
- $categories_to_delete = $categories->slice(1);
-
- $this->adminPatch('/categories', [
- 'categories' => $categories_to_delete->pluck('id')->all(),
- 'action' => 'delete',
- ])
- ->assertRedirect()
- ->assertSessionMissing('errors');
-
- $categories_to_delete->each(function ($category) {
- $this->assertDatabaseMissing('categories', $category);
- });
- }
-
- #[Test]
- public function it_does_not_allow_unauthorized_users_to_bulk_delete_categories()
- {
- $this->actingAsAdmin('view_categories');
-
- $categories = $this->getFactory(Category::class)->count(3)->create();
-
- $categories = $categories->map(function ($category) {
- return collect($category->toArray())
- ->only('id')
- ->all();
- });
-
- $categories->each(function ($category) {
- $this->assertDatabaseHas('categories', $category);
- });
-
- $categories_to_delete = $categories->slice(1);
-
- $this->adminPatch('/categories', [
- 'categories' => $categories_to_delete->pluck('id')->all(),
- 'action' => 'delete',
- ])
- ->assertStatus(403)
- ->assertSessionMissing('errors');
-
- $categories_to_delete->each(function ($category) {
- $this->assertDatabaseHas('categories', $category);
- });
- }
-
- #[Test]
- public function it_can_validate_category_inputs()
- {
- $this->actingAsAdmin('edit_categories');
-
- $this->adminPost('/categories', [
- 'name' => [],
- 'slug' => [],
- ])
- ->assertRedirect()
- ->assertSessionHasErrors([
- 'name',
- 'slug',
- ]);
-
- $this->assertDatabaseMissing('categories', [
- 'name' => [],
- 'slug' => [],
- ]);
- }
-}
diff --git a/tests/stubs/tests/OrdersControllerTest.stub b/tests/stubs/tests/OrdersControllerTest.stub
deleted file mode 100644
index 5ff2847..0000000
--- a/tests/stubs/tests/OrdersControllerTest.stub
+++ /dev/null
@@ -1,410 +0,0 @@
-withoutExceptionHandling();
-
- $this->actingAsAdmin('view_orders');
-
- $order = $this->getFactory(Order::class)->create();
-
- $this->adminGet('/orders')
- ->assertStatus(200)
- ->assertSee($order->order_no);
- }
-
- #[Test]
- public function it_can_filter_orders()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('view_orders');
-
- $order = $this->getFactory(Order::class)->create();
-
- $this->adminGet(add_query_arg([
- 'search' => $order->order_no,
- 'date_field' => 'created_at',
- 'date_from' => now()->subDay()->toDateTimeString(),
- 'date_to' => now()->addDay()->toDateTimeString(),
- ], '/orders'))
- ->assertStatus(200)
- ->assertSee($order->order_no);
- }
-
- #[Test]
- public function it_can_export_orders()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('view_orders');
-
- $order = $this->getFactory(Order::class)->create();
-
- Excel::fake();
-
- $this->adminGet(add_query_arg(['download' => 1], '/orders'))
- ->assertSuccessful();
-
- Excel::assertDownloaded('orders.xlsx', function (OrdersExport $export) {
- return $export->modelClass() == Order::class;
- });
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_list_orders()
- {
- $this->actingAsAdmin();
-
- $order = $this->getFactory(Order::class)->create();
-
- $this->adminGet('/orders')
- ->assertStatus(403)
- ->assertDontSee($order->order_no);
- }
-
- #[Test]
- public function it_can_show_the_create_new_order_page()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('edit_orders');
-
- $this->adminGet('/orders/create')
- ->assertStatus(200)
- ->assertSee('New Order');
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_see_the_create_new_order_page()
- {
- $this->actingAsAdmin();
-
- $this->adminGet('/orders/create')
- ->assertStatus(403)
- ->assertDontSee('New Order');
- }
-
- #[Test]
- public function it_can_create_a_new_order()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('edit_orders');
-
- $order = $this->getFactory(Order::class)->make();
-
- $this->adminPost('/orders', [
- 'order_no' => $order->order_no,
- 'category' => $order->category_id,
- 'product_slug' => $order->product_slug,
- 'status' => $order->status->value,
- ])
- ->assertRedirect()
- ->assertSessionMissing('errors');
-
- $this->assertDatabaseHas('orders', [
- 'order_no' => $order->order_no,
- 'category_id' => $order->category_id,
- 'product_slug' => $order->product_slug,
- 'status' => $order->status->value,
- ]);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_add_a_order()
- {
- $this->actingAsAdmin();
-
- $order = $this->getFactory(Order::class)->make();
-
- $this->adminPost('/orders', [
- 'order_no' => $order->order_no,
- 'category' => $order->category_id,
- 'product_slug' => $order->product_slug,
- 'status' => $order->status->value,
- ])
- ->assertSessionMissing('errors')
- ->assertStatus(403);
-
- $this->assertDatabaseMissing('orders', [
- 'order_no' => $order->order_no,
- 'category_id' => $order->category_id,
- 'product_slug' => $order->product_slug,
- 'status' => $order->status->value,
- ]);
- }
-
- #[Test]
- public function it_can_show_the_edit_order_page()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('edit_orders');
-
- $order = $this->getFactory(Order::class)->create();
-
- $this->adminGet("/orders/{$order->id}/edit")
- ->assertStatus(200)
- ->assertSee($order->order_no);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_see_the_edit_order_page()
- {
- $this->actingAsAdmin();
-
- $order = $this->getFactory(Order::class)->create();
-
- $this->adminGet("/orders/{$order->id}/edit")
- ->assertStatus(403)
- ->assertDontSee($order->order_no);
- }
-
- #[Test]
- public function it_can_update_a_order()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('edit_orders');
-
- $old_category = $this->getFactory(Category::class)->create();
- $old_product_slug = $this->getFactory(Product::class)->create();
-
- $order = Order::factory()->create([
- 'order_no' => 'foo',
- 'category_id' => $old_category->id,
- 'product_slug' => $old_product_slug->slug,
- 'status' => OrderStatuses::Pending->value,
- ]);
-
- $new_category = $this->getFactory(Category::class)->create();
- $new_product_slug = $this->getFactory(Product::class)->create();
-
- $this->adminPatch("/orders/{$order->id}", [
- 'order_no' => 'bar',
- 'category' => $new_category->id,
- 'product_slug' => $new_product_slug->slug,
- 'status' => OrderStatuses::Paid->value,
- ])
- ->assertRedirect()
- ->assertSessionMissing('errors');
-
- $this->assertDatabaseHas('orders', [
- 'id' => $order->id,
- 'order_no' => 'bar',
- 'category_id' => $new_category->id,
- 'product_slug' => $new_product_slug->slug,
- 'status' => OrderStatuses::Paid->value,
- ]);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_update_a_order()
- {
- $this->actingAsAdmin();
-
- $old_category = $this->getFactory(Category::class)->create();
- $old_product_slug = $this->getFactory(Product::class)->create();
-
- $order = Order::factory()->create([
- 'order_no' => 'foo',
- 'category_id' => $old_category->id,
- 'product_slug' => $old_product_slug->slug,
- 'status' => OrderStatuses::Pending->value,
- ]);
-
- $new_category = $this->getFactory(Category::class)->create();
- $new_product_slug = $this->getFactory(Product::class)->create();
-
- $this->adminPatch("/orders/{$order->id}", [
- 'order_no' => 'bar',
- 'category' => $new_category->id,
- 'product_slug' => $new_product_slug->slug,
- 'status' => OrderStatuses::Paid->value,
- ])
- ->assertSessionMissing('errors')
- ->assertStatus(403);
-
- $this->assertDatabaseHas('orders', [
- 'id' => $order->id,
- 'order_no' => 'foo',
- 'category_id' => $old_category->id,
- 'product_slug' => $old_product_slug->slug,
- 'status' => OrderStatuses::Pending->value,
- ]);
- }
-
- #[Test]
- public function it_can_show_a_order()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('view_orders');
-
- $order = $this->getFactory(Order::class)->create();
-
- $this->adminGet("/orders/{$order->id}")
- ->assertStatus(200)
- ->assertSee($order->order_no);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_view_a_order()
- {
- $this->actingAsAdmin();
-
- $order = $this->getFactory(Order::class)->create();
-
- $this->adminGet("/orders/{$order->id}")
- ->assertStatus(403)
- ->assertDontSee($order->order_no);
- }
-
- #[Test]
- public function it_can_delete_a_order()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('delete_orders');
-
- $order = $this->getFactory(Order::class)->create();
-
- $this->assertDatabaseHas('orders', [
- 'id' => $order->id,
- ]);
-
- $this->adminDelete("/orders/{$order->id}")
- ->assertRedirect();
-
- $this->assertDatabaseMissing('orders', [
- 'id' => $order->id,
- ]);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_delete_a_order()
- {
- $this->actingAsAdmin();
-
- $order = $this->getFactory(Order::class)->create();
-
- $this->assertDatabaseHas('orders', [
- 'id' => $order->id,
- ]);
-
- $this->adminDelete("/orders/{$order->id}")
- ->assertStatus(403);
-
- $this->assertDatabaseHas('orders', [
- 'id' => $order->id,
- ]);
- }
-
- #[Test]
- public function it_can_bulk_delete_orders()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin(['view_orders', 'delete_orders']);
-
- $orders = $this->getFactory(Order::class)->count(3)->create();
-
- $orders = $orders->map(function ($order) {
- return collect($order->toArray())
- ->only('id')
- ->all();
- });
-
- $orders->each(function ($order) {
- $this->assertDatabaseHas('orders', $order);
- });
-
- $orders_to_delete = $orders->slice(1);
-
- $this->adminPatch('/orders', [
- 'orders' => $orders_to_delete->pluck('id')->all(),
- 'action' => 'delete',
- ])
- ->assertRedirect()
- ->assertSessionMissing('errors');
-
- $orders_to_delete->each(function ($order) {
- $this->assertDatabaseMissing('orders', $order);
- });
- }
-
- #[Test]
- public function it_does_not_allow_unauthorized_users_to_bulk_delete_orders()
- {
- $this->actingAsAdmin('view_orders');
-
- $orders = $this->getFactory(Order::class)->count(3)->create();
-
- $orders = $orders->map(function ($order) {
- return collect($order->toArray())
- ->only('id')
- ->all();
- });
-
- $orders->each(function ($order) {
- $this->assertDatabaseHas('orders', $order);
- });
-
- $orders_to_delete = $orders->slice(1);
-
- $this->adminPatch('/orders', [
- 'orders' => $orders_to_delete->pluck('id')->all(),
- 'action' => 'delete',
- ])
- ->assertStatus(403)
- ->assertSessionMissing('errors');
-
- $orders_to_delete->each(function ($order) {
- $this->assertDatabaseHas('orders', $order);
- });
- }
-
- #[Test]
- public function it_can_validate_order_inputs()
- {
- $this->actingAsAdmin('edit_orders');
-
- $this->adminPost('/orders', [
- 'order_no' => [],
- 'category' => -1,
- 'product_slug' => -1,
- 'status' => [],
- ])
- ->assertRedirect()
- ->assertSessionHasErrors([
- 'order_no',
- 'category',
- 'product_slug',
- 'status',
- ]);
-
- $this->assertDatabaseMissing('orders', [
- 'order_no' => [],
- 'category_id' => -1,
- 'product_slug' => -1,
- 'status' => [],
- ]);
- }
-}
diff --git a/tests/stubs/tests/ProductsControllerTest.stub b/tests/stubs/tests/ProductsControllerTest.stub
deleted file mode 100644
index 0c65907..0000000
--- a/tests/stubs/tests/ProductsControllerTest.stub
+++ /dev/null
@@ -1,674 +0,0 @@
-withoutExceptionHandling();
-
- $this->actingAsAdmin('view_products');
-
- $product = $this->getFactory(Product::class)->create();
-
- $this->adminGet('/products')
- ->assertStatus(200)
- ->assertSee($product->name);
- }
-
- #[Test]
- public function it_can_filter_products()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('view_products');
-
- $product = $this->getFactory(Product::class)->create();
-
- $this->adminGet(add_query_arg([
- 'search' => $product->name,
- 'date_field' => 'created_at',
- 'date_from' => now()->subDay()->toDateTimeString(),
- 'date_to' => now()->addDay()->toDateTimeString(),
- ], '/products'))
- ->assertStatus(200)
- ->assertSee($product->name);
- }
-
- #[Test]
- public function it_can_export_products()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('view_products');
-
- $product = $this->getFactory(Product::class)->create();
-
- Excel::fake();
-
- $this->adminGet(add_query_arg(['download' => 1], '/products'))
- ->assertSuccessful();
-
- Excel::assertDownloaded('products.xlsx', function (ProductsExport $export) {
- return $export->modelClass() == Product::class;
- });
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_list_products()
- {
- $this->actingAsAdmin();
-
- $product = $this->getFactory(Product::class)->create();
-
- $this->adminGet('/products')
- ->assertStatus(403)
- ->assertDontSee($product->name);
- }
-
- #[Test]
- public function it_can_show_the_create_new_product_page()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('edit_products');
-
- $this->adminGet('/products/create')
- ->assertStatus(200)
- ->assertSee('New Product');
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_see_the_create_new_product_page()
- {
- $this->actingAsAdmin();
-
- $this->adminGet('/products/create')
- ->assertStatus(403)
- ->assertDontSee('New Product');
- }
-
- #[Test]
- public function it_can_create_a_new_product()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('edit_products');
-
- $product = $this->getFactory(Product::class)->make();
-
- $this->adminPost('/products', [
- 'name' => $product->name,
- 'address' => $product->address,
- 'slug' => $product->slug,
- 'description' => $product->description,
- 'price' => $product->price,
- 'stock' => $product->stock,
- 'on_sale' => $product->on_sale,
- 'features' => $product->features,
- 'published_at' => $product->published_at,
- 'expire_at' => $product->expire_at,
- 'released_on' => $product->released_on,
- 'sale_time' => $product->sale_time,
- 'status' => $product->status,
- 'category' => $product->category_id,
- 'manufactured_year' => $product->manufactured_year,
- ])
- ->assertRedirect()
- ->assertSessionMissing('errors');
-
- $this->assertDatabaseHas('products', [
- 'name' => $product->name,
- 'address' => $product->address,
- 'slug' => $product->slug,
- 'description' => $product->description,
- 'price' => $product->price,
- 'stock' => $product->stock,
- 'on_sale' => $product->on_sale,
- 'features' => is_null($product->features) ? null : json_encode($product->features),
- 'published_at' => $product->published_at,
- 'expire_at' => $product->expire_at,
- 'released_on' => $product->released_on,
- 'sale_time' => $product->sale_time,
- 'status' => $product->status,
- 'category_id' => $product->category_id,
- 'manufactured_year' => $product->manufactured_year,
- ]);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_add_a_product()
- {
- $this->actingAsAdmin();
-
- $product = $this->getFactory(Product::class)->make();
-
- $this->adminPost('/products', [
- 'name' => $product->name,
- 'address' => $product->address,
- 'slug' => $product->slug,
- 'description' => $product->description,
- 'price' => $product->price,
- 'stock' => $product->stock,
- 'on_sale' => $product->on_sale,
- 'features' => $product->features,
- 'published_at' => $product->published_at,
- 'expire_at' => $product->expire_at,
- 'released_on' => $product->released_on,
- 'sale_time' => $product->sale_time,
- 'status' => $product->status,
- 'category' => $product->category_id,
- 'manufactured_year' => $product->manufactured_year,
- ])
- ->assertSessionMissing('errors')
- ->assertStatus(403);
-
- $this->assertDatabaseMissing('products', [
- 'name' => $product->name,
- 'address' => $product->address,
- 'slug' => $product->slug,
- 'description' => $product->description,
- 'price' => $product->price,
- 'stock' => $product->stock,
- 'on_sale' => $product->on_sale,
- 'features' => is_null($product->features) ? null : json_encode($product->features),
- 'published_at' => $product->published_at,
- 'expire_at' => $product->expire_at,
- 'released_on' => $product->released_on,
- 'sale_time' => $product->sale_time,
- 'status' => $product->status,
- 'category_id' => $product->category_id,
- 'manufactured_year' => $product->manufactured_year,
- ]);
- }
-
- #[Test]
- public function it_can_show_the_edit_product_page()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('edit_products');
-
- $product = $this->getFactory(Product::class)->create();
-
- $this->adminGet("/products/{$product->id}/edit")
- ->assertStatus(200)
- ->assertSee($product->name);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_see_the_edit_product_page()
- {
- $this->actingAsAdmin();
-
- $product = $this->getFactory(Product::class)->create();
-
- $this->adminGet("/products/{$product->id}/edit")
- ->assertStatus(403)
- ->assertDontSee($product->name);
- }
-
- #[Test]
- public function it_can_update_a_product()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('edit_products');
-
- $old_category = $this->getFactory(Category::class)->create();
-
- $product = Product::factory()->create([
- 'name' => 'foo',
- 'address' => 'foo',
- 'slug' => 'foo',
- 'description' => 'Lorem ipsum',
- 'price' => 10.50,
- 'stock' => 1,
- 'on_sale' => true,
- 'features' => ['apple'],
- 'published_at' => '2024-02-12 14:54:00',
- 'expire_at' => '2024-02-12 14:54:00',
- 'released_on' => '2024-02-12',
- 'sale_time' => '14:54:00',
- 'status' => 'draft',
- 'category_id' => $old_category->id,
- 'manufactured_year' => 2024,
- ]);
-
- $new_category = $this->getFactory(Category::class)->create();
-
- $this->adminPatch("/products/{$product->id}", [
- 'name' => 'bar',
- 'address' => 'bar',
- 'slug' => 'bar',
- 'description' => 'Itsu bitsum',
- 'price' => 5.24,
- 'stock' => 2,
- 'on_sale' => false,
- 'features' => ['orange'],
- 'published_at' => '2023-01-11 13:30:00',
- 'expire_at' => '2023-01-11 13:30:00',
- 'released_on' => '2023-01-11',
- 'sale_time' => '13:30:00',
- 'status' => 'published',
- 'category' => $new_category->id,
- 'manufactured_year' => 2023,
- ])
- ->assertRedirect()
- ->assertSessionMissing('errors');
-
- $this->assertDatabaseHas('products', [
- 'id' => $product->id,
- 'name' => 'bar',
- 'address' => 'bar',
- 'slug' => 'bar',
- 'description' => 'Itsu bitsum',
- 'price' => 5.24,
- 'stock' => 2,
- 'on_sale' => false,
- 'features' => json_encode(['orange']),
- 'published_at' => '2023-01-11 13:30:00',
- 'expire_at' => '2023-01-11 13:30:00',
- 'released_on' => '2023-01-11',
- 'sale_time' => '13:30:00',
- 'status' => 'published',
- 'category_id' => $new_category->id,
- 'manufactured_year' => 2023,
- ]);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_update_a_product()
- {
- $this->actingAsAdmin();
-
- $old_category = $this->getFactory(Category::class)->create();
-
- $product = Product::factory()->create([
- 'name' => 'foo',
- 'address' => 'foo',
- 'slug' => 'foo',
- 'description' => 'Lorem ipsum',
- 'price' => 10.50,
- 'stock' => 1,
- 'on_sale' => true,
- 'features' => ['apple'],
- 'published_at' => '2024-02-12 14:54:00',
- 'expire_at' => '2024-02-12 14:54:00',
- 'released_on' => '2024-02-12',
- 'sale_time' => '14:54:00',
- 'status' => 'draft',
- 'category_id' => $old_category->id,
- 'manufactured_year' => 2024,
- ]);
-
- $new_category = $this->getFactory(Category::class)->create();
-
- $this->adminPatch("/products/{$product->id}", [
- 'name' => 'bar',
- 'address' => 'bar',
- 'slug' => 'bar',
- 'description' => 'Itsu bitsum',
- 'price' => 5.24,
- 'stock' => 2,
- 'on_sale' => false,
- 'features' => ['orange'],
- 'published_at' => '2023-01-11 13:30:00',
- 'expire_at' => '2023-01-11 13:30:00',
- 'released_on' => '2023-01-11',
- 'sale_time' => '13:30:00',
- 'status' => 'published',
- 'category' => $new_category->id,
- 'manufactured_year' => 2023,
- ])
- ->assertSessionMissing('errors')
- ->assertStatus(403);
-
- $this->assertDatabaseHas('products', [
- 'id' => $product->id,
- 'name' => 'foo',
- 'address' => 'foo',
- 'slug' => 'foo',
- 'description' => 'Lorem ipsum',
- 'price' => 10.50,
- 'stock' => 1,
- 'on_sale' => true,
- 'features' => json_encode(['apple']),
- 'published_at' => '2024-02-12 14:54:00',
- 'expire_at' => '2024-02-12 14:54:00',
- 'released_on' => '2024-02-12',
- 'sale_time' => '14:54:00',
- 'status' => 'draft',
- 'category_id' => $old_category->id,
- 'manufactured_year' => 2024,
- ]);
- }
-
- #[Test]
- public function it_can_show_a_product()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('view_products');
-
- $product = $this->getFactory(Product::class)->create();
-
- $this->adminGet("/products/{$product->id}")
- ->assertStatus(200)
- ->assertSee($product->name);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_view_a_product()
- {
- $this->actingAsAdmin();
-
- $product = $this->getFactory(Product::class)->create();
-
- $this->adminGet("/products/{$product->id}")
- ->assertStatus(403)
- ->assertDontSee($product->name);
- }
-
- #[Test]
- public function it_can_soft_delete_a_product()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('delete_products');
-
- $product = $this->getFactory(Product::class)->create();
-
- $this->assertDatabaseHas('products', [
- 'id' => $product->id,
- ]);
-
- $this->adminDelete("/products/{$product->id}")
- ->assertRedirect();
-
- $this->assertSoftDeleted('products', [
- 'id' => $product->id,
- ]);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_soft_delete_a_product()
- {
- $this->actingAsAdmin();
-
- $product = $this->getFactory(Product::class)->create();
-
- $this->assertDatabaseHas('products', [
- 'id' => $product->id,
- ]);
-
- $this->adminDelete("/products/{$product->id}")
- ->assertStatus(403);
-
- $this->assertDatabaseHas('products', [
- 'id' => $product->id,
- ]);
- }
-
- #[Test]
- public function it_can_force_delete_a_product()
- {
- $this->withoutExceptionHandling();
-
- $product = $this->getFactory(Product::class)->create();
- $product->delete();
-
- // sanity check
- $this->assertSoftDeleted('products', [
- 'id' => $product->id,
- ]);
-
- $this->actingAsAdmin('force_delete_products');
-
- $this->adminDelete("/products/{$product->id}/force-delete")
- ->assertRedirect();
-
- $this->assertDatabaseMissing('products', [
- 'id' => $product->id,
- ]);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_force_delete_a_product()
- {
- $product = $this->getFactory(Product::class)->create();
- $product->delete();
-
- // sanity check
- $this->assertSoftDeleted('products', [
- 'id' => $product->id,
- ]);
-
- $this->actingAsAdmin();
-
- $this->adminDelete("/products/{$product->id}/force-delete")
- ->assertStatus(403);
-
- $this->assertSoftDeleted('products', [
- 'id' => $product->id,
- ]);
- }
-
- #[Test]
- public function it_can_restore_a_soft_deleted_product()
- {
- $this->withoutExceptionHandling();
-
- $product = $this->getFactory(Product::class)->create();
- $product->delete();
-
- // sanity check
- $this->assertSoftDeleted('products', [
- 'id' => $product->id,
- ]);
-
- $this->actingAsAdmin('delete_products');
-
- $this->adminPost("/products/{$product->id}/restore")
- ->assertRedirect();
-
- $this->assertDatabaseHas('products', [
- 'id' => $product->id,
- 'deleted_at' => null,
- ]);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_restore_a_product()
- {
- $product = $this->getFactory(Product::class)->create();
- $product->delete();
-
- // sanity check
- $this->assertSoftDeleted('products', [
- 'id' => $product->id,
- ]);
-
- $this->actingAsAdmin();
-
- $this->adminPost("/products/{$product->id}/restore")
- ->assertStatus(403);
-
- $this->assertSoftDeleted('products', [
- 'id' => $product->id,
- ]);
- }
-
- #[Test]
- public function it_can_list_soft_deleted_products()
- {
- $this->withoutExceptionHandling();
-
- $product = $this->getFactory(Product::class)->create();
- $product->delete();
-
- // sanity check
- $this->assertSoftDeleted('products', [
- 'id' => $product->id,
- ]);
-
- $this->actingAsAdmin('delete_products');
-
- $this->adminGet("/products/trash")
- ->assertStatus(200)
- ->assertSee($product->name);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_list_soft_deleted_products()
- {
- $product = $this->getFactory(Product::class)->create();
- $product->delete();
-
- // sanity check
- $this->assertSoftDeleted('products', [
- 'id' => $product->id,
- ]);
-
- $this->actingAsAdmin();
-
- $this->adminGet("/products/trash")
- ->assertStatus(403)
- ->assertDontSee($product->name);
- }
-
- #[Test]
- public function it_can_bulk_soft_delete_products()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin(['view_products', 'delete_products']);
-
- $products = $this->getFactory(Product::class)->count(3)->create();
-
- $products = $products->map(function ($product) {
- return collect($product->toArray())
- ->only('id')
- ->all();
- });
-
- $products->each(function ($product) {
- $this->assertDatabaseHas('products', $product);
- });
-
- $products_to_delete = $products->slice(1);
-
- $this->adminPatch('/products', [
- 'products' => $products_to_delete->pluck('id')->all(),
- 'action' => 'delete',
- ])
- ->assertRedirect()
- ->assertSessionMissing('errors');
-
- $products_to_delete->each(function ($product) {
- $this->assertSoftDeleted('products', $product);
- });
- }
-
- #[Test]
- public function it_does_not_allow_unauthorized_users_to_bulk_soft_delete_products()
- {
- $this->actingAsAdmin('view_products');
-
- $products = $this->getFactory(Product::class)->count(3)->create();
-
- $products = $products->map(function ($product) {
- return collect($product->toArray())
- ->only('id')
- ->all();
- });
-
- $products->each(function ($product) {
- $this->assertDatabaseHas('products', $product);
- });
-
- $products_to_delete = $products->slice(1);
-
- $this->adminPatch('/products', [
- 'products' => $products_to_delete->pluck('id')->all(),
- 'action' => 'delete',
- ])
- ->assertStatus(403)
- ->assertSessionMissing('errors');
-
- $products_to_delete->each(function ($product) {
- $this->assertDatabaseHas('products', $product);
- });
- }
-
- #[Test]
- public function it_can_validate_product_inputs()
- {
- $this->actingAsAdmin('edit_products');
-
- $this->adminPost('/products', [
- 'name' => [],
- 'address' => [],
- 'slug' => [],
- 'description' => [],
- 'price' => 'foo',
- 'stock' => 'foo',
- 'on_sale' => 'foo',
- 'features' => 'foo',
- 'published_at' => 'foo',
- 'expire_at' => 'foo',
- 'released_on' => 'foo',
- 'sale_time' => 'foo',
- 'status' => [],
- 'category' => -1,
- 'manufactured_year' => -1,
- ])
- ->assertRedirect()
- ->assertSessionHasErrors([
- 'name',
- 'address',
- 'slug',
- 'description',
- 'price',
- 'stock',
- 'on_sale',
- 'features',
- 'published_at',
- 'expire_at',
- 'released_on',
- 'sale_time',
- 'status',
- 'category',
- 'manufactured_year',
- ]);
-
- $this->assertDatabaseMissing('products', [
- 'name' => [],
- 'address' => [],
- 'slug' => [],
- 'description' => [],
- 'price' => 'foo',
- 'stock' => 'foo',
- 'on_sale' => 'foo',
- 'features' => json_encode('foo'),
- 'published_at' => 'foo',
- 'expire_at' => 'foo',
- 'released_on' => 'foo',
- 'sale_time' => 'foo',
- 'status' => [],
- 'category_id' => -1,
- 'manufactured_year' => -1,
- ]);
- }
-}
diff --git a/tests/stubs/tests/SingleValueEnumOrdersControllerTest.stub b/tests/stubs/tests/SingleValueEnumOrdersControllerTest.stub
deleted file mode 100644
index 248ade0..0000000
--- a/tests/stubs/tests/SingleValueEnumOrdersControllerTest.stub
+++ /dev/null
@@ -1,410 +0,0 @@
-withoutExceptionHandling();
-
- $this->actingAsAdmin('view_single_value_enum_orders');
-
- $single_value_enum_order = $this->getFactory(SingleValueEnumOrder::class)->create();
-
- $this->adminGet('/single-value-enum-orders')
- ->assertStatus(200)
- ->assertSee($single_value_enum_order->order_no);
- }
-
- #[Test]
- public function it_can_filter_single_value_enum_orders()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('view_single_value_enum_orders');
-
- $single_value_enum_order = $this->getFactory(SingleValueEnumOrder::class)->create();
-
- $this->adminGet(add_query_arg([
- 'search' => $single_value_enum_order->order_no,
- 'date_field' => 'created_at',
- 'date_from' => now()->subDay()->toDateTimeString(),
- 'date_to' => now()->addDay()->toDateTimeString(),
- ], '/single-value-enum-orders'))
- ->assertStatus(200)
- ->assertSee($single_value_enum_order->order_no);
- }
-
- #[Test]
- public function it_can_export_single_value_enum_orders()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('view_single_value_enum_orders');
-
- $single_value_enum_order = $this->getFactory(SingleValueEnumOrder::class)->create();
-
- Excel::fake();
-
- $this->adminGet(add_query_arg(['download' => 1], '/single-value-enum-orders'))
- ->assertSuccessful();
-
- Excel::assertDownloaded('single-value-enum-orders.xlsx', function (SingleValueEnumOrdersExport $export) {
- return $export->modelClass() == SingleValueEnumOrder::class;
- });
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_list_single_value_enum_orders()
- {
- $this->actingAsAdmin();
-
- $single_value_enum_order = $this->getFactory(SingleValueEnumOrder::class)->create();
-
- $this->adminGet('/single-value-enum-orders')
- ->assertStatus(403)
- ->assertDontSee($single_value_enum_order->order_no);
- }
-
- #[Test]
- public function it_can_show_the_create_new_single_value_enum_order_page()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('edit_single_value_enum_orders');
-
- $this->adminGet('/single-value-enum-orders/create')
- ->assertStatus(200)
- ->assertSee('New Single Value Enum Order');
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_see_the_create_new_single_value_enum_order_page()
- {
- $this->actingAsAdmin();
-
- $this->adminGet('/single-value-enum-orders/create')
- ->assertStatus(403)
- ->assertDontSee('New Single Value Enum Order');
- }
-
- #[Test]
- public function it_can_create_a_new_single_value_enum_order()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('edit_single_value_enum_orders');
-
- $single_value_enum_order = $this->getFactory(SingleValueEnumOrder::class)->make();
-
- $this->adminPost('/single-value-enum-orders', [
- 'order_no' => $single_value_enum_order->order_no,
- 'category' => $single_value_enum_order->category_id,
- 'product_slug' => $single_value_enum_order->product_slug,
- 'status' => $single_value_enum_order->status->value,
- ])
- ->assertRedirect()
- ->assertSessionMissing('errors');
-
- $this->assertDatabaseHas('single_value_enum_orders', [
- 'order_no' => $single_value_enum_order->order_no,
- 'category_id' => $single_value_enum_order->category_id,
- 'product_slug' => $single_value_enum_order->product_slug,
- 'status' => $single_value_enum_order->status->value,
- ]);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_add_a_single_value_enum_order()
- {
- $this->actingAsAdmin();
-
- $single_value_enum_order = $this->getFactory(SingleValueEnumOrder::class)->make();
-
- $this->adminPost('/single-value-enum-orders', [
- 'order_no' => $single_value_enum_order->order_no,
- 'category' => $single_value_enum_order->category_id,
- 'product_slug' => $single_value_enum_order->product_slug,
- 'status' => $single_value_enum_order->status->value,
- ])
- ->assertSessionMissing('errors')
- ->assertStatus(403);
-
- $this->assertDatabaseMissing('single_value_enum_orders', [
- 'order_no' => $single_value_enum_order->order_no,
- 'category_id' => $single_value_enum_order->category_id,
- 'product_slug' => $single_value_enum_order->product_slug,
- 'status' => $single_value_enum_order->status->value,
- ]);
- }
-
- #[Test]
- public function it_can_show_the_edit_single_value_enum_order_page()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('edit_single_value_enum_orders');
-
- $single_value_enum_order = $this->getFactory(SingleValueEnumOrder::class)->create();
-
- $this->adminGet("/single-value-enum-orders/{$single_value_enum_order->id}/edit")
- ->assertStatus(200)
- ->assertSee($single_value_enum_order->order_no);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_see_the_edit_single_value_enum_order_page()
- {
- $this->actingAsAdmin();
-
- $single_value_enum_order = $this->getFactory(SingleValueEnumOrder::class)->create();
-
- $this->adminGet("/single-value-enum-orders/{$single_value_enum_order->id}/edit")
- ->assertStatus(403)
- ->assertDontSee($single_value_enum_order->order_no);
- }
-
- #[Test]
- public function it_can_update_a_single_value_enum_order()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('edit_single_value_enum_orders');
-
- $old_category = $this->getFactory(Category::class)->create();
- $old_product_slug = $this->getFactory(Product::class)->create();
-
- $single_value_enum_order = SingleValueEnumOrder::factory()->create([
- 'order_no' => 'foo',
- 'category_id' => $old_category->id,
- 'product_slug' => $old_product_slug->slug,
- 'status' => SingleCaseOrderStatuses::Pending->value,
- ]);
-
- $new_category = $this->getFactory(Category::class)->create();
- $new_product_slug = $this->getFactory(Product::class)->create();
-
- $this->adminPatch("/single-value-enum-orders/{$single_value_enum_order->id}", [
- 'order_no' => 'bar',
- 'category' => $new_category->id,
- 'product_slug' => $new_product_slug->slug,
- 'status' => SingleCaseOrderStatuses::Pending->value,
- ])
- ->assertRedirect()
- ->assertSessionMissing('errors');
-
- $this->assertDatabaseHas('single_value_enum_orders', [
- 'id' => $single_value_enum_order->id,
- 'order_no' => 'bar',
- 'category_id' => $new_category->id,
- 'product_slug' => $new_product_slug->slug,
- 'status' => SingleCaseOrderStatuses::Pending->value,
- ]);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_update_a_single_value_enum_order()
- {
- $this->actingAsAdmin();
-
- $old_category = $this->getFactory(Category::class)->create();
- $old_product_slug = $this->getFactory(Product::class)->create();
-
- $single_value_enum_order = SingleValueEnumOrder::factory()->create([
- 'order_no' => 'foo',
- 'category_id' => $old_category->id,
- 'product_slug' => $old_product_slug->slug,
- 'status' => SingleCaseOrderStatuses::Pending->value,
- ]);
-
- $new_category = $this->getFactory(Category::class)->create();
- $new_product_slug = $this->getFactory(Product::class)->create();
-
- $this->adminPatch("/single-value-enum-orders/{$single_value_enum_order->id}", [
- 'order_no' => 'bar',
- 'category' => $new_category->id,
- 'product_slug' => $new_product_slug->slug,
- 'status' => SingleCaseOrderStatuses::Pending->value,
- ])
- ->assertSessionMissing('errors')
- ->assertStatus(403);
-
- $this->assertDatabaseHas('single_value_enum_orders', [
- 'id' => $single_value_enum_order->id,
- 'order_no' => 'foo',
- 'category_id' => $old_category->id,
- 'product_slug' => $old_product_slug->slug,
- 'status' => SingleCaseOrderStatuses::Pending->value,
- ]);
- }
-
- #[Test]
- public function it_can_show_a_single_value_enum_order()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('view_single_value_enum_orders');
-
- $single_value_enum_order = $this->getFactory(SingleValueEnumOrder::class)->create();
-
- $this->adminGet("/single-value-enum-orders/{$single_value_enum_order->id}")
- ->assertStatus(200)
- ->assertSee($single_value_enum_order->order_no);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_view_a_single_value_enum_order()
- {
- $this->actingAsAdmin();
-
- $single_value_enum_order = $this->getFactory(SingleValueEnumOrder::class)->create();
-
- $this->adminGet("/single-value-enum-orders/{$single_value_enum_order->id}")
- ->assertStatus(403)
- ->assertDontSee($single_value_enum_order->order_no);
- }
-
- #[Test]
- public function it_can_delete_a_single_value_enum_order()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin('delete_single_value_enum_orders');
-
- $single_value_enum_order = $this->getFactory(SingleValueEnumOrder::class)->create();
-
- $this->assertDatabaseHas('single_value_enum_orders', [
- 'id' => $single_value_enum_order->id,
- ]);
-
- $this->adminDelete("/single-value-enum-orders/{$single_value_enum_order->id}")
- ->assertRedirect();
-
- $this->assertDatabaseMissing('single_value_enum_orders', [
- 'id' => $single_value_enum_order->id,
- ]);
- }
-
- #[Test]
- public function it_does_not_allow_an_unauthorized_user_to_delete_a_single_value_enum_order()
- {
- $this->actingAsAdmin();
-
- $single_value_enum_order = $this->getFactory(SingleValueEnumOrder::class)->create();
-
- $this->assertDatabaseHas('single_value_enum_orders', [
- 'id' => $single_value_enum_order->id,
- ]);
-
- $this->adminDelete("/single-value-enum-orders/{$single_value_enum_order->id}")
- ->assertStatus(403);
-
- $this->assertDatabaseHas('single_value_enum_orders', [
- 'id' => $single_value_enum_order->id,
- ]);
- }
-
- #[Test]
- public function it_can_bulk_delete_single_value_enum_orders()
- {
- $this->withoutExceptionHandling();
-
- $this->actingAsAdmin(['view_single_value_enum_orders', 'delete_single_value_enum_orders']);
-
- $single_value_enum_orders = $this->getFactory(SingleValueEnumOrder::class)->count(3)->create();
-
- $single_value_enum_orders = $single_value_enum_orders->map(function ($single_value_enum_order) {
- return collect($single_value_enum_order->toArray())
- ->only('id')
- ->all();
- });
-
- $single_value_enum_orders->each(function ($single_value_enum_order) {
- $this->assertDatabaseHas('single_value_enum_orders', $single_value_enum_order);
- });
-
- $single_value_enum_orders_to_delete = $single_value_enum_orders->slice(1);
-
- $this->adminPatch('/single-value-enum-orders', [
- 'single_value_enum_orders' => $single_value_enum_orders_to_delete->pluck('id')->all(),
- 'action' => 'delete',
- ])
- ->assertRedirect()
- ->assertSessionMissing('errors');
-
- $single_value_enum_orders_to_delete->each(function ($single_value_enum_order) {
- $this->assertDatabaseMissing('single_value_enum_orders', $single_value_enum_order);
- });
- }
-
- #[Test]
- public function it_does_not_allow_unauthorized_users_to_bulk_delete_single_value_enum_orders()
- {
- $this->actingAsAdmin('view_single_value_enum_orders');
-
- $single_value_enum_orders = $this->getFactory(SingleValueEnumOrder::class)->count(3)->create();
-
- $single_value_enum_orders = $single_value_enum_orders->map(function ($single_value_enum_order) {
- return collect($single_value_enum_order->toArray())
- ->only('id')
- ->all();
- });
-
- $single_value_enum_orders->each(function ($single_value_enum_order) {
- $this->assertDatabaseHas('single_value_enum_orders', $single_value_enum_order);
- });
-
- $single_value_enum_orders_to_delete = $single_value_enum_orders->slice(1);
-
- $this->adminPatch('/single-value-enum-orders', [
- 'single_value_enum_orders' => $single_value_enum_orders_to_delete->pluck('id')->all(),
- 'action' => 'delete',
- ])
- ->assertStatus(403)
- ->assertSessionMissing('errors');
-
- $single_value_enum_orders_to_delete->each(function ($single_value_enum_order) {
- $this->assertDatabaseHas('single_value_enum_orders', $single_value_enum_order);
- });
- }
-
- #[Test]
- public function it_can_validate_single_value_enum_order_inputs()
- {
- $this->actingAsAdmin('edit_single_value_enum_orders');
-
- $this->adminPost('/single-value-enum-orders', [
- 'order_no' => [],
- 'category' => -1,
- 'product_slug' => -1,
- 'status' => [],
- ])
- ->assertRedirect()
- ->assertSessionHasErrors([
- 'order_no',
- 'category',
- 'product_slug',
- 'status',
- ]);
-
- $this->assertDatabaseMissing('single_value_enum_orders', [
- 'order_no' => [],
- 'category_id' => -1,
- 'product_slug' => -1,
- 'status' => [],
- ]);
- }
-}
diff --git a/tests/stubs/tests/_correctDbValues.stub b/tests/stubs/tests/_correctDbValues.stub
deleted file mode 100644
index 58657b4..0000000
--- a/tests/stubs/tests/_correctDbValues.stub
+++ /dev/null
@@ -1,15 +0,0 @@
- 'name' => 'foo',
- 'address' => 'foo',
- 'slug' => 'foo',
- 'description' => 'Lorem ipsum',
- 'price' => 10.50,
- 'stock' => 1,
- 'on_sale' => true,
- 'features' => json_encode(['apple']),
- 'published_at' => '2024-02-12 14:54:00',
- 'expire_at' => '2024-02-12 14:54:00',
- 'released_on' => '2024-02-12',
- 'sale_time' => '14:54:00',
- 'status' => 'draft',
- 'category_id' => $old_category->id,
- 'manufactured_year' => 2024,
diff --git a/tests/stubs/tests/_correctInputs.stub b/tests/stubs/tests/_correctInputs.stub
deleted file mode 100644
index 778e4cf..0000000
--- a/tests/stubs/tests/_correctInputs.stub
+++ /dev/null
@@ -1,15 +0,0 @@
- 'name' => 'foo',
- 'address' => 'foo',
- 'slug' => 'foo',
- 'description' => 'Lorem ipsum',
- 'price' => 10.50,
- 'stock' => 1,
- 'on_sale' => true,
- 'features' => ['apple'],
- 'published_at' => '2024-02-12 14:54:00',
- 'expire_at' => '2024-02-12 14:54:00',
- 'released_on' => '2024-02-12',
- 'sale_time' => '14:54:00',
- 'status' => 'draft',
- 'category' => $old_category->id,
- 'manufactured_year' => 2024,
diff --git a/tests/stubs/tests/_differentCorrectDbValues.stub b/tests/stubs/tests/_differentCorrectDbValues.stub
deleted file mode 100644
index f74f406..0000000
--- a/tests/stubs/tests/_differentCorrectDbValues.stub
+++ /dev/null
@@ -1,15 +0,0 @@
- 'name' => 'bar',
- 'address' => 'bar',
- 'slug' => 'bar',
- 'description' => 'Itsu bitsum',
- 'price' => 5.24,
- 'stock' => 2,
- 'on_sale' => false,
- 'features' => json_encode(['orange']),
- 'published_at' => '2023-01-11 13:30:00',
- 'expire_at' => '2023-01-11 13:30:00',
- 'released_on' => '2023-01-11',
- 'sale_time' => '13:30:00',
- 'status' => 'published',
- 'category_id' => $new_category->id,
- 'manufactured_year' => 2023,
diff --git a/tests/stubs/tests/_differentCorrectInputs.stub b/tests/stubs/tests/_differentCorrectInputs.stub
deleted file mode 100644
index 6727c3d..0000000
--- a/tests/stubs/tests/_differentCorrectInputs.stub
+++ /dev/null
@@ -1,15 +0,0 @@
- 'name' => 'bar',
- 'address' => 'bar',
- 'slug' => 'bar',
- 'description' => 'Itsu bitsum',
- 'price' => 5.24,
- 'stock' => 2,
- 'on_sale' => false,
- 'features' => ['orange'],
- 'published_at' => '2023-01-11 13:30:00',
- 'expire_at' => '2023-01-11 13:30:00',
- 'released_on' => '2023-01-11',
- 'sale_time' => '13:30:00',
- 'status' => 'published',
- 'category' => $new_category->id,
- 'manufactured_year' => 2023,
diff --git a/tests/stubs/tests/_errors.stub b/tests/stubs/tests/_errors.stub
deleted file mode 100644
index 90c1b62..0000000
--- a/tests/stubs/tests/_errors.stub
+++ /dev/null
@@ -1,15 +0,0 @@
- 'name',
- 'address',
- 'slug',
- 'description',
- 'price',
- 'stock',
- 'on_sale',
- 'features',
- 'published_at',
- 'expire_at',
- 'released_on',
- 'sale_time',
- 'status',
- 'category',
- 'manufactured_year',
diff --git a/tests/stubs/tests/_factoryDbValues.stub b/tests/stubs/tests/_factoryDbValues.stub
deleted file mode 100644
index d456fe1..0000000
--- a/tests/stubs/tests/_factoryDbValues.stub
+++ /dev/null
@@ -1,15 +0,0 @@
- 'name' => $product->name,
- 'address' => $product->address,
- 'slug' => $product->slug,
- 'description' => $product->description,
- 'price' => $product->price,
- 'stock' => $product->stock,
- 'on_sale' => $product->on_sale,
- 'features' => is_null($product->features) ? null : json_encode($product->features),
- 'published_at' => $product->published_at,
- 'expire_at' => $product->expire_at,
- 'released_on' => $product->released_on,
- 'sale_time' => $product->sale_time,
- 'status' => $product->status,
- 'category_id' => $product->category_id,
- 'manufactured_year' => $product->manufactured_year,
diff --git a/tests/stubs/tests/_factoryInputs.stub b/tests/stubs/tests/_factoryInputs.stub
deleted file mode 100644
index 75979da..0000000
--- a/tests/stubs/tests/_factoryInputs.stub
+++ /dev/null
@@ -1,15 +0,0 @@
- 'name' => $product->name,
- 'address' => $product->address,
- 'slug' => $product->slug,
- 'description' => $product->description,
- 'price' => $product->price,
- 'stock' => $product->stock,
- 'on_sale' => $product->on_sale,
- 'features' => $product->features,
- 'published_at' => $product->published_at,
- 'expire_at' => $product->expire_at,
- 'released_on' => $product->released_on,
- 'sale_time' => $product->sale_time,
- 'status' => $product->status,
- 'category' => $product->category_id,
- 'manufactured_year' => $product->manufactured_year,
diff --git a/tests/stubs/tests/_wrongDbValues.stub b/tests/stubs/tests/_wrongDbValues.stub
deleted file mode 100644
index f1ce9c5..0000000
--- a/tests/stubs/tests/_wrongDbValues.stub
+++ /dev/null
@@ -1,15 +0,0 @@
- 'name' => [],
- 'address' => [],
- 'slug' => [],
- 'description' => [],
- 'price' => 'foo',
- 'stock' => 'foo',
- 'on_sale' => 'foo',
- 'features' => json_encode('foo'),
- 'published_at' => 'foo',
- 'expire_at' => 'foo',
- 'released_on' => 'foo',
- 'sale_time' => 'foo',
- 'status' => [],
- 'category_id' => -1,
- 'manufactured_year' => -1,
diff --git a/tests/stubs/tests/_wrongInputs.stub b/tests/stubs/tests/_wrongInputs.stub
deleted file mode 100644
index 5edc79a..0000000
--- a/tests/stubs/tests/_wrongInputs.stub
+++ /dev/null
@@ -1,15 +0,0 @@
- 'name' => [],
- 'address' => [],
- 'slug' => [],
- 'description' => [],
- 'price' => 'foo',
- 'stock' => 'foo',
- 'on_sale' => 'foo',
- 'features' => 'foo',
- 'published_at' => 'foo',
- 'expire_at' => 'foo',
- 'released_on' => 'foo',
- 'sale_time' => 'foo',
- 'status' => [],
- 'category' => -1,
- 'manufactured_year' => -1,
diff --git a/tests/stubs/views/categories/_actions.blade.php b/tests/stubs/views/categories/_actions.blade.php
deleted file mode 100644
index 3179c38..0000000
--- a/tests/stubs/views/categories/_actions.blade.php
+++ /dev/null
@@ -1,41 +0,0 @@
-
diff --git a/tests/stubs/views/categories/_filter.blade.php b/tests/stubs/views/categories/_filter.blade.php
deleted file mode 100644
index 8857bf4..0000000
--- a/tests/stubs/views/categories/_filter.blade.php
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/tests/stubs/views/categories/_form.blade.php b/tests/stubs/views/categories/_form.blade.php
deleted file mode 100644
index 2371ceb..0000000
--- a/tests/stubs/views/categories/_form.blade.php
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
- {{ __('Save') }}
-
-
-
- {{ __('Cancel') }}
-
-
-
-
-@include('admin.components.floating-submit')
diff --git a/tests/stubs/views/categories/_list.blade.php b/tests/stubs/views/categories/_list.blade.php
deleted file mode 100644
index 59f9b1d..0000000
--- a/tests/stubs/views/categories/_list.blade.php
+++ /dev/null
@@ -1,33 +0,0 @@
-@foreach($categories as $category)
-
-
-
- {!! $category->admin_link !!}
-
-
-
-
-
-
-@endforeach
diff --git a/tests/stubs/views/categories/_table.blade.php b/tests/stubs/views/categories/_table.blade.php
deleted file mode 100644
index aa0bda9..0000000
--- a/tests/stubs/views/categories/_table.blade.php
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
- @if(empty($no_bulk))
-
- @include('admin.categories._bulk')
-
- @endif
-
-
-
-
-
-
-
- @if($categories->isEmpty())
-
- {{ __('No matching categories found.') }}
-
- @else
- @include('admin.categories._list')
- @endif
-
-
- @if(empty($no_pagination))
-
- {{ $categories->links('forms::material-admin-26.pagination') }}
-
- @endif
-
-
diff --git a/tests/stubs/views/categories/index.blade.php b/tests/stubs/views/categories/index.blade.php
deleted file mode 100644
index 1a83396..0000000
--- a/tests/stubs/views/categories/index.blade.php
+++ /dev/null
@@ -1,29 +0,0 @@
-@extends('admin.categories.categories')
-
-@section('page-subheading')
- {{ $title }}
-@endsection
-
-@section('content')
- @if($categories->isNotEmpty() || App\Models\Category::exists())
-
-
- @include('admin.categories._filter')
-
-
- @include('admin.categories._table')
-
- @else
-
- @endif
-@endsection
diff --git a/tests/stubs/views/orders/_details.blade.php b/tests/stubs/views/orders/_details.blade.php
deleted file mode 100644
index c38ab7f..0000000
--- a/tests/stubs/views/orders/_details.blade.php
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/tests/stubs/views/orders/_form.blade.php b/tests/stubs/views/orders/_form.blade.php
deleted file mode 100644
index 697f2bb..0000000
--- a/tests/stubs/views/orders/_form.blade.php
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- {{ __('Save') }}
-
-
-
- {{ __('Cancel') }}
-
-
-
-
-@include('admin.components.floating-submit')
diff --git a/tests/stubs/views/orders/_list.blade.php b/tests/stubs/views/orders/_list.blade.php
deleted file mode 100644
index 9d6676d..0000000
--- a/tests/stubs/views/orders/_list.blade.php
+++ /dev/null
@@ -1,37 +0,0 @@
-@foreach($orders as $order)
-
-
-
- {!! $order->admin_link !!}
-
-
-
-
-
-
-
-
-
-
-@endforeach
diff --git a/tests/stubs/views/products/_actions.blade.php b/tests/stubs/views/products/_actions.blade.php
deleted file mode 100644
index 4616512..0000000
--- a/tests/stubs/views/products/_actions.blade.php
+++ /dev/null
@@ -1,48 +0,0 @@
-
diff --git a/tests/stubs/views/products/_bulk.blade.php b/tests/stubs/views/products/_bulk.blade.php
deleted file mode 100644
index 5e7f544..0000000
--- a/tests/stubs/views/products/_bulk.blade.php
+++ /dev/null
@@ -1,9 +0,0 @@
-@php
- $actions = [];
-
- if (auth()->user()->can('delete_products')) {
- $actions['delete'] = __('Delete');
- }
-@endphp
-
-
diff --git a/tests/stubs/views/products/_columns.blade.stub b/tests/stubs/views/products/_columns.blade.stub
deleted file mode 100644
index 912e049..0000000
--- a/tests/stubs/views/products/_columns.blade.stub
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/tests/stubs/views/products/_details.blade.php b/tests/stubs/views/products/_details.blade.php
deleted file mode 100644
index e1fd1e6..0000000
--- a/tests/stubs/views/products/_details.blade.php
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/tests/stubs/views/products/_filter.blade.php b/tests/stubs/views/products/_filter.blade.php
deleted file mode 100644
index 85606a1..0000000
--- a/tests/stubs/views/products/_filter.blade.php
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/tests/stubs/views/products/_form.blade.php b/tests/stubs/views/products/_form.blade.php
deleted file mode 100644
index 8a7a883..0000000
--- a/tests/stubs/views/products/_form.blade.php
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ __('Save') }}
-
-
-
- {{ __('Cancel') }}
-
-
-
-
-@include('admin.components.floating-submit')
diff --git a/tests/stubs/views/products/_list.blade.php b/tests/stubs/views/products/_list.blade.php
deleted file mode 100644
index 94814d7..0000000
--- a/tests/stubs/views/products/_list.blade.php
+++ /dev/null
@@ -1,75 +0,0 @@
-@foreach($products as $product)
-
-
-
- {!! $product->admin_link !!}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-@endforeach
diff --git a/tests/stubs/views/products/_table.blade.php b/tests/stubs/views/products/_table.blade.php
deleted file mode 100644
index e0c487c..0000000
--- a/tests/stubs/views/products/_table.blade.php
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
- @if(empty($no_bulk))
-
- @include('admin.products._bulk')
-
- @endif
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- @if($products->isEmpty())
-
- {{ __('No matching products found.') }}
-
- @else
- @include('admin.products._list')
- @endif
-
-
- @if(empty($no_pagination))
-
- {{ $products->links('forms::material-admin-26.pagination') }}
-
- @endif
-
-
diff --git a/tests/stubs/views/products/_titles.blade.stub b/tests/stubs/views/products/_titles.blade.stub
deleted file mode 100644
index 653bed5..0000000
--- a/tests/stubs/views/products/_titles.blade.stub
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/tests/stubs/views/products/create.blade.php b/tests/stubs/views/products/create.blade.php
deleted file mode 100644
index 811bd4d..0000000
--- a/tests/stubs/views/products/create.blade.php
+++ /dev/null
@@ -1,9 +0,0 @@
-@extends('admin.products.products')
-
-@section('page-title', __('New Product'))
-
-@section('content')
-
- @include('admin.products._form')
-
-@endsection
diff --git a/tests/stubs/views/products/edit.blade.php b/tests/stubs/views/products/edit.blade.php
deleted file mode 100644
index e7837f9..0000000
--- a/tests/stubs/views/products/edit.blade.php
+++ /dev/null
@@ -1,9 +0,0 @@
-@extends('admin.products.products')
-
-@section('page-title', __('Edit Product'))
-
-@section('content')
-
- @include('admin.products._form')
-
-@endsection
diff --git a/tests/stubs/views/products/index.blade.php b/tests/stubs/views/products/index.blade.php
deleted file mode 100644
index b7eae03..0000000
--- a/tests/stubs/views/products/index.blade.php
+++ /dev/null
@@ -1,36 +0,0 @@
-@extends('admin.products.products')
-
-@section('page-title')
- {{ if_route('admin.products.trash') ? __('Deleted Products') : __('Products') }}
-@endsection
-
-@section('page-subheading')
- {{ $title }}
-@endsection
-
-@section('content')
- @if($products->isNotEmpty() || App\Models\Product::exists())
-
-
- @include('admin.products._filter')
-
-
- @include('admin.products._table', [
- 'no_bulk' => $trashed,
- 'no_checkbox' => $trashed,
- ])
-
- @else
-
- @endif
-@endsection
diff --git a/tests/stubs/views/products/products.blade.php b/tests/stubs/views/products/products.blade.php
deleted file mode 100644
index 9cd39e4..0000000
--- a/tests/stubs/views/products/products.blade.php
+++ /dev/null
@@ -1,12 +0,0 @@
-@extends('layouts.admin')
-
-@section('title', 'Products')
-@section('page-title', __('Products'))
-
-@section('top-search')
-
-@endsection
-
-@section('model-actions')
- @include('admin.products._actions')
-@endsection
diff --git a/tests/stubs/views/products/show.blade.php b/tests/stubs/views/products/show.blade.php
deleted file mode 100644
index 7001497..0000000
--- a/tests/stubs/views/products/show.blade.php
+++ /dev/null
@@ -1,7 +0,0 @@
-@extends('admin.products.products')
-
-@section('page-title', __('View Product'))
-
-@section('content')
- @include('admin.products._details')
-@endsection
diff --git a/workbench/.prettierrc.json b/workbench/.prettierrc.json
new file mode 100644
index 0000000..afa7d06
--- /dev/null
+++ b/workbench/.prettierrc.json
@@ -0,0 +1,4 @@
+{
+ "semi": false,
+ "printWidth": 120
+}
diff --git a/workbench/app/Enums/ProductTypes.php b/workbench/app/Enums/ProductTypes.php
new file mode 100644
index 0000000..4fba30d
--- /dev/null
+++ b/workbench/app/Enums/ProductTypes.php
@@ -0,0 +1,17 @@
+ __('Physical'),
+ self::Digital => __('Digital'),
+ };
+ }
+}
diff --git a/workbench/app/Http/Controllers/Controller.php b/workbench/app/Http/Controllers/Controller.php
new file mode 100644
index 0000000..8677cd5
--- /dev/null
+++ b/workbench/app/Http/Controllers/Controller.php
@@ -0,0 +1,8 @@
+ */
+ use HasFactory;
+
+ use HasRoles;
+ use InteractsWithMedia;
+ use OwnsMedia;
+
+ protected $fillable = ['name', 'email', 'password'];
+
+ protected $hidden = ['password', 'remember_token'];
+
+ public function registerMediaCollections(): void
+ {
+ $this->registerMediapickerCollections();
+ }
+
+ public function registerMediaConversions(?Media $media = null): void
+ {
+ $this->registerMediapickerConversions($media);
+ }
+
+ protected function casts(): array
+ {
+ return [
+ 'email_verified_at' => 'datetime',
+ 'password' => 'hashed',
+ ];
+ }
+}
diff --git a/tests/stubs/Providers/AppServiceProvider.php b/workbench/app/Providers/AppServiceProvider.php
similarity index 100%
rename from tests/stubs/Providers/AppServiceProvider.php
rename to workbench/app/Providers/AppServiceProvider.php
diff --git a/workbench/build.php b/workbench/build.php
new file mode 100644
index 0000000..8b0d0be
--- /dev/null
+++ b/workbench/build.php
@@ -0,0 +1,81 @@
+
+ */
+class UserFactory extends Factory
+{
+ protected $model = User::class;
+
+ public function definition(): array
+ {
+ return [
+ 'name' => fake()->name(),
+ 'email' => fake()->unique()->safeEmail(),
+ 'email_verified_at' => now(),
+ 'password' => 'password',
+ 'remember_token' => Str::random(10),
+ ];
+ }
+}
diff --git a/workbench/database/migrations/0001_01_01_000000_create_users_table.php b/workbench/database/migrations/0001_01_01_000000_create_users_table.php
new file mode 100644
index 0000000..3bea822
--- /dev/null
+++ b/workbench/database/migrations/0001_01_01_000000_create_users_table.php
@@ -0,0 +1,43 @@
+id();
+ $table->string('name');
+ $table->string('email')->unique();
+ $table->timestamp('email_verified_at')->nullable();
+ $table->string('password');
+ $table->rememberToken();
+ $table->timestamps();
+ });
+
+ Schema::create('password_reset_tokens', function (Blueprint $table) {
+ $table->string('email')->primary();
+ $table->string('token');
+ $table->timestamp('created_at')->nullable();
+ });
+
+ Schema::create('sessions', function (Blueprint $table) {
+ $table->string('id')->primary();
+ $table->foreignId('user_id')->nullable()->index();
+ $table->string('ip_address', 45)->nullable();
+ $table->text('user_agent')->nullable();
+ $table->longText('payload');
+ $table->integer('last_activity')->index();
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::dropIfExists('sessions');
+ Schema::dropIfExists('password_reset_tokens');
+ Schema::dropIfExists('users');
+ }
+};
diff --git a/workbench/database/migrations/2026_01_01_000000_create_media_table.php b/workbench/database/migrations/2026_01_01_000000_create_media_table.php
new file mode 100644
index 0000000..47a4be9
--- /dev/null
+++ b/workbench/database/migrations/2026_01_01_000000_create_media_table.php
@@ -0,0 +1,32 @@
+id();
+
+ $table->morphs('model');
+ $table->uuid()->nullable()->unique();
+ $table->string('collection_name');
+ $table->string('name');
+ $table->string('file_name');
+ $table->string('mime_type')->nullable();
+ $table->string('disk');
+ $table->string('conversions_disk')->nullable();
+ $table->unsignedBigInteger('size');
+ $table->json('manipulations');
+ $table->json('custom_properties');
+ $table->json('generated_conversions');
+ $table->json('responsive_images');
+ $table->unsignedInteger('order_column')->nullable()->index();
+
+ $table->nullableTimestamps();
+ });
+ }
+};
diff --git a/workbench/database/migrations/2026_01_01_000001_create_catalog_tables.php b/workbench/database/migrations/2026_01_01_000001_create_catalog_tables.php
new file mode 100644
index 0000000..615e299
--- /dev/null
+++ b/workbench/database/migrations/2026_01_01_000001_create_catalog_tables.php
@@ -0,0 +1,79 @@
+id();
+ $table->string('name')->unique();
+ $table->string('slug')->unique();
+ $table->text('description')->nullable();
+ $table->boolean('is_active')->default(true);
+ $table->timestamps();
+ });
+
+ Schema::create('brands', function (Blueprint $table) {
+ $table->id();
+ $table->string('name');
+ $table->string('website')->nullable();
+ $table->timestamps();
+ });
+
+ Schema::create('products', function (Blueprint $table) {
+ $table->id();
+ $table->string('name');
+ $table->string('sku', 20)->unique();
+ $table->text('description')->nullable();
+ $table->decimal('price', 10, 2)->unsigned();
+ $table->unsignedInteger('stock');
+ $table->enum('status', ['draft', 'published']);
+ $table->string('type')->comment('enum:App\\Enums\\ProductTypes');
+ $table->date('released_on')->nullable();
+ $table->dateTime('published_at')->nullable();
+ $table->time('sale_time')->nullable();
+ $table->json('features')->nullable();
+ $table->year('manufactured_year')->nullable();
+ $table->boolean('is_featured')->default(false);
+ $table->foreignId('category_id')->constrained()->cascadeOnDelete();
+ $table->foreignId('brand_id')->nullable()->constrained()->nullOnDelete();
+ $table->timestamps();
+ $table->softDeletes();
+ });
+
+ Schema::create('tags', function (Blueprint $table) {
+ $table->id();
+ $table->string('name');
+ $table->timestamps();
+ });
+
+ Schema::create('product_tag', function (Blueprint $table) {
+ $table->foreignId('product_id')->constrained()->cascadeOnDelete();
+ $table->foreignId('tag_id')->constrained()->cascadeOnDelete();
+ $table->primary(['product_id', 'tag_id']);
+ });
+
+ Schema::create('reviews', function (Blueprint $table) {
+ $table->id();
+ $table->foreignId('product_id')->constrained()->cascadeOnDelete();
+ $table->string('title');
+ $table->text('body');
+ $table->unsignedTinyInteger('rating');
+ $table->timestamps();
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::dropIfExists('reviews');
+ Schema::dropIfExists('product_tag');
+ Schema::dropIfExists('tags');
+ Schema::dropIfExists('products');
+ Schema::dropIfExists('brands');
+ Schema::dropIfExists('categories');
+ }
+};
diff --git a/workbench/database/migrations/2026_01_01_000002_create_posts_table.php b/workbench/database/migrations/2026_01_01_000002_create_posts_table.php
new file mode 100644
index 0000000..bde99db
--- /dev/null
+++ b/workbench/database/migrations/2026_01_01_000002_create_posts_table.php
@@ -0,0 +1,24 @@
+id();
+ $table->string('title');
+ $table->text('excerpt')->nullable();
+ $table->dateTime('published_at')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::dropIfExists('posts');
+ }
+};
diff --git a/workbench/database/migrations/2026_01_01_000003_create_articles_table.php b/workbench/database/migrations/2026_01_01_000003_create_articles_table.php
new file mode 100644
index 0000000..08f940d
--- /dev/null
+++ b/workbench/database/migrations/2026_01_01_000003_create_articles_table.php
@@ -0,0 +1,26 @@
+id();
+ $table->string('title');
+ $table->string('slug')->unique();
+ $table->text('body');
+ $table->json('translations')->nullable();
+ $table->string('lang')->nullable()->index();
+ $table->timestamps();
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::dropIfExists('articles');
+ }
+};
diff --git a/workbench/database/migrations/2026_01_01_000004_create_customers_table.php b/workbench/database/migrations/2026_01_01_000004_create_customers_table.php
new file mode 100644
index 0000000..6611c46
--- /dev/null
+++ b/workbench/database/migrations/2026_01_01_000004_create_customers_table.php
@@ -0,0 +1,24 @@
+string('phone')->nullable();
+ $table->date('date_of_birth')->nullable();
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::dropIfExists('customers');
+ }
+};
diff --git a/workbench/generate/GenerateAppTest.php b/workbench/generate/GenerateAppTest.php
new file mode 100644
index 0000000..d9fc281
--- /dev/null
+++ b/workbench/generate/GenerateAppTest.php
@@ -0,0 +1,62 @@
+}>
+ */
+ public static function commands(): array
+ {
+ return [
+ // Blade, plain
+ ['generate:all', ['table' => 'categories']],
+ ['generate:all', ['table' => 'tags']],
+ // Inertia React
+ ['generate:all', ['table' => 'brands', '--views' => 'inertia']],
+ // Blade: soft deletes, enums, every column type, a many-to-many relation
+ ['generate:all', ['table' => 'products']],
+ // Inertia React, nested under products
+ ['generate:all', ['table' => 'reviews', '--parent' => 'products', '--views' => 'inertia']],
+ // Media collections (javaabu/mediapicker), Blade and Inertia React
+ ['generate:all', ['table' => 'posts', '--media' => 'featured_image:image,documents:document:multiple']],
+ // Translatable when javaabu/translatable is installed (the translations and lang columns)
+ ['generate:all', ['table' => 'articles']],
+ // A javaabu/auth user type and the admin pages of its users
+ ['generate:auth', ['table' => 'customers']],
+ ];
+ }
+
+ #[Test]
+ public function it_generates_the_app(): void
+ {
+ foreach (static::commands() as [$command, $arguments]) {
+ $exit = Artisan::call($command, [...$arguments, '--create' => true, '--force' => true]);
+
+ fwrite(STDOUT, "\n> {$command} " . json_encode($arguments, JSON_UNESCAPED_SLASHES) . "\n" . Artisan::output());
+
+ $this->assertEquals(0, $exit, "{$command} failed");
+ }
+ }
+}
diff --git a/workbench/phpunit.generate.xml b/workbench/phpunit.generate.xml
new file mode 100644
index 0000000..ce2b33e
--- /dev/null
+++ b/workbench/phpunit.generate.xml
@@ -0,0 +1,12 @@
+
+
+
+
+ generate
+
+
+
diff --git a/workbench/phpunit.xml b/workbench/phpunit.xml
new file mode 100644
index 0000000..ed92d5f
--- /dev/null
+++ b/workbench/phpunit.xml
@@ -0,0 +1,13 @@
+
+
+
+
+ ../build/app/tests/Feature
+
+
+
diff --git a/workbench/resources/views/app.blade.php b/workbench/resources/views/app.blade.php
new file mode 100644
index 0000000..9b8697a
--- /dev/null
+++ b/workbench/resources/views/app.blade.php
@@ -0,0 +1,10 @@
+
+
+
+
+ @inertiaHead
+
+
+ @inertia
+
+
diff --git a/workbench/resources/views/layouts/admin.blade.php b/workbench/resources/views/layouts/admin.blade.php
new file mode 100644
index 0000000..ab0f714
--- /dev/null
+++ b/workbench/resources/views/layouts/admin.blade.php
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+ @yield('title') - {{ config('app.name') }}
+
+
+ @if(session('status'))
+ {{ session('status') }}
+ @endif
+
+
+ @yield('content')
+
+
+ @stack('scripts')
+
+
diff --git a/workbench/routes/web.php b/workbench/routes/web.php
new file mode 100644
index 0000000..72ea86d
--- /dev/null
+++ b/workbench/routes/web.php
@@ -0,0 +1,5 @@
+ 'ok');
diff --git a/workbench/tests/Feature/GeneratedUserTypeTest.php b/workbench/tests/Feature/GeneratedUserTypeTest.php
new file mode 100644
index 0000000..8d92272
--- /dev/null
+++ b/workbench/tests/Feature/GeneratedUserTypeTest.php
@@ -0,0 +1,49 @@
+assertEquals('customer', $type->guardName());
+ $this->assertEquals(Customer::class, $type->modelClass());
+ $this->assertEquals('customer', UserTypes::for(new Customer)->name());
+ }
+
+ #[Test]
+ public function it_shows_the_login_page_of_the_user_type(): void
+ {
+ $this->get(route('customer.login'))
+ ->assertOk();
+ }
+
+ #[Test]
+ public function it_logs_customers_in_on_their_guard(): void
+ {
+ $customer = Customer::factory()->create(['email' => 'ali@example.com']);
+
+ $this->post(route('customer.login.store'), [
+ 'email' => 'ali@example.com',
+ 'password' => 'password',
+ ])->assertRedirect();
+
+ $this->assertAuthenticatedAs($customer, 'customer');
+ $this->assertGuest('web');
+ }
+}
diff --git a/workbench/tests/TestCase.php b/workbench/tests/TestCase.php
new file mode 100644
index 0000000..ed863e0
--- /dev/null
+++ b/workbench/tests/TestCase.php
@@ -0,0 +1,79 @@
+set('app.key', 'base64:yWa/ByhLC/GUvfToOuaPD7zDwB64qkc/QkaQOrT5IpE=');
+ $config->set('app.locale', 'en');
+
+ $config->set('database.default', 'testing');
+ $config->set('database.connections.testing', [
+ 'driver' => 'sqlite',
+ 'database' => ':memory:',
+ 'prefix' => '',
+ 'foreign_key_constraints' => true,
+ ]);
+
+ $config->set('session.driver', 'array');
+ $config->set('cache.default', 'array');
+ $config->set('queue.default', 'sync');
+
+ $config->set('auth.providers.users.model', 'App\\Models\\User');
+
+ $config->set('permission.cache.store', 'array');
+
+ $config->set('inertia.testing.ensure_pages_exist', true);
+ $config->set('inertia.testing.page_paths', [resource_path('js/pages')]);
+ $config->set('inertia.testing.page_extensions', ['tsx']);
+
+ $config->set('media-library.media_model', 'Javaabu\\Mediapicker\\Models\\Media');
+ $config->set('media-library.disk_name', 'public');
+
+ $config->set('generators.enums', [
+ 'products.type' => 'App\\Enums\\ProductTypes',
+ ]);
+ });
+ }
+
+ protected function defineRoutes($router): void
+ {
+ // routes/web.php loads the admin routes the generators write
+ /** @var Router $router */
+ $router->group([], base_path('routes/web.php'));
+ }
+
+ protected function defineDatabaseMigrations(): void
+ {
+ $this->loadMigrationsFrom(database_path('migrations'));
+ }
+}
diff --git a/workbench/tsconfig.json b/workbench/tsconfig.json
new file mode 100644
index 0000000..0f485fb
--- /dev/null
+++ b/workbench/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "jsx": "react-jsx",
+ "strict": true,
+ "noUncheckedIndexedAccess": true,
+ "isolatedModules": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "noEmit": true,
+ "types": ["node"],
+ "paths": {
+ "@javaabu/stencil": ["../vendor/javaabu/stencil/resources/js"],
+ "@javaabu/stencil/*": ["../vendor/javaabu/stencil/resources/js/*"],
+ "@javaabu/filters": ["../vendor/javaabu/filters/resources/js"],
+ "@javaabu/filters/*": ["../vendor/javaabu/filters/resources/js/*"],
+ "@javaabu/bulk-actions": ["../vendor/javaabu/bulk-actions/resources/js"],
+ "@javaabu/bulk-actions/*": ["../vendor/javaabu/bulk-actions/resources/js/*"],
+ "@javaabu/mediapicker": ["./types/mediapicker.d.ts"]
+ }
+ },
+ "include": ["../build/app/resources/js/pages", "./types"]
+}
diff --git a/workbench/types/mediapicker.d.ts b/workbench/types/mediapicker.d.ts
new file mode 100644
index 0000000..89ccbac
--- /dev/null
+++ b/workbench/types/mediapicker.d.ts
@@ -0,0 +1,5 @@
+// @javaabu/mediapicker's React code pulls in the block editor and the cropper;
+// the generated pages only pass its stencil component to .
+import type { StencilComponent } from "@javaabu/stencil"
+
+export declare const StencilMediaPicker: StencilComponent
diff --git a/workbench/types/vite.d.ts b/workbench/types/vite.d.ts
new file mode 100644
index 0000000..7e94499
--- /dev/null
+++ b/workbench/types/vite.d.ts
@@ -0,0 +1,5 @@
+// stencil's styles import CSS as a string through Vite
+declare module "*.css?raw" {
+ const css: string
+ export default css
+}