@silverassist/agents-toolkit 2.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 (51) hide show
  1. package/LICENSE +135 -0
  2. package/README.md +388 -0
  3. package/bin/cli.js +940 -0
  4. package/package.json +52 -0
  5. package/src/index.js +58 -0
  6. package/templates/agents/AGENTS.codex.md +195 -0
  7. package/templates/agents/AGENTS.md +195 -0
  8. package/templates/agents/CLAUDE.md +213 -0
  9. package/templates/agents/copilot-instructions.md +83 -0
  10. package/templates/shared/instructions/css-styling.instructions.md +142 -0
  11. package/templates/shared/instructions/documentation-language.instructions.md +216 -0
  12. package/templates/shared/instructions/github-workflow.instructions.md +245 -0
  13. package/templates/shared/instructions/php-standards.instructions.md +293 -0
  14. package/templates/shared/instructions/react-components.instructions.md +196 -0
  15. package/templates/shared/instructions/server-actions.instructions.md +429 -0
  16. package/templates/shared/instructions/testing-standards.instructions.md +264 -0
  17. package/templates/shared/instructions/tests.instructions.md +103 -0
  18. package/templates/shared/instructions/typescript.instructions.md +106 -0
  19. package/templates/shared/instructions/wordpress-plugin-architecture.instructions.md +238 -0
  20. package/templates/shared/prompts/README.md +129 -0
  21. package/templates/shared/prompts/_partials/README.md +57 -0
  22. package/templates/shared/prompts/_partials/documentation.md +203 -0
  23. package/templates/shared/prompts/_partials/git-operations.md +171 -0
  24. package/templates/shared/prompts/_partials/github-integration.md +100 -0
  25. package/templates/shared/prompts/_partials/jira-integration.md +155 -0
  26. package/templates/shared/prompts/_partials/pr-template.md +216 -0
  27. package/templates/shared/prompts/_partials/validations.md +87 -0
  28. package/templates/shared/prompts/add-tests.prompt.md +151 -0
  29. package/templates/shared/prompts/analyze-github-issue.prompt.md +73 -0
  30. package/templates/shared/prompts/analyze-ticket.prompt.md +63 -0
  31. package/templates/shared/prompts/create-plan.prompt.md +118 -0
  32. package/templates/shared/prompts/create-pr.prompt.md +139 -0
  33. package/templates/shared/prompts/finalize-pr.prompt.md +150 -0
  34. package/templates/shared/prompts/fix-issues.prompt.md +92 -0
  35. package/templates/shared/prompts/new-wp-component.prompt.md +51 -0
  36. package/templates/shared/prompts/new-wp-plugin.prompt.md +46 -0
  37. package/templates/shared/prompts/prepare-pr.prompt.md +123 -0
  38. package/templates/shared/prompts/prepare-release.prompt.md +74 -0
  39. package/templates/shared/prompts/quality-check.prompt.md +45 -0
  40. package/templates/shared/prompts/review-code.prompt.md +81 -0
  41. package/templates/shared/prompts/work-github-issue.prompt.md +96 -0
  42. package/templates/shared/prompts/work-ticket.prompt.md +98 -0
  43. package/templates/shared/skills/README.md +53 -0
  44. package/templates/shared/skills/component-architecture/SKILL.md +321 -0
  45. package/templates/shared/skills/create-component/SKILL.md +714 -0
  46. package/templates/shared/skills/domain-driven-design/SKILL.md +277 -0
  47. package/templates/shared/skills/plugin-creation/SKILL.md +1094 -0
  48. package/templates/shared/skills/quality-checks/SKILL.md +493 -0
  49. package/templates/shared/skills/release-management/SKILL.md +649 -0
  50. package/templates/shared/skills/testing/SKILL.md +682 -0
  51. package/templates/shared/skills/testing-patterns/SKILL.md +243 -0
