Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,18 +172,21 @@ All user-facing strings must be translatable.

When writing or refactoring PHPUnit tests:

- Keep production code aligned with real WordPress runtime expectations.
- Do not add test-only `function_exists()` guards for core WordPress APIs just to satisfy PHPUnit.
- If a test environment is missing a WordPress function, add a deterministic stub in `tests/Support/WordPressStubs.php`.
- Never add test-only logic, boot contracts, or test-oriented exceptions/messages to plugin runtime code in `includes/` or `clawpress.php`.
- Do not introduce production `assert_*` helpers, runtime throws, or `function_exists()` guards solely to make tests pass.
- If PHPUnit needs a missing WordPress API, add a deterministic stub in `tests/Support/WordPressStubs.php` instead of changing plugin behavior.
- When a stub needs state, store it in `WordPress_Stubs` and reset it in `WordPress_Stubs::reset()` so tests remain isolated.
- Keep stubs minimal and behavior-focused: enough for assertions without recreating WordPress internals.
- If behavior is truly optional in production (version-gated or feature-detected APIs), keep runtime guards and prefer covering both paths in tests.
- Tests must validate real plugin behavior and observable outcomes, not internal test scaffolding.
- Prefer exercising registered hooks with `do_action()`/`apply_filters()` and asserting side effects (registered menus/routes, enqueued assets, persisted meta/options, scheduler calls).
- Avoid low-value tests that only assert stub presence (for example `function_exists()` contract lists) or only test helper assertions without behavior coverage.
- If behavior is truly optional in production (version-gated or feature-detected APIs), keep runtime guards and cover both paths in tests.

### Verification Expectations

- Run targeted PHPUnit tests for the changed module first.
- Run the full PHPUnit suite when shared test support files (like `WordPressStubs.php`) are changed.
- Prefer assertions that verify integration calls happened (for example submenu registration/removal and metadata checks).
- Prefer assertions that verify integration calls and state transitions happened (for example submenu registration/removal and metadata checks).

## Important Notes

Expand Down
1 change: 0 additions & 1 deletion includes/class-plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ private function __construct() {
// Initialize AI client. Goto Settings -> AI Credentials to set up.
add_action( 'init', [ 'WordPress\AI_Client\AI_Client', 'init' ] );
}

