symfonia-ai-tools 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +489 -0
  2. package/bin/cli.mjs +35 -0
  3. package/lib/installer.mjs +495 -0
  4. package/lib/questions.mjs +332 -0
  5. package/lib/ui.mjs +76 -0
  6. package/lib/utils.mjs +231 -0
  7. package/package.json +26 -0
  8. package/templates/base/CLAUDE.md +34 -0
  9. package/templates/base/_ai/_guidelines_header.md +70 -0
  10. package/templates/base/_ai/context/README.md +20 -0
  11. package/templates/base/_ai/prompts/codereview.prompt.md +324 -0
  12. package/templates/base/_ai/prompts/duplicate-code-analysis.prompt.md +128 -0
  13. package/templates/base/_ai/prompts/figma-analysis.prompt.md +155 -0
  14. package/templates/base/_ai/prompts/security-review.prompt.md +46 -0
  15. package/templates/base/_ai/skills/README.md +80 -0
  16. package/templates/base/_ai/skills/TEMPLATE.md +106 -0
  17. package/templates/base/_ai/skills/babysit-prs/SKILL.md +105 -0
  18. package/templates/base/_ai/skills/debug/SKILL.md +93 -0
  19. package/templates/base/_ai/skills/fill-worklogs/SKILL.md +158 -0
  20. package/templates/base/_ai/skills/hotfix/SKILL.md +52 -0
  21. package/templates/base/_ai/skills/jira-task/SKILL.md +170 -0
  22. package/templates/base/_ai/skills/my-prs/SKILL.md +78 -0
  23. package/templates/base/_ai/skills/pr-dashboard/SKILL.md +43 -0
  24. package/templates/base/_ai/skills/pr-prepare/SKILL.md +106 -0
  25. package/templates/base/_ai/skills/refactor/SKILL.md +87 -0
  26. package/templates/base/_ai/skills/write-tests/SKILL.md +109 -0
  27. package/templates/base/_claude/settings.local.json +37 -0
  28. package/templates/base/_cursor/rules/global.mdc +7 -0
  29. package/templates/base/_editorconfig +18 -0
  30. package/templates/base/_gemini/settings.json +3 -0
  31. package/templates/base/_github/copilot-instructions.md +1 -0
  32. package/templates/base/_github/pull_request_template.md +23 -0
  33. package/templates/base/_gitignore +22 -0
  34. package/templates/base/_junie/guidelines.md +1 -0
  35. package/templates/base/commit-instructions.md +92 -0
  36. package/templates/packs/docker/_ai/instructions/docker.instructions.md +193 -0
  37. package/templates/packs/docker/_guidelines.md +10 -0
  38. package/templates/packs/docker/pack.json +8 -0
  39. package/templates/packs/laravel/_ai/instructions/api-resource.instructions.md +251 -0
  40. package/templates/packs/laravel/_ai/instructions/module.instructions.md +133 -0
  41. package/templates/packs/laravel/_ai/instructions/service-repository.instructions.md +215 -0
  42. package/templates/packs/laravel/_ai/instructions/testing.instructions.md +278 -0
  43. package/templates/packs/laravel/_ai/skills/migration/SKILL.md +172 -0
  44. package/templates/packs/laravel/_ai/skills/new-endpoint/SKILL.md +165 -0
  45. package/templates/packs/laravel/_ai/skills/new-module/SKILL.md +208 -0
  46. package/templates/packs/laravel/_ai/skills/queued-job/SKILL.md +248 -0
  47. package/templates/packs/laravel/_ai/skills/testing-feature/SKILL.md +196 -0
  48. package/templates/packs/laravel/_ai/skills/testing-manual/SKILL.md +186 -0
  49. package/templates/packs/laravel/_ai/skills/testing-unit/SKILL.md +200 -0
  50. package/templates/packs/laravel/_guidelines.md +25 -0
  51. package/templates/packs/laravel/pack.json +6 -0
  52. package/templates/packs/playwright/_ai/instructions/playwright.instructions.md +219 -0
  53. package/templates/packs/playwright/_ai/skills/playwright/README.md +194 -0
  54. package/templates/packs/playwright/_ai/skills/playwright/SKILL.md +1245 -0
  55. package/templates/packs/playwright/_ai/skills/playwright-codereview/SKILL.md +642 -0
  56. package/templates/packs/playwright/_ai/skills/playwright-record/README.md +87 -0
  57. package/templates/packs/playwright/_ai/skills/playwright-record/SKILL.md +564 -0
  58. package/templates/packs/playwright/_guidelines.md +12 -0
  59. package/templates/packs/playwright/pack.json +9 -0
  60. package/templates/packs/storybook/_ai/instructions/storybook.instructions.md +181 -0
  61. package/templates/packs/storybook/pack.json +6 -0
  62. package/templates/packs/vitest/_ai/instructions/vitest.instructions.md +688 -0
  63. package/templates/packs/vitest/pack.json +6 -0
  64. package/templates/packs/vue3/_ai/instructions/api.instructions.md +163 -0
  65. package/templates/packs/vue3/_ai/instructions/coding-conventions.instructions.md +160 -0
  66. package/templates/packs/vue3/_ai/instructions/composables.instructions.md +218 -0
  67. package/templates/packs/vue3/_ai/instructions/forms.instructions.md +227 -0
  68. package/templates/packs/vue3/_ai/instructions/store.instructions.md +504 -0
  69. package/templates/packs/vue3/_ai/instructions/vue.instructions.md +339 -0
  70. package/templates/packs/vue3/_ai/skills/api-integration/SKILL.md +195 -0
  71. package/templates/packs/vue3/_ai/skills/new-component/SKILL.md +133 -0
  72. package/templates/packs/vue3/_ai/skills/new-module/SKILL.md +177 -0
  73. package/templates/packs/vue3/_guidelines.md +45 -0
  74. package/templates/packs/vue3/pack.json +11 -0