@@ -0,0 +1,682 @@
1
+ ---
2
+ name: testing
3
+ description: Write and run PHPUnit tests for Silver Assist WordPress plugins. Covers WordPress Test Suite integration, test bootstrap, base TestCase class, factory patterns, database schema handling, test naming conventions, and common testing patterns. Use when writing new tests, debugging test failures, or setting up test infrastructure.
4
+ ---
5
+
6
+ # Silver Assist — Testing
7
+
8
+ This skill covers the testing strategy for Silver Assist WordPress plugins, including PHPUnit configuration, WordPress Test Suite integration, test patterns, and best practices.
9
+
10
+ ## When to Use
11
+
12
+ - Writing new unit or integration tests
13
+ - Setting up test infrastructure for a new plugin
14
+ - Debugging test failures
15
+ - Understanding WordPress Test Suite patterns
16
+ - Dealing with database operations in tests
17
+ - Running tests locally or in CI
18
+
19
+ ## Prerequisites
20
+
21
+ - `composer install` completed
22
+ - For integration tests: WordPress Test Suite installed
23
+ - PHPUnit 9.6+ (via Composer)
24
+ - yoast/phpunit-polyfills (via Composer)
25
+
26
+ ---
27
+
28
+ ## Test Infrastructure Setup
29
+
30
+ ### PHPUnit Configuration
31
+
32
+ File: `phpunit.xml.dist`
33
+
34
+ ```xml
35
+ <?xml version="1.0" encoding="UTF-8"?>
36
+ <phpunit
37
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
38
+ xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.6/phpunit.xsd"
39
+ bootstrap="tests/bootstrap.php"
40
+ colors="true"
41
+ verbose="true"
42
+ stopOnFailure="false"
43
+ stopOnError="false"
44
+ beStrictAboutOutputDuringTests="true"
45
+ beStrictAboutTodoAnnotatedTests="true">
46
+
47
+ <testsuites>
48
+ <testsuite name="unit">
49
+ <directory suffix="Test.php">./tests/Unit</directory>
50
+ </testsuite>
51
+ <testsuite name="integration">
52
+ <directory suffix="Test.php">./tests/Integration</directory>
53
+ </testsuite>
54
+ <testsuite name="all">
55
+ <directory suffix="Test.php">./tests/Unit</directory>
56
+ <directory suffix="Test.php">./tests/Integration</directory>
57
+ </testsuite>
58
+ </testsuites>
59
+
60
+ <groups>
61
+ <exclude>
62
+ <group>ajax</group>
63
+ <group>ms-files</group>
64
+ <group>external-http</group>
65
+ </exclude>
66
+ </groups>
67
+
68
+ <coverage processUncoveredFiles="true">
69
+ <include>
70
+ <directory suffix=".php">./includes</directory>
71
+ </include>
72
+ <exclude>
73
+ <directory>./vendor</directory>
74
+ <directory>./tests</directory>
75
+ </exclude>
76
+ </coverage>
77
+
78
+ <php>
79
+ <env name="WP_ENVIRONMENT_TYPE" value="test"/>
80
+ <env name="WP_DEBUG" value="true"/>
81
+ <env name="WP_DEBUG_LOG" value="false"/>
82
+ <env name="WP_DEBUG_DISPLAY" value="false"/>
83
+ <env name="SCRIPT_DEBUG" value="false"/>
84
+ <const name="WP_TESTS_PHPUNIT_POLYFILLS_PATH" value="vendor/yoast/phpunit-polyfills"/>
85
+ <const name="PLUGIN_PREFIX_TESTING" value="true"/>
86
+ </php>
87
+ </phpunit>
88
+ ```
89
+
90
+ **IMPORTANT**: Replace `PLUGIN_PREFIX_TESTING` with your actual plugin constant prefix.
91
+
92
+ ### Test Bootstrap
93
+
94
+ File: `tests/bootstrap.php`
95
+
96
+ ```php
97
+ <?php
98
+ /**
99
+ * PHPUnit Bootstrap for SilverAssist Plugin.
100
+ *
101
+ * @package SilverAssist\PluginName
102
+ */
103
+
104
+ // Composer autoloader for stubs and dependencies.
105
+ require_once dirname( __DIR__ ) . '/vendor/autoload.php';
106
+
107
+ // Define test constants.
108
+ if ( ! defined( 'PLUGIN_PREFIX_TESTING' ) ) {
109
+ define( 'PLUGIN_PREFIX_TESTING', true );
110
+ }
111
+
112
+ define( 'PLUGIN_PREFIX_TESTS_DIR', __DIR__ );
113
+ define( 'PLUGIN_PREFIX_PLUGIN_DIR', dirname( __DIR__ ) );
114
+ define( 'PLUGIN_PREFIX_PLUGIN_FILE', PLUGIN_PREFIX_PLUGIN_DIR . '/plugin-main-file.php' );
115
+
116
+ // Define plugin constants (normally defined in main plugin file).
117
+ if ( ! defined( 'PLUGIN_PREFIX_VERSION' ) ) {
118
+ define( 'PLUGIN_PREFIX_VERSION', '1.0.0' );
119
+ }
120
+ if ( ! defined( 'PLUGIN_PREFIX_FILE' ) ) {
121
+ define( 'PLUGIN_PREFIX_FILE', PLUGIN_PREFIX_PLUGIN_FILE );
122
+ }
123
+ if ( ! defined( 'PLUGIN_PREFIX_PATH' ) ) {
124
+ define( 'PLUGIN_PREFIX_PATH', PLUGIN_PREFIX_PLUGIN_DIR . '/' );
125
+ }
126
+ if ( ! defined( 'PLUGIN_PREFIX_BASENAME' ) ) {
127
+ define( 'PLUGIN_PREFIX_BASENAME', 'plugin-name/plugin-main-file.php' );
128
+ }
129
+
130
+ // WordPress test environment check.
131
+ $_tests_dir = getenv( 'WP_TESTS_DIR' );
132
+
133
+ if ( ! $_tests_dir ) {
134
+ $_tests_dir = rtrim( sys_get_temp_dir(), '/\\' ) . '/wordpress-tests-lib';
135
+ }
136
+
137
+ // Check if WordPress test suite is available.
138
+ $wp_tests_available = false;
139
+ $_tests_includes_dir = null;
140
+
141
+ if ( file_exists( $_tests_dir . '/includes/functions.php' ) ) {
142
+ // Standard wordpress-tests-lib structure.
143
+ $wp_tests_available = true;
144
+ $_tests_includes_dir = $_tests_dir . '/includes';
145
+ } elseif ( file_exists( $_tests_dir . '/tests/phpunit/includes/functions.php' ) ) {
146
+ // wordpress-develop repository structure.
147
+ $wp_tests_available = true;
148
+ $_tests_includes_dir = $_tests_dir . '/tests/phpunit/includes';
149
+ }
150
+
151
+ /**
152
+ * Manually load the plugin being tested.
153
+ */
154
+ function _manually_load_plugin() {
155
+ // Load composer autoloader.
156
+ if ( file_exists( PLUGIN_PREFIX_PLUGIN_DIR . '/vendor/autoload.php' ) ) {
157
+ require_once PLUGIN_PREFIX_PLUGIN_DIR . '/vendor/autoload.php';
158
+ }
159
+
160
+ // Note: Do NOT load main plugin file here.
161
+ // It will be loaded by WordPress Test Suite after WordPress loads.
162
+ }
163
+
164
+ if ( $wp_tests_available ) {
165
+ // Load WordPress Test Suite.
166
+ require_once $_tests_includes_dir . '/functions.php';
167
+
168
+ tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' );
169
+
170
+ // Start WordPress Test Suite.
171
+ require_once $_tests_includes_dir . '/bootstrap.php';
172
+ } else {
173
+ // WordPress Test Suite not available - tests will use mocks.
174
+ echo "Warning: WordPress Test Suite not found. Tests will run with limited functionality.\n";
175
+ _manually_load_plugin();
176
+ }
177
+ ```
178
+
179
+ **Key Points:**
180
+ - Constants are defined with `if ( ! defined() )` guards to allow phpunit.xml.dist to set them
181
+ - WordPress Test Suite is auto-detected from `$WP_TESTS_DIR` env var or `/tmp/wordpress-tests-lib`
182
+ - Plugin is loaded via `muplugins_loaded` filter (NOT directly in bootstrap)
183
+ - Do NOT load the main plugin file in `_manually_load_plugin()` — WordPress Test Suite handles this
184
+
185
+ ### Base Test Class
186
+
187
+ File: `tests/Helpers/TestCase.php`
188
+
189
+ ```php
190
+ <?php
191
+ /**
192
+ * Base Test Case for SilverAssist Plugin.
193
+ *
194
+ * @package SilverAssist\PluginName
195
+ * @subpackage Tests\Helpers
196
+ */
197
+
198
+ namespace SilverAssist\PluginName\Tests\Helpers;
199
+
200
+ /**
201
+ * Base test case using WordPress Test Suite.
202
+ *
203
+ * All tests extend this class to have access to WordPress functions,
204
+ * factory methods, and proper database transaction rollback.
205
+ */
206
+ abstract class TestCase extends \WP_UnitTestCase {
207
+ // Base test functionality.
208
+ // Add shared setup/teardown and helper methods here.
209
+ }
210
+ ```
211
+
212
+ ### Directory Structure
213
+
214
+ ```
215
+ tests/
216
+ ├── bootstrap.php # Test bootstrap
217
+ ├── README.md # Testing documentation
218
+ ├── Unit/ # Unit tests (mirrors includes/ structure)
219
+ │ ├── Core/
220
+ │ │ └── PluginTest.php
221
+ │ └── Service/
222
+ │ └── ServiceNameTest.php
223
+ ├── Integration/ # Integration tests
224
+ │ └── Admin/
225
+ │ └── SettingsTest.php
226
+ └── Helpers/ # Test utilities
227
+ ├── TestCase.php # Base test class
228
+ └── Helpers.php # Test helper functions
229
+ ```
230
+
231
+ ---
232
+
233
+ ## Test Naming Conventions
234
+
235
+ ### Lifecycle Methods (WordPress snake_case)
236
+
237
+ Since TestCase extends `WP_UnitTestCase`, use **WordPress-style snake_case** for lifecycle methods:
238
+
239
+ ```php
240
+ // ✅ CORRECT: WordPress snake_case style (WP_UnitTestCase).
241
+ public function set_up(): void {
242
+ parent::set_up();
243
+ // Setup code.
244
+ }
245
+
246
+ public function tear_down(): void {
247
+ // Cleanup code.
248
+ parent::tear_down();
249
+ }
250
+
251
+ public static function set_up_before_class(): void {
252
+ parent::set_up_before_class();
253
+ }
254
+
255
+ public static function tear_down_after_class(): void {
256
+ parent::tear_down_after_class();
257
+ }
258
+
259
+ // ❌ WRONG: PHPUnit camelCase style (don't use with WP_UnitTestCase).
260
+ public function setUp(): void // WRONG
261
+ public function tearDown(): void // WRONG
262
+ ```
263
+
264
+ ### Test Methods (camelCase)
265
+
266
+ ```php
267
+ // ✅ Test methods use camelCase.
268
+ public function testMethodReturnsExpectedValue(): void {
269
+ // Test implementation.
270
+ }
271
+
272
+ public function testActivatorCreatesDefaultOptions(): void {
273
+ // Test implementation.
274
+ }
275
+ ```
276
+
277
+ ### Test File Naming
278
+
279
+ - Files: `ClassNameTest.php` (matches class being tested + `Test` suffix)
280
+ - Location: mirrors `includes/` structure under `tests/Unit/` or `tests/Integration/`
281
+
282
+ Example:
283
+ - `includes/Core/Plugin.php` → `tests/Unit/Core/PluginTest.php`
284
+ - `includes/Service/FormHandler.php` → `tests/Unit/Service/FormHandlerTest.php`
285
+
286
+ ---
287
+
288
+ ## WordPress Factory Pattern
289
+
290
+ **CRITICAL**: Use `static::factory()` — NOT `$this->factory` (deprecated since WordPress 6.1+).
291
+
292
+ ### Creating Test Data
293
+
294
+ ```php
295
+ public function set_up(): void {
296
+ parent::set_up();
297
+
298
+ // Create admin user.
299
+ $this->admin_user_id = static::factory()->user->create( [
300
+ 'role' => 'administrator',
301
+ ] );
302
+ \wp_set_current_user( $this->admin_user_id );
303
+
304
+ // Create test post.
305
+ $this->post_id = static::factory()->post->create( [
306
+ 'post_title' => 'Test Post',
307
+ 'post_status' => 'publish',
308
+ 'post_type' => 'post',
309
+ ] );
310
+ }
311
+ ```
312
+
313
+ ### Available Factories
314
+
315
+ ```php
316
+ // Posts.
317
+ static::factory()->post->create( [ 'post_title' => 'Test', 'post_status' => 'publish' ] );
318
+
319
+ // Users.
320
+ static::factory()->user->create( [ 'role' => 'administrator' ] );
321
+ static::factory()->user->create( [ 'role' => 'editor' ] );
322
+ static::factory()->user->create( [ 'role' => 'subscriber' ] );
323
+
324
+ // Comments.
325
+ static::factory()->comment->create( [ 'comment_post_ID' => $post_id ] );
326
+
327
+ // Terms.
328
+ static::factory()->term->create( [ 'taxonomy' => 'category', 'name' => 'Test Category' ] );
329
+
330
+ // Categories.
331
+ static::factory()->category->create( [ 'name' => 'Test Category' ] );
332
+
333
+ // Create multiple at once.
334
+ $post_ids = static::factory()->post->create_many( 5, [ 'post_status' => 'publish' ] );
335
+ ```
336
+
337
+ ### Create and Get
338
+
339
+ ```php
340
+ // create() returns ID only.
341
+ $post_id = static::factory()->post->create( [ ... ] );
342
+
343
+ // create_and_get() returns full object.
344
+ $post = static::factory()->post->create_and_get( [ ... ] );
345
+ echo $post->post_title;
346
+ ```
347
+
348
+ ---
349
+
350
+ ## Real WordPress Functions in Tests
351
+
352
+ With `WP_UnitTestCase`, you have access to ALL WordPress functions:
353
+
354
+ ```php
355
+ // Options API.
356
+ \update_option( 'key', 'value' );
357
+ $value = \get_option( 'key' );
358
+ \delete_option( 'key' );
359
+
360
+ // User functions.
361
+ \wp_set_current_user( $user_id );
362
+ $can_edit = \current_user_can( 'edit_posts' );
363
+
364
+ // Post functions.
365
+ \wp_delete_post( $post_id, true );
366
+ $post = \get_post( $post_id );
367
+
368
+ // Hooks.
369
+ \has_action( 'hook_name', $callback );
370
+ \has_filter( 'filter_name', $callback );
371
+ \add_action( 'hook_name', $callback );
372
+ \do_action( 'hook_name', $args );
373
+
374
+ // Transients.
375
+ \set_transient( 'key', 'value', 3600 );
376
+ $value = \get_transient( 'key' );
377
+ \delete_transient( 'key' );
378
+ ```
379
+
380
+ ---
381
+
382
+ ## Database Schema Changes in Tests
383
+
384
+ ### The Problem
385
+
386
+ **CRITICAL**: `CREATE TABLE` triggers an implicit MySQL `COMMIT` which breaks WordPress Test Suite's transaction-based rollback.
387
+
388
+ - WordPress Test Suite wraps each test in `START TRANSACTION`
389
+ - After each test, it `ROLLBACK`s to restore clean state
390
+ - `CREATE TABLE` triggers implicit COMMIT, breaking rollback
391
+ - Result: Tables persist incorrectly or disappear between tests
392
+
393
+ ### The Solution: `wpSetUpBeforeClass()`
394
+
395
+ Use `wpSetUpBeforeClass()` for DDL statements — it runs ONCE before all tests in the class, outside the transaction system:
396
+
397
+ ```php
398
+ class YourTest extends TestCase {
399
+ /**
400
+ * Create shared fixtures before class.
401
+ *
402
+ * Runs ONCE before any tests in the class.
403
+ * Use this for CREATE TABLE statements.
404
+ *
405
+ * @param WP_UnitTest_Factory $factory Factory instance.
406
+ */
407
+ public static function wpSetUpBeforeClass( $factory ): void {
408
+ global $wpdb;
409
+
410
+ // ✅ CREATE TABLE happens outside transaction system.
411
+ // Safe to use here - runs once before all tests.
412
+ Activator::create_tables();
413
+ }
414
+
415
+ /**
416
+ * Setup test environment.
417
+ *
418
+ * Runs BEFORE EACH test. DO NOT create tables here.
419
+ */
420
+ public function set_up(): void {
421
+ parent::set_up();
422
+
423
+ // ✅ TRUNCATE is safe - doesn't trigger COMMIT.
424
+ $this->clean_table_data();
425
+ }
426
+
427
+ /**
428
+ * Clean table data.
429
+ *
430
+ * @return void
431
+ */
432
+ protected function clean_table_data(): void {
433
+ global $wpdb;
434
+ $table_name = $wpdb->prefix . 'your_table';
435
+
436
+ // TRUNCATE doesn't trigger COMMIT - safe for test isolation.
437
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery
438
+ $wpdb->query( $wpdb->prepare( 'TRUNCATE TABLE %i', $table_name ) );
439
+ }
440
+ }
441
+ ```
442
+
443
+ ### MySQL Statements Reference
444
+
445
+ **Trigger Implicit COMMIT (use only in `wpSetUpBeforeClass`):**
446
+ - `CREATE TABLE` / `DROP TABLE`
447
+ - `CREATE DATABASE` / `DROP DATABASE`
448
+ - `ALTER TABLE`
449
+ - `RENAME TABLE`
450
+
451
+ **Safe for `set_up()` / `tear_down()`:**
452
+ - `TRUNCATE TABLE` (safe in WordPress Test Suite context)
453
+ - `INSERT` / `UPDATE` / `DELETE` (regular DML)
454
+ - `SELECT` queries
455
+
456
+ ---
457
+
458
+ ## Common Test Patterns
459
+
460
+ ### Testing a Singleton Component
461
+
462
+ ```php
463
+ <?php
464
+ namespace SilverAssist\PluginName\Tests\Unit\Core;
465
+
466
+ use SilverAssist\PluginName\Tests\Helpers\TestCase;
467
+ use SilverAssist\PluginName\Core\Plugin;
468
+
469
+ class PluginTest extends TestCase {
470
+ /**
471
+ * Test singleton returns same instance.
472
+ */
473
+ public function testInstanceReturnsSameObject(): void {
474
+ $instance1 = Plugin::instance();
475
+ $instance2 = Plugin::instance();
476
+
477
+ $this->assertSame( $instance1, $instance2 );
478
+ }
479
+
480
+ /**
481
+ * Test plugin implements LoadableInterface.
482
+ */
483
+ public function testImplementsLoadableInterface(): void {
484
+ $plugin = Plugin::instance();
485
+
486
+ $this->assertInstanceOf(
487
+ \SilverAssist\PluginName\Core\Interfaces\LoadableInterface::class,
488
+ $plugin
489
+ );
490
+ }
491
+
492
+ /**
493
+ * Test priority value.
494
+ */
495
+ public function testGetPriorityReturnsTen(): void {
496
+ $plugin = Plugin::instance();
497
+
498
+ $this->assertSame( 10, $plugin->get_priority() );
499
+ }
500
+ }
501
+ ```
502
+
503
+ ### Testing Options/Settings
504
+
505
+ ```php
506
+ public function testActivatorSetsDefaultOptions(): void {
507
+ // Ensure option doesn't exist.
508
+ \delete_option( 'plugin_prefix_settings' );
509
+
510
+ // Run activation.
511
+ Activator::activate();
512
+
513
+ // Verify defaults were set.
514
+ $settings = \get_option( 'plugin_prefix_settings' );
515
+ $this->assertIsArray( $settings );
516
+ $this->assertTrue( $settings['enabled'] );
517
+ }
518
+ ```
519
+
520
+ ### Testing Hooks Registration
521
+
522
+ ```php
523
+ public function testInitRegistersHooks(): void {
524
+ $instance = MyComponent::instance();
525
+ $instance->init();
526
+
527
+ $this->assertNotFalse(
528
+ \has_action( 'admin_menu', [ $instance, 'register_menu' ] )
529
+ );
530
+ $this->assertNotFalse(
531
+ \has_filter( 'plugin_action_links', [ $instance, 'add_action_links' ] )
532
+ );
533
+ }
534
+ ```
535
+
536
+ ### Testing Capability Checks
537
+
538
+ ```php
539
+ public function testNonAdminCannotAccessSettings(): void {
540
+ // Create subscriber user.
541
+ $subscriber_id = static::factory()->user->create( [ 'role' => 'subscriber' ] );
542
+ \wp_set_current_user( $subscriber_id );
543
+
544
+ // Should not pass capability check.
545
+ $this->assertFalse( \current_user_can( 'manage_options' ) );
546
+ }
547
+
548
+ public function testAdminCanAccessSettings(): void {
549
+ // Create admin user.
550
+ $admin_id = static::factory()->user->create( [ 'role' => 'administrator' ] );
551
+ \wp_set_current_user( $admin_id );
552
+
553
+ $this->assertTrue( \current_user_can( 'manage_options' ) );
554
+ }
555
+ ```
556
+
557
+ ### Testing Database Operations
558
+
559
+ ```php
560
+ public static function wpSetUpBeforeClass( $factory ): void {
561
+ Activator::create_tables();
562
+ }
563
+
564
+ public function set_up(): void {
565
+ parent::set_up();
566
+ $this->clean_table_data();
567
+ }
568
+
569
+ public function testInsertAndRetrieveRecord(): void {
570
+ global $wpdb;
571
+ $table = $wpdb->prefix . 'plugin_table';
572
+
573
+ // Insert.
574
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery
575
+ $wpdb->insert(
576
+ $table,
577
+ [ 'created_at' => current_time( 'mysql' ) ],
578
+ [ '%s' ]
579
+ );
580
+
581
+ $id = $wpdb->insert_id;
582
+ $this->assertGreaterThan( 0, $id );
583
+
584
+ // Retrieve.
585
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery
586
+ $result = $wpdb->get_row(
587
+ $wpdb->prepare( 'SELECT * FROM %i WHERE id = %d', $table, $id )
588
+ );
589
+
590
+ $this->assertNotNull( $result );
591
+ $this->assertEquals( $id, $result->id );
592
+ }
593
+ ```
594
+
595
+ ---
596
+
597
+ ## Running Tests
598
+
599
+ ### Local Development
600
+
601
+ ```bash
602
+ # Run all tests.
603
+ vendor/bin/phpunit
604
+
605
+ # Run specific test suite.
606
+ vendor/bin/phpunit --testsuite unit
607
+ vendor/bin/phpunit --testsuite integration
608
+
609
+ # Run specific test file.
610
+ vendor/bin/phpunit tests/Unit/Core/PluginTest.php
611
+
612
+ # Run specific test method.
613
+ vendor/bin/phpunit --filter testMethodName
614
+
615
+ # With coverage (requires xdebug).
616
+ vendor/bin/phpunit --coverage-html coverage/
617
+ vendor/bin/phpunit --coverage-text
618
+
619
+ # Human-readable output.
620
+ vendor/bin/phpunit --testdox
621
+ ```
622
+
623
+ ### WordPress Test Suite Installation
624
+
625
+ ```bash
626
+ bash scripts/install-wp-tests.sh <db-name> <db-user> <db-pass> <db-host> <wp-version> <skip-database-creation>
627
+
628
+ # Example:
629
+ bash scripts/install-wp-tests.sh wordpress_test root 'root' localhost latest true
630
+ ```
631
+
632
+ ### CI Pipeline
633
+
634
+ Tests run automatically in CI via `quality-checks.yml`:
635
+ - PHP 8.2 (with coverage)
636
+ - PHP 8.3
637
+ - PHP 8.4
638
+ - WordPress compatibility matrix (6.5, 6.6, 6.7, latest)
639
+
640
+ ---
641
+
642
+ ## Troubleshooting
643
+
644
+ ### "Class WP_UnitTestCase not found"
645
+
646
+ WordPress Test Suite is not installed. Install it or set `$WP_TESTS_DIR`:
647
+
648
+ ```bash
649
+ export WP_TESTS_DIR=/tmp/wordpress-tests-lib
650
+ bash scripts/install-wp-tests.sh wordpress_test root 'root' localhost latest true
651
+ ```
652
+
653
+ ### Tests pass locally but fail in CI
654
+
655
+ Common causes:
656
+ 1. **Missing `set_up()` parent call**: Always call `parent::set_up()` first
657
+ 2. **State leaking between tests**: Use `tear_down()` to clean up
658
+ 3. **Database table issues**: Use `wpSetUpBeforeClass()` for DDL statements
659
+ 4. **Timezone differences**: Use `current_time('mysql')` instead of `date()`
660
+
661
+ ### "Table already exists" errors
662
+
663
+ Don't create tables in `set_up()`. Use `wpSetUpBeforeClass()` which runs once:
664
+
665
+ ```php
666
+ public static function wpSetUpBeforeClass( $factory ): void {
667
+ Activator::create_tables(); // Uses dbDelta which handles "IF NOT EXISTS" internally.
668
+ }
669
+ ```
670
+
671
+ ### "Cannot modify header information"
672
+
673
+ Test is producing output before headers. Check for:
674
+ - `echo` statements in tested code
675
+ - Missing output buffering
676
+ - `beStrictAboutOutputDuringTests="true"` in phpunit.xml.dist (expected behavior)
677
+
678
+ ### Slow tests
679
+
680
+ - Run only unit tests: `vendor/bin/phpunit --testsuite unit`
681
+ - Use `--filter` for specific tests
682
+ - Skip WordPress setup for non-integration tests: use `--skip-wp-setup` flag