/**
* Get singleton instance.
*/
Expand Down
51 changes: 51 additions & 0 deletions tests/Unit/ActionLogHelperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ function dbDelta( string $queries ): array {
use ClawPress\Helpers\Action_Log_Helper;
use ClawPress\Plugin;
use ClawPress\Tests\Support\TestCase;
use ClawPress\Tests\Support\WordPress_Stubs;

/**
* Minimal wpdb stub for action log helper tests.
Expand Down Expand Up @@ -146,6 +147,56 @@ public function test_plugin_activation_creates_action_log_table(): void {
$this->assertStringContainsString( 'clawpress_action_logs', (string) $GLOBALS['clawpress_test_dbdelta_queries'][0] );
}

public function test_plugin_activation_initializes_default_panel_state_for_current_user_when_missing(): void {
WordPress_Stubs::$current_user_id = 27;

Plugin::activate();

$this->assertArrayHasKey( 27, WordPress_Stubs::$user_meta );
$this->assertArrayHasKey( 'clawpress_panel_state', WordPress_Stubs::$user_meta[27] );
$this->assertSame(
[
'open' => true,
'width' => 420,
'last_history_id' => '',
'welcome_card_seen' => false,
],
WordPress_Stubs::$user_meta[27]['clawpress_panel_state']
);
}

public function test_plugin_activation_does_not_overwrite_existing_panel_state(): void {
WordPress_Stubs::$current_user_id = 48;
WordPress_Stubs::$user_meta[48] = [
'clawpress_panel_state' => [
'open' => false,
'width' => 600,
'last_history_id' => 'history-2',
'welcome_card_seen' => true,
],
];

Plugin::activate();

$this->assertSame(
[
'open' => false,
'width' => 600,
'last_history_id' => 'history-2',
'welcome_card_seen' => true,
],
WordPress_Stubs::$user_meta[48]['clawpress_panel_state']
);
}

public function test_plugin_activation_skips_panel_state_when_no_authenticated_user(): void {
WordPress_Stubs::$current_user_id = 0;

Plugin::activate();

$this->assertArrayNotHasKey( 0, WordPress_Stubs::$user_meta );
}

public function test_log_event_persists_row_into_action_log_table(): void {
$helper = Action_Log_Helper::get_instance();
$result = $helper->log_event(
Expand Down
21 changes: 15 additions & 6 deletions tests/Unit/AdminPageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
namespace ClawPress\Tests\Unit;

use ClawPress\AdminPage\Admin_Page;
use ClawPress\PostTypes\Post_Types;
use ClawPress\Tests\Support\TestCase;
use ClawPress\Tests\Support\WordPress_Stubs;

Expand All @@ -26,11 +27,17 @@ public function test_register_adds_expected_hooks(): void {
}

public function test_register_admin_page_registers_menu_item(): void {
$admin_page = new Admin_Page();
$admin_page->register_admin_page();
new Admin_Page();

do_action( 'admin_menu' );

$this->assertCount( 1, WordPress_Stubs::$menu_pages );
$this->assertSame( 'clawpress', WordPress_Stubs::$menu_pages[0]['menu_slug'] );
$this->assertCount( 1, WordPress_Stubs::$removed_submenu_pages );
$this->assertCount( 3, WordPress_Stubs::$submenu_pages );
$this->assertSame( 'clawpress', WordPress_Stubs::$submenu_pages[0]['menu_slug'] );
$this->assertSame( 'edit.php?post_type=' . Post_Types::AGENT_FILE_POST_TYPE, WordPress_Stubs::$submenu_pages[1]['menu_slug'] );
$this->assertSame( 'edit.php?post_type=' . Post_Types::AGENT_MEMORY_POST_TYPE, WordPress_Stubs::$submenu_pages[2]['menu_slug'] );
}

public function test_render_admin_page_outputs_mount_node(): void {
Expand All @@ -43,16 +50,18 @@ public function test_render_admin_page_outputs_mount_node(): void {
}

public function test_enqueue_admin_assets_bails_for_unrelated_screen(): void {
$admin_page = new Admin_Page();
$admin_page->enqueue_admin_assets( 'dashboard_page' );
new Admin_Page();

do_action( 'admin_enqueue_scripts', 'dashboard_page' );

$this->assertCount( 0, WordPress_Stubs::$enqueued_scripts );
$this->assertCount( 0, WordPress_Stubs::$enqueued_styles );
}

public function test_enqueue_admin_assets_enqueues_script_and_style(): void {
$admin_page = new Admin_Page();
$admin_page->enqueue_admin_assets( 'toplevel_page_clawpress' );
new Admin_Page();

do_action( 'admin_enqueue_scripts', 'toplevel_page_clawpress' );

$this->assertCount( 1, WordPress_Stubs::$enqueued_scripts );
$this->assertCount( 1, WordPress_Stubs::$enqueued_styles );
Expand Down
43 changes: 43 additions & 0 deletions tests/Unit/PluginTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,19 @@
namespace ClawPress\Tests\Unit;

use ClawPress\Plugin;
use ClawPress\PostTypes\Post_Types;
use ClawPress\Tests\Support\TestCase;
use ClawPress\Tests\Support\WordPress_Stubs;

final class PluginTest extends TestCase {
protected function setUp(): void {
parent::setUp();

$instance_property = new \ReflectionProperty( Plugin::class, 'instance' );
$instance_property->setAccessible( true );
$instance_property->setValue( null, null );
}

public function test_get_instance_wires_all_module_hooks_once(): void {
$instance = Plugin::get_instance();
$this->assertSame( $instance, Plugin::get_instance() );
Expand All @@ -34,4 +43,38 @@ public function test_get_instance_wires_all_module_hooks_once(): void {
$this->assertContains( 'action_scheduler_ensure_recurring_actions', $hooks );
$this->assertContains( 'clawpress_heartbeat_tick', $hooks );
}

public function test_plugin_boot_admin_menu_hook_registers_expected_menu_items(): void {
Plugin::get_instance();

do_action( 'admin_menu' );

$this->assertCount( 1, WordPress_Stubs::$menu_pages );
$this->assertSame( 'clawpress', WordPress_Stubs::$menu_pages[0]['menu_slug'] );
$this->assertCount( 1, WordPress_Stubs::$removed_submenu_pages );
$this->assertCount( 3, WordPress_Stubs::$submenu_pages );
$this->assertSame( 'clawpress', WordPress_Stubs::$submenu_pages[0]['menu_slug'] );
$this->assertSame( 'edit.php?post_type=' . Post_Types::AGENT_FILE_POST_TYPE, WordPress_Stubs::$submenu_pages[1]['menu_slug'] );
$this->assertSame( 'edit.php?post_type=' . Post_Types::AGENT_MEMORY_POST_TYPE, WordPress_Stubs::$submenu_pages[2]['menu_slug'] );
}

public function test_plugin_boot_admin_enqueue_scripts_hook_enqueues_admin_and_panel_assets(): void {
Plugin::get_instance();

do_action( 'admin_enqueue_scripts', 'toplevel_page_clawpress' );

$enqueued_script_handles = array_column( WordPress_Stubs::$enqueued_scripts, 'handle' );
$enqueued_style_handles = array_column( WordPress_Stubs::$enqueued_styles, 'handle' );
$localized_script_names = array_column( WordPress_Stubs::$localized_scripts, 'object_name' );

$this->assertCount( 2, WordPress_Stubs::$enqueued_scripts );
$this->assertCount( 2, WordPress_Stubs::$enqueued_styles );
$this->assertCount( 2, WordPress_Stubs::$localized_scripts );
$this->assertContains( 'clawpress', $enqueued_script_handles );
$this->assertContains( 'clawpress-panel', $enqueued_script_handles );
$this->assertContains( 'clawpress', $enqueued_style_handles );
$this->assertContains( 'clawpress-panel', $enqueued_style_handles );
$this->assertContains( 'CLAWPRESS_ADMIN', $localized_script_names );
$this->assertContains( 'CLAWPRESS_PANEL', $localized_script_names );
}
}