-
Notifications
You must be signed in to change notification settings - Fork 110
fix: follow-ups from review of tonight's core-beta merges #521
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
abd6bda
8748668
ff5767a
6efc2ec
29ca13f
7e8eb97
73a8c61
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -101,6 +101,22 @@ function clean_snippets_cache( string $table_name ) { | |
| * @return bool Whether the group was flushed. | ||
| */ | ||
| function flush_cache_group( string $group ): bool { | ||
| /** | ||
| * Short-circuits flushing a cache group. | ||
| * | ||
| * Returning a boolean skips the object cache entirely: false makes the | ||
| * caller fall back to deleting the known keys one by one, for a cache | ||
| * that reports group support it does not really have. | ||
| * | ||
| * @param bool|null $flushed Whether the group was flushed, or null to let the cache try. | ||
| * @param string $group Cache group. | ||
| */ | ||
| $flushed = apply_filters( 'code_snippets/pre_flush_cache_group', null, $group ); | ||
|
|
||
| if ( null !== $flushed ) { | ||
| return (bool) $flushed; | ||
|
Comment on lines
+114
to
+117
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift Handle a failed flush for every cache group. Line 114 applies the filter to previous-version and legacy groups. If the filter returns 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| if ( ! function_exists( 'wp_cache_flush_group' ) || | ||
| ! function_exists( 'wp_cache_supports' ) || | ||
| ! wp_cache_supports( 'flush_group' ) ) { | ||
|
|
@@ -142,10 +158,12 @@ function flush_versioned_cache_groups( string $previous_version ): void { | |
| * @return void | ||
| */ | ||
| function flush_known_cache_keys(): void { | ||
| clean_snippets_cache( code_snippets()->db->get_table_name( false ) ); | ||
| // Both tables' keys go, whether or not this is a network: deleting a key | ||
| // that was never written costs nothing, and it keeps one path to test. | ||
| $tables = [ code_snippets()->db->get_table_name( false ), code_snippets()->db->get_table_name( true ) ]; | ||
|
|
||
| if ( is_multisite() ) { | ||
| clean_snippets_cache( code_snippets()->db->get_table_name( true ) ); | ||
| foreach ( array_unique( $tables ) as $table ) { | ||
| clean_snippets_cache( $table ); | ||
| } | ||
|
|
||
| wp_cache_delete( Settings\CACHE_KEY, CACHE_GROUP ); | ||
|
|
@@ -914,8 +932,13 @@ function get_snippet_by_cloud_id( string $cloud_id, ?bool $multisite = null ): ? | |
| */ | ||
| function normalize_snippet_code( string $code, string $type ): string { | ||
| // A markdown fence around the whole snippet, as copied from a chat window. | ||
| $code = preg_replace( '/\A\s*```[a-z]*[ \t]*\R/i', '', $code ); | ||
| $code = preg_replace( '/\R\s*```\s*\z/', '', $code ); | ||
| // The closing fence only goes when an opening one was there: on its own it | ||
| // is the author's content, as in an HTML snippet ending in backticks. | ||
| $code = preg_replace( '/\A\s*```[a-z]*[ \t]*\R/i', '', $code, 1, $fenced ); | ||
|
|
||
| if ( $fenced ) { | ||
| $code = preg_replace( '/\R\s*```\s*\z/', '', $code ); | ||
| } | ||
|
|
||
| switch ( $type ) { | ||
| case 'php': | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -101,7 +101,10 @@ private function single_use( string $code ): Snippet { | |
| $snippet->code = $code; | ||
| $snippet->active = false; | ||
|
|
||
| return save_snippet( $snippet ); | ||
| $saved = save_snippet( $snippet ); | ||
| $this->assertNotNull( $saved, 'the single-use snippet must save before the test can run it' ); | ||
|
|
||
| return $saved; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -187,4 +190,20 @@ public function test_heartbeat_refreshes_the_nonce(): void { | |
| wp_set_current_user( self::factory()->user->create( [ 'role' => 'subscriber' ] ) ); | ||
| $this->assertArrayNotHasKey( 'code_snippets_run_once_nonce', $menu->refresh_run_once_nonce( [] ) ); | ||
| } | ||
|
|
||
| /** | ||
| * A user without the capability is refused even with a nonce of their own. | ||
| * | ||
| * @return void | ||
| */ | ||
| public function test_capability_is_required_even_with_a_valid_nonce(): void { | ||
| $snippet = $this->single_use( 'update_option( "run_once_ran", "yes" );' ); | ||
|
|
||
| wp_set_current_user( self::factory()->user->create( [ 'role' => 'subscriber' ] ) ); | ||
| $own_nonce = wp_create_nonce( Manage_Menu::RUN_ONCE_NONCE ); | ||
|
|
||
| $this->assertNull( $this->run_once_request( $snippet->id, $own_nonce ) ); | ||
| $this->assertFalse( (bool) get_snippet( $snippet->id )->active ); | ||
| $this->assertFalse( get_option( 'run_once_ran' ) ); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Reset the In Proposed fix public function set_up() {
parent::set_up();
wp_set_current_user( self::factory()->user->create( [ 'role' => 'administrator' ] ) );
+ delete_option( 'run_once_ran' );
$this->redirected_to = '';
add_filter( 'wp_redirect', [ $this, 'capture_redirect' ] );
}
public function tear_down() {
remove_filter( 'wp_redirect', [ $this, 'capture_redirect' ] );
remove_all_filters( 'code_snippets/execute_snippets' );
+ delete_option( 'run_once_ran' );
$_REQUEST = [];
parent::tear_down();
}As per path instructions, keep tests deterministic and reset fixtures, filters, and request state. 🧰 Tools🪛 PHPMD (2.15.0)[error] 15-209: The class Manage_Menu_Run_Once_Test is not named in CamelCase. (undefined) (CamelCaseClassName) [error] 15-209: The property $redirected_to is not named in camelCase. (undefined) (CamelCasePropertyName) [error] 199-208: The method test_capability_is_required_even_with_a_valid_nonce is not named in camelCase. (undefined) (CamelCaseMethodName) 🤖 Prompt for AI AgentsSource: Path instructions |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| <?php | ||
| /** | ||
| * Tests for the task-based settings layout. | ||
| * | ||
| * @package Code_Snippets | ||
| */ | ||
|
|
||
| namespace Code_Snippets\Settings; | ||
|
|
||
| use Code_Snippets\Admin\Menus\Settings_Menu; | ||
| use Code_Snippets\UnitTestCase; | ||
| use ReflectionClass; | ||
|
|
||
| /** | ||
| * Tabs only show fields that exist and apply; headings and labels render once and escaped. | ||
| * | ||
| * @group settings | ||
| */ | ||
| class Settings_Layout_Test extends UnitTestCase { | ||
|
|
||
| /** | ||
| * Remove what a test registered. | ||
| * | ||
| * @return void | ||
| */ | ||
| public function tear_down() { | ||
| global $wp_settings_fields, $wp_settings_sections; | ||
|
|
||
| unset( $wp_settings_fields[ Settings_Menu::SETTINGS_PAGE ]['layout-test'], $wp_settings_sections[ Settings_Menu::SETTINGS_PAGE ] ); | ||
| unset( $_REQUEST['section'] ); | ||
| remove_all_filters( 'code_snippets_settings_tab_contents' ); | ||
| remove_all_filters( 'code_snippets_settings_tabs' ); | ||
| parent::tear_down(); | ||
| } | ||
|
|
||
| /** | ||
| * A field the definitions do not know, and one whose condition is not met, are left out. | ||
| * | ||
| * @return void | ||
| */ | ||
| public function test_undefined_and_hidden_fields_are_left_out(): void { | ||
| add_filter( | ||
| 'code_snippets_settings_tab_contents', | ||
| static function ( array $contents ): array { | ||
| $contents['interface'][] = [ 'general', 'no_such_field' ]; | ||
| return $contents; | ||
| } | ||
| ); | ||
|
|
||
| $settings = Settings_Fields::get_default_values(); | ||
|
|
||
| $settings['general']['enable_admin_bar'] = false; | ||
| $hidden = Settings_Layout::get_visible_fields( 'interface', $settings ); | ||
|
|
||
| $settings['general']['enable_admin_bar'] = true; | ||
| $shown = Settings_Layout::get_visible_fields( 'interface', $settings ); | ||
|
|
||
| $this->assertNotContains( [ 'general', 'no_such_field' ], $shown, 'a field with no definition is skipped' ); | ||
| $this->assertNotContains( [ 'general', 'admin_bar_snippet_limit' ], $hidden, 'a field whose condition is not met is skipped' ); | ||
| $this->assertContains( [ 'general', 'admin_bar_snippet_limit' ], $shown ); | ||
| $this->assertContains( [ 'general', 'enable_admin_bar' ], $hidden, 'the field the condition depends on is always there' ); | ||
| $this->assertSame( [], Settings_Layout::get_visible_fields( 'no-such-tab', $settings ) ); | ||
| } | ||
|
|
||
| /** | ||
| * A tab with nothing to show is not offered. | ||
| * | ||
| * @return void | ||
| */ | ||
| public function test_tabs_with_nothing_to_show_are_unavailable(): void { | ||
| add_filter( | ||
| 'code_snippets_settings_tabs', | ||
| static function ( array $tabs ): array { | ||
| $tabs['empty'] = 'Empty'; | ||
| return $tabs; | ||
| } | ||
| ); | ||
|
|
||
| $available = Settings_Layout::get_available_tabs(); | ||
|
|
||
| $this->assertArrayHasKey( 'editing', $available ); | ||
| $this->assertArrayNotHasKey( 'empty', $available ); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Test the filtered tab result. In Add a visible tab and matching field content through the filters. Assert that the visible tab exists in Keep the assertion that As per path instructions, tests must flag an assertion that cannot fail. 🧰 Tools🪛 PHPMD (2.15.0)[error] 19-144: The class Settings_Layout_Test is not named in CamelCase. (undefined) (CamelCaseClassName) [error] 70-83: The method test_tabs_with_nothing_to_show_are_unavailable is not named in camelCase. (undefined) (CamelCaseMethodName) 🤖 Prompt for AI AgentsSource: Path instructions |
||
| } | ||
|
|
||
| /** | ||
| * A group heading renders once for its group, escaped, and a labelable field gets a real label. | ||
| * | ||
| * @return void | ||
| */ | ||
| public function test_group_headings_render_once_and_escaped(): void { | ||
| add_settings_section( 'layout-test', 'Layout test', '__return_empty_string', Settings_Menu::SETTINGS_PAGE ); | ||
| add_settings_field( 'first', 'First', '__return_null', Settings_Menu::SETTINGS_PAGE, 'layout-test', [ 'group_heading' => 'Group <b>one</b>' ] ); | ||
| add_settings_field( | ||
| 'second', | ||
| 'Second', | ||
| '__return_null', | ||
| Settings_Menu::SETTINGS_PAGE, | ||
| 'layout-test', | ||
| [ | ||
| 'group_heading' => 'Group <b>one</b>', | ||
| 'label_for' => 'field-second', | ||
| ] | ||
| ); | ||
| add_settings_field( 'third', 'Third', '__return_null', Settings_Menu::SETTINGS_PAGE, 'layout-test', [ 'group_heading' => 'Group two' ] ); | ||
|
|
||
| ob_start(); | ||
| do_settings_fields_with_headings( Settings_Menu::SETTINGS_PAGE, 'layout-test' ); | ||
| $html = (string) ob_get_clean(); | ||
|
|
||
| $this->assertSame( 1, substr_count( $html, 'Group <b>one</b>' ), 'the heading is drawn once and escaped' ); | ||
| $this->assertStringNotContainsString( '<b>one</b>', $html ); | ||
| $this->assertStringContainsString( 'Group two', $html ); | ||
| $this->assertStringContainsString( '<label for="field-second">Second</label>', $html ); | ||
| $this->assertStringContainsString( '<th scope="row">First</th>', $html ); | ||
| } | ||
|
|
||
| /** | ||
| * The current section is the requested one when it exists, else the default, else the first. | ||
| * | ||
| * @return void | ||
| */ | ||
| public function test_current_section_falls_back_sensibly(): void { | ||
| global $wp_settings_sections; | ||
|
|
||
| // Only the registered sections are read, so the menu's dependencies are not needed. | ||
| $menu = ( new ReflectionClass( Settings_Menu::class ) )->newInstanceWithoutConstructor(); | ||
|
|
||
| $this->assertSame( 'anything', $menu->get_current_section( 'anything' ), 'with no sections the default is returned as given' ); | ||
|
|
||
| $wp_settings_sections[ Settings_Menu::SETTINGS_PAGE ] = [ // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- test fixture, removed in tear_down. | ||
| 'editing' => [ 'id' => 'editing' ], | ||
| 'running' => [ 'id' => 'running' ], | ||
| ]; | ||
|
|
||
| $this->assertSame( 'running', $menu->get_current_section( 'running' ) ); | ||
| $this->assertSame( 'editing', $menu->get_current_section( 'no-such-section' ), 'an unknown default falls back to the first tab' ); | ||
|
|
||
| $_REQUEST['section'] = 'running'; | ||
| $this->assertSame( 'running', $menu->get_current_section() ); | ||
|
|
||
| $_REQUEST['section'] = '<script>bogus</script>'; | ||
| $this->assertSame( 'editing', $menu->get_current_section(), 'an invalid request value falls back to the first tab' ); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add a check for the refreshed link.
In
src/js/components/ManageMenu/SnippetsTable/TableColumns.tsxLine 43, no supplied runnable check covers the new mousedown path. Add a component test for this handler. Change the nonce after render. Firemousedown. Assert thathrefuses the new nonce.As per path instructions, ask for a test when a PR adds real logic and the code has no runnable check.
🤖 Prompt for AI Agents
Source: Path instructions