@@ -0,0 +1,133 @@
1
+ ---
2
+ applyTo: "**/Modules/**"
3
+ ---
4
+
5
+ # Modular Architecture - Instrukcje
6
+
7
+ ## Struktura modulu
8
+
9
+ Kazdy modul biznesowy ma oddzielny katalog w `Modules/`:
10
+
11
+ ```
12
+ Modules/Feature/
13
+ ├── Config/
14
+ │ └── config.php # Konfiguracja modulu
15
+ ├── Console/ # Artisan commands
16
+ ├── Database/
17
+ │ ├── Filters/ # Custom query filters (Spatie)
18
+ │ ├── Migrations/ # Migracje modulu
19
+ │ ├── Repositories/ # Data access (CQRS)
20
+ │ │ ├── EloquentFeatureCommandRepository.php
21
+ │ │ └── EloquentFeatureQueryRepository.php
22
+ │ ├── Factories/ # Model factories (testy)
23
+ │ └── Scopes/ # Global scopes
24
+ ├── Domain/ # Logika domenowa (DDD)
25
+ │ ├── DTO/ # Data Transfer Objects
26
+ │ ├── Services/ # Domain services
27
+ │ ├── Events/ # Domain events
28
+ │ └── ValueObjects/ # Value Objects
29
+ ├── Entities/ # Eloquent models
30
+ ├── Emails/ # Mailable classes
31
+ ├── Http/
32
+ │ ├── Controllers/ # Endpointy API
33
+ │ ├── Policies/ # Autoryzacja
34
+ │ ├── Requests/ # Form Requests (walidacja)
35
+ │ └── Resources/ # API Resources (transformery)
36
+ ├── Jobs/ # Queued jobs
37
+ ├── Listeners/ # Event listeners
38
+ ├── Providers/
39
+ │ ├── FeatureServiceProvider.php # Rejestracja modulu
40
+ │ └── RouteServiceProvider.php # Routing modulu
41
+ ├── Routes/
42
+ │ └── api.php # Endpointy API modulu
43
+ ├── Tests/
44
+ │ ├── Feature/ # Integration tests
45
+ │ ├── Unit/ # Unit tests
46
+ │ └── Fixtures/ # Test fixtures
47
+ ├── module.json # Metadane modulu
48
+ └── composer.json # Zaleznosci modulu
49
+ ```
50
+
51
+ ## module.json
52
+
53
+ ```json
54
+ {
55
+ "name": "Feature",
56
+ "alias": "feature",
57
+ "description": "Opis modulu",
58
+ "active": 1,
59
+ "order": 0,
60
+ "providers": [
61
+ "Modules\\Feature\\Providers\\FeatureServiceProvider"
62
+ ]
63
+ }
64
+ ```
65
+
66
+ ## ServiceProvider
67
+
68
+ ```php
69
+ declare(strict_types=1);
70
+
71
+ namespace Modules\Feature\Providers;
72
+
73
+ use Illuminate\Support\ServiceProvider;
74
+ use Illuminate\Support\Facades\Gate;
75
+
76
+ class FeatureServiceProvider extends ServiceProvider
77
+ {
78
+ public function boot(): void
79
+ {
80
+ $this->loadMigrationsFrom(__DIR__ . '/../Database/Migrations');
81
+ $this->registerTranslations();
82
+ $this->registerConfig();
83
+
84
+ // Policies
85
+ Gate::define('feature-admin', FeaturePolicy::class . '@admin');
86
+
87
+ // Commands
88
+ $this->commands([
89
+ SyncFeatureDataCommand::class,
90
+ ]);
91
+ }
92
+
93
+ public function register(): void
94
+ {
95
+ $this->app->register(RouteServiceProvider::class);
96
+
97
+ // Bindings (interface → implementation)
98
+ $this->app->bind(
99
+ FeatureCommandRepository::class,
100
+ EloquentFeatureCommandRepository::class,
101
+ );
102
+ $this->app->bind(
103
+ FeatureQueryRepository::class,
104
+ EloquentFeatureQueryRepository::class,
105
+ );
106
+ }
107
+ }
108
+ ```
109
+
110
+ ## Routing modulu
111
+
112
+ ```php
113
+ // Routes/api.php
114
+ use Illuminate\Support\Facades\Route;
115
+
116
+ Route::middleware(['auth:api'])->prefix('feature')->group(function () {
117
+ Route::get('/', [FeatureController::class, 'index']);
118
+ Route::get('/{featureId}', [FeatureController::class, 'show']);
119
+ Route::post('/', [FeatureController::class, 'store']);
120
+ Route::put('/{featureId}', [FeatureController::class, 'update']);
121
+ Route::delete('/{featureId}', [FeatureController::class, 'destroy']);
122
+ });
123
+ ```
124
+
125
+ ## Zasady
126
+
127
+ 1. **Jeden modul = jedna domena biznesowa** - nie mieszaj odpowiedzialnosci
128
+ 2. **Modul jest samodzielny** - ma swoje migracje, routing, testy, providery
129
+ 3. **Komunikacja miedzy modulami** - przez Events/Listeners lub interfejsy, nie bezposrednie zaleznosci
130
+ 4. **CQRS** - oddzielne repozytoria Command (zapis) i Query (odczyt)
131
+ 5. **Nowy modul**: `php artisan module:make NazwaModulu`, potem dostosuj strukture
132
+ 6. **Bindingi** - interfejs → implementacja w ServiceProvider `register()`
133
+ 7. **Testy w module** - `Tests/Feature/` i `Tests/Unit/` w katalogu modulu
@@ -0,0 +1,215 @@
1
+ ---
2
+ applyTo: "**/*Service*.php,**/*Repository*.php,**/DTO/**,**/ValueObjects/**"
3
+ ---
4
+
5
+ # Service, Repository, DTO, Value Objects - Instrukcje
6
+
7
+ ## Service Layer
8
+
9
+ Serwisy koordynuja logike biznesowa. Kontroler deleguje do serwisu, serwis uzywa repozytoriow.
10
+
11
+ ```php
12
+ declare(strict_types=1);
13
+
14
+ namespace Modules\Feature\Domain\Services;
15
+
16
+ readonly class FeatureService
17
+ {
18
+ public function __construct(
19
+ private FeatureCommandRepository $commandRepository,
20
+ private FeatureQueryRepository $queryRepository,
21
+ ) {}
22
+
23
+ public function create(FeatureDTO $dto): Feature
24
+ {
25
+ $feature = Feature::create($dto);
26
+ $this->commandRepository->save($feature);
27
+
28
+ event(new FeatureCreated($feature));
29
+
30
+ return $feature;
31
+ }
32
+
33
+ public function getForUser(int $userId, int $limit): LengthAwarePaginator
34
+ {
35
+ return $this->queryRepository->getByUser($userId, $limit);
36
+ }
37
+ }
38
+ ```
39
+
40
+ ### Zasady Service
41
+
42
+ - `readonly class` - serwis nie ma mutowalnego stanu
43
+ - Constructor injection dla zaleznosci
44
+ - Jedna publiczna metoda = jedna operacja biznesowa
45
+ - Serwis **nie** zwraca Response - to robi kontroler
46
+ - Serwis moze emitowac Events
47
+
48
+ ## Repository Pattern (CQRS)
49
+
50
+ Oddziel operacje zapisu (Command) od odczytu (Query):
51
+
52
+ ### Command Repository (zapis)
53
+
54
+ ```php
55
+ declare(strict_types=1);
56
+
57
+ namespace Modules\Feature\Database\Repositories;
58
+
59
+ interface FeatureCommandRepository
60
+ {
61
+ public function save(Feature $feature): void;
62
+ public function delete(UuidInterface $id): void;
63
+ }
64
+
65
+ class EloquentFeatureCommandRepository implements FeatureCommandRepository
66
+ {
67
+ public function save(Feature $feature): void
68
+ {
69
+ $model = FeatureModel::query()
70
+ ->where(FeatureModel::COLUMN_ID, $feature->getId()->toString())
71
+ ->first();
72
+
73
+ if (!$model) {
74
+ $model = new FeatureModel();
75
+ }
76
+
77
+ $model->fill($feature->toArray())->save();
78
+ }
79
+
80
+ public function delete(UuidInterface $id): void
81
+ {
82
+ FeatureModel::query()
83
+ ->where(FeatureModel::COLUMN_ID, $id->toString())
84
+ ->delete();
85
+ }
86
+ }
87
+ ```
88
+
89
+ ### Query Repository (odczyt)
90
+
91
+ ```php
92
+ declare(strict_types=1);
93
+
94
+ namespace Modules\Feature\Database\Repositories;
95
+
96
+ interface FeatureQueryRepository
97
+ {
98
+ /** @return LengthAwarePaginator<FeatureModel> */
99
+ public function getByUser(int $userId, int $limit): LengthAwarePaginator;
100
+
101
+ public function findById(UuidInterface $id): ?FeatureModel;
102
+ }
103
+
104
+ class EloquentFeatureQueryRepository implements FeatureQueryRepository
105
+ {
106
+ /** @return LengthAwarePaginator<FeatureModel> */
107
+ public function getByUser(int $userId, int $limit): LengthAwarePaginator
108
+ {
109
+ return FeatureModel::query()
110
+ ->where(FeatureModel::COLUMN_USER_ID, $userId)
111
+ ->with(FeatureModel::RELATION_TYPE)
112
+ ->orderByDesc(FeatureModel::COLUMN_CREATED_AT)
113
+ ->paginate($limit);
114
+ }
115
+ }
116
+ ```
117
+
118
+ ## DTO (Data Transfer Object)
119
+
120
+ Przenosi dane miedzy warstwami bez logiki biznesowej:
121
+
122
+ ```php
123
+ declare(strict_types=1);
124
+
125
+ namespace Modules\Feature\Domain\DTO;
126
+
127
+ readonly class FeatureDTO
128
+ {
129
+ public function __construct(
130
+ public string $name,
131
+ public string $description,
132
+ public int $userId,
133
+ public ?int $categoryId = null,
134
+ ) {}
135
+
136
+ public static function fromRequest(StoreFeatureRequest $request): self
137
+ {
138
+ return new self(
139
+ name: $request->validated('name'),
140
+ description: $request->validated('description'),
141
+ userId: $request->user()->getId(),
142
+ categoryId: $request->validated('category_id'),
143
+ );
144
+ }
145
+ }
146
+ ```
147
+
148
+ ## Value Objects
149
+
150
+ Reprezentuja wartosc domenowa - niemutowalne, porownywane przez wartosc:
151
+
152
+ ```php
153
+ declare(strict_types=1);
154
+
155
+ namespace Modules\Feature\Domain\ValueObjects;
156
+
157
+ readonly class DateRange
158
+ {
159
+ public function __construct(
160
+ public Carbon $start,
161
+ public Carbon $end,
162
+ ) {
163
+ if ($start->isAfter($end)) {
164
+ throw new \InvalidArgumentException('Start date must be before end date');
165
+ }
166
+ }
167
+
168
+ public function overlaps(self $other): bool
169
+ {
170
+ return $this->start->isBefore($other->end)
171
+ && $this->end->isAfter($other->start);
172
+ }
173
+
174
+ public function days(): int
175
+ {
176
+ return $this->start->diffInDays($this->end);
177
+ }
178
+ }
179
+ ```
180
+
181
+ ## Kontroler - integracja
182
+
183
+ ```php
184
+ declare(strict_types=1);
185
+
186
+ namespace Modules\Feature\Http\Controllers;
187
+
188
+ class FeatureController extends Controller
189
+ {
190
+ public function __construct(
191
+ private readonly FeatureService $service,
192
+ ) {}
193
+
194
+ public function store(StoreFeatureRequest $request): JsonResponse
195
+ {
196
+ $dto = FeatureDTO::fromRequest($request);
197
+ $feature = $this->service->create($dto);
198
+
199
+ return FeatureResource::make($feature)
200
+ ->response()
201
+ ->setStatusCode(Response::HTTP_CREATED);
202
+ }
203
+ }
204
+ ```
205
+
206
+ ## Zasady
207
+
208
+ 1. **Service** - `readonly class`, business logic, emituje events
209
+ 2. **Command Repository** - tylko zapis (save, delete, update)
210
+ 3. **Query Repository** - tylko odczyt, eager loading, paginacja
211
+ 4. **DTO** - `readonly class`, statyczny `fromRequest()`, brak logiki
212
+ 5. **Value Object** - `readonly class`, walidacja w konstruktorze, metody porownania
213
+ 6. **Interfejsy** - repozytorium ma interfejs, implementacja w `Eloquent*Repository`
214
+ 7. **Binding** - interfejs → implementacja rejestrowany w ServiceProvider
215
+ 8. **Kontroler nie zawiera logiki** - deleguje do serwisu, zwraca Resource
@@ -0,0 +1,278 @@
1
+ ---
2
+ applyTo: "**/*Test*.php,**/tests/**,**/*Factory*.php"
3
+ ---
4
+
5
+ # PHP Testing (PHPUnit) - Instrukcje
6
+
7
+ ## Uruchamianie testow
8
+
9
+ ```bash
10
+ # Wszystkie testy
11
+ docker exec {{DOCKER_CONTAINER}} php artisan test
12
+
13
+ # Konkretny plik
14
+ docker exec {{DOCKER_CONTAINER}} php artisan test --filter=FeatureServiceTest
15
+
16
+ # Konkretna metoda
17
+ docker exec {{DOCKER_CONTAINER}} php artisan test --filter=test_user_can_create_feature
18
+
19
+ # Z coverage
20
+ docker exec {{DOCKER_CONTAINER}} php artisan test --coverage
21
+ ```
22
+
23
+ ## Feature Tests (integracyjne)
24
+
25
+ Testuja caly flow: request → kontroler → serwis → baza → response.
26
+
27
+ ```php
28
+ declare(strict_types=1);
29
+
30
+ namespace Modules\Feature\Tests\Feature;
31
+
32
+ use Tests\TestCase;
33
+ use Illuminate\Foundation\Testing\DatabaseTransactions;
34
+
35
+ class FeatureControllerTest extends TestCase
36
+ {
37
+ use DatabaseTransactions;
38
+
39
+ private User $user;
40
+ private Feature $feature;
41
+
42
+ protected function setUp(): void
43
+ {
44
+ parent::setUp();
45
+ $this->user = User::factory()->create();
46
+ $this->feature = Feature::factory()->create(['user_id' => $this->user->getId()]);
47
+ }
48
+
49
+ public function test_user_can_list_features(): void
50
+ {
51
+ // Arrange
52
+ Feature::factory()->count(5)->create(['user_id' => $this->user->getId()]);
53
+
54
+ // Act
55
+ $response = $this->actingAs($this->user)
56
+ ->getJson('/api/features?page=1&limit=10');
57
+
58
+ // Assert
59
+ $response->assertOk()
60
+ ->assertJsonStructure([
61
+ 'data' => [['id', 'name', 'status', 'createdAt']],
62
+ 'meta' => ['total', 'perPage', 'currentPage'],
63
+ ])
64
+ ->assertJsonCount(6, 'data');
65
+ }
66
+
67
+ public function test_user_can_create_feature(): void
68
+ {
69
+ // Arrange
70
+ $payload = [
71
+ 'name' => 'New Feature',
72
+ 'description' => 'Description',
73
+ 'status' => 'draft',
74
+ ];
75
+
76
+ // Act
77
+ $response = $this->actingAs($this->user)
78
+ ->postJson('/api/features', $payload);
79
+
80
+ // Assert
81
+ $response->assertCreated()
82
+ ->assertJsonFragment(['name' => 'New Feature']);
83
+
84
+ $this->assertDatabaseHas('features', [
85
+ 'name' => 'New Feature',
86
+ 'user_id' => $this->user->getId(),
87
+ ]);
88
+ }
89
+
90
+ public function test_unauthorized_user_cannot_delete(): void
91
+ {
92
+ $otherUser = User::factory()->create();
93
+
94
+ $response = $this->actingAs($otherUser)
95
+ ->deleteJson("/api/features/{$this->feature->getId()}");
96
+
97
+ $response->assertForbidden();
98
+ }
99
+ }
100
+ ```
101
+
102
+ ## Unit Tests
103
+
104
+ Testuja izolowana logike bez bazy i HTTP:
105
+
106
+ ```php
107
+ declare(strict_types=1);
108
+
109
+ namespace Modules\Feature\Tests\Unit;
110
+
111
+ use PHPUnit\Framework\TestCase;
112
+
113
+ class FeatureDurationCalculatorTest extends TestCase
114
+ {
115
+ private FeatureDurationCalculator $calculator;
116
+
117
+ protected function setUp(): void
118
+ {
119
+ parent::setUp();
120
+ $this->calculator = new FeatureDurationCalculator();
121
+ }
122
+
123
+ public function test_calculates_business_days(): void
124
+ {
125
+ $start = Carbon::parse('2025-01-06'); // Poniedzialek
126
+ $end = Carbon::parse('2025-01-10'); // Piatek
127
+
128
+ $result = $this->calculator->businessDays($start, $end);
129
+
130
+ $this->assertEquals(5, $result);
131
+ }
132
+
133
+ /**
134
+ * @dataProvider durationDataProvider
135
+ */
136
+ public function test_duration_calculation(
137
+ string $start,
138
+ string $end,
139
+ int $expectedDays,
140
+ ): void {
141
+ $result = $this->calculator->businessDays(
142
+ Carbon::parse($start),
143
+ Carbon::parse($end),
144
+ );
145
+
146
+ $this->assertEquals($expectedDays, $result);
147
+ }
148
+
149
+ public static function durationDataProvider(): array
150
+ {
151
+ return [
152
+ 'full week' => ['2025-01-06', '2025-01-10', 5],
153
+ 'with weekend' => ['2025-01-06', '2025-01-13', 6],
154
+ 'same day' => ['2025-01-06', '2025-01-06', 1],
155
+ 'weekend only' => ['2025-01-11', '2025-01-12', 0],
156
+ ];
157
+ }
158
+ }
159
+ ```
160
+
161
+ ## Model Factories
162
+
163
+ ```php
164
+ declare(strict_types=1);
165
+
166
+ namespace Modules\Feature\Database\Factories;
167
+
168
+ use Illuminate\Database\Eloquent\Factories\Factory;
169
+
170
+ class FeatureFactory extends Factory
171
+ {
172
+ protected $model = FeatureModel::class;
173
+
174
+ public function definition(): array
175
+ {
176
+ return [
177
+ 'name' => fake()->sentence(3),
178
+ 'description' => fake()->paragraph(),
179
+ 'status' => fake()->randomElement(FeatureStatus::cases()),
180
+ 'user_id' => User::factory(),
181
+ 'created_at' => fake()->dateTimeBetween('-1 year'),
182
+ ];
183
+ }
184
+
185
+ public function draft(): static
186
+ {
187
+ return $this->state(['status' => FeatureStatus::DRAFT]);
188
+ }
189
+
190
+ public function published(): static
191
+ {
192
+ return $this->state(['status' => FeatureStatus::PUBLISHED]);
193
+ }
194
+
195
+ public function withCategory(int $categoryId): static
196
+ {
197
+ return $this->state(['category_id' => $categoryId]);
198
+ }
199
+ }
200
+ ```
201
+
202
+ ## Laravel Fakes
203
+
204
+ ```php
205
+ public function test_sends_notification_on_create(): void
206
+ {
207
+ Notification::fake();
208
+
209
+ $this->actingAs($this->user)
210
+ ->postJson('/api/features', $this->validPayload());
211
+
212
+ Notification::assertSentTo($this->user, FeatureCreatedNotification::class);
213
+ }
214
+
215
+ public function test_dispatches_export_job(): void
216
+ {
217
+ Queue::fake();
218
+
219
+ $this->actingAs($this->user)
220
+ ->postJson('/api/features/export');
221
+
222
+ Queue::assertPushed(ExportFeatureJob::class, function ($job) {
223
+ return $job->userId === $this->user->getId();
224
+ });
225
+ }
226
+
227
+ public function test_fires_event_on_update(): void
228
+ {
229
+ Event::fake([FeatureUpdated::class]);
230
+
231
+ $this->actingAs($this->user)
232
+ ->putJson("/api/features/{$this->feature->getId()}", $this->validPayload());
233
+
234
+ Event::assertDispatched(FeatureUpdated::class);
235
+ }
236
+ ```
237
+
238
+ ## Test Helpers / Fixtures
239
+
240
+ ```php
241
+ // Tests/Fixtures/FeatureFixtures.php
242
+ trait FeatureFixtures
243
+ {
244
+ protected function createFeatureWithRelations(array $overrides = []): FeatureModel
245
+ {
246
+ $category = Category::factory()->create();
247
+ $user = User::factory()->create();
248
+
249
+ return Feature::factory()
250
+ ->withCategory($category->getId())
251
+ ->create(array_merge([
252
+ 'user_id' => $user->getId(),
253
+ ], $overrides));
254
+ }
255
+
256
+ protected function validPayload(array $overrides = []): array
257
+ {
258
+ return array_merge([
259
+ 'name' => 'Test Feature',
260
+ 'description' => 'Test description',
261
+ 'status' => 'draft',
262
+ ], $overrides);
263
+ }
264
+ }
265
+ ```
266
+
267
+ ## Zasady
268
+
269
+ 1. **`test_` prefix + snake_case** - `test_user_can_create_feature()`
270
+ 2. **AAA pattern** - Arrange, Act, Assert (komentarze opcjonalne)
271
+ 3. **`DatabaseTransactions`** - trait dla Feature tests, auto-rollback
272
+ 4. **`setUp()`** - tworzenie wspolnych danych, nie w kazdym tescie
273
+ 5. **Data Providers** - dla wielu scenariuszy z roznymi danymi
274
+ 6. **Factory states** - `->draft()`, `->published()` zamiast surowych state
275
+ 7. **Laravel fakes** - `Notification::fake()`, `Queue::fake()`, `Event::fake()`, `Mail::fake()`
276
+ 8. **assertDatabaseHas/Missing** - sprawdzaj stan bazy po operacjach
277
+ 9. **actingAs()** - autentykuj uzytkownika w Feature tests
278
+ 10. **Testy w module** - `Modules/Feature/Tests/`, nie w globalnym `tests/`