From 4970c8dd5590938821fd17ba9df9d2a6d8947a90 Mon Sep 17 00:00:00 2001 From: Sarthak Jaiswal Date: Sun, 4 Jan 2026 19:48:26 +0530 Subject: [PATCH 1/7] Implement transition animation duration setting with validation and real-time preview --- plugins/view-transitions/includes/admin.php | 3 + .../view-transitions/includes/settings.php | 68 ++++++++++++++++++- plugins/view-transitions/tests/test-theme.php | 32 +++++++++ 3 files changed, 100 insertions(+), 3 deletions(-) diff --git a/plugins/view-transitions/includes/admin.php b/plugins/view-transitions/includes/admin.php index 83e81e45e3..2bb299847d 100644 --- a/plugins/view-transitions/includes/admin.php +++ b/plugins/view-transitions/includes/admin.php @@ -27,9 +27,12 @@ function plvt_print_view_transitions_admin_style(): void { if ( ! isset( $options['enable_admin_transitions'] ) || true !== $options['enable_admin_transitions'] ) { return; } + + $duration_seconds = absint( $options['default_transition_animation_duration'] ) / 1000; ?> 'string', 'enum' => array_keys( plvt_get_view_transition_animation_labels() ), ), + 'default_transition_animation_duration' => array( + 'description' => __( 'Duration of the view transition animation in milliseconds.', 'view-transitions' ), + 'type' => 'integer', + 'minimum' => PLVT_MIN_ANIMATION_DURATION, + 'maximum' => PLVT_MAX_ANIMATION_DURATION, + ), ), 'additionalProperties' => false, ), @@ -473,10 +498,47 @@ function plvt_render_settings_field( array $args ): void { + " + value="" + class="small-text" + min="" + max="" + step="50" + + aria-describedby="" + + > + + ' . esc_html( (string) $seconds ) . 's' + ); + ?> + + id="" name="" value="" diff --git a/plugins/view-transitions/tests/test-theme.php b/plugins/view-transitions/tests/test-theme.php index 7879c72f73..c675ccd903 100644 --- a/plugins/view-transitions/tests/test-theme.php +++ b/plugins/view-transitions/tests/test-theme.php @@ -48,4 +48,36 @@ public function test_plvt_load_view_transitions(): void { $this->assertTrue( wp_style_is( 'plvt-view-transitions', 'registered' ) ); $this->assertTrue( wp_style_is( 'plvt-view-transitions', 'enqueued' ) ); } + + /** + * @covers ::plvt_inject_animation_duration + */ + public function test_plvt_inject_animation_duration_with_existing_css(): void { + $css = '::view-transition-old(*) { animation-name: test; }'; + $result = plvt_inject_animation_duration( $css, 500 ); + + $this->assertStringContainsString( '--plvt-view-transition-animation-duration: 0.5s', $result ); + $this->assertStringContainsString( $css, $result ); + } + + /** + * @covers ::plvt_inject_animation_duration + */ + public function test_plvt_inject_animation_duration_with_empty_css(): void { + $result = plvt_inject_animation_duration( '', 400 ); + + $this->assertStringContainsString( 'animation-duration: 0.4s', $result ); + $this->assertStringNotContainsString( '--plvt-view-transition-animation-duration', $result ); + } + + /** + * @covers ::plvt_inject_animation_duration + */ + public function test_plvt_inject_animation_duration_converts_milliseconds_to_seconds(): void { + $result = plvt_inject_animation_duration( '', 1000 ); + $this->assertStringContainsString( '1s', $result ); + + $result = plvt_inject_animation_duration( '', 250 ); + $this->assertStringContainsString( '0.25s', $result ); + } } From 07de264c348cb061c80909c8ee08d06d55dcc698 Mon Sep 17 00:00:00 2001 From: Sarthak Jaiswal Date: Sun, 18 Jan 2026 21:54:03 +0530 Subject: [PATCH 2/7] Addressed feedback changes and added unit test for better coverage --- .../view-transitions/includes/settings.php | 76 ++++++++++------ plugins/view-transitions/tests/test-admin.php | 60 +++++++++++++ .../view-transitions/tests/test-settings.php | 89 +++++++++++++++++++ 3 files changed, 200 insertions(+), 25 deletions(-) create mode 100644 plugins/view-transitions/tests/test-admin.php create mode 100644 plugins/view-transitions/tests/test-settings.php diff --git a/plugins/view-transitions/includes/settings.php b/plugins/view-transitions/includes/settings.php index 1db2008e62..d920fe2810 100644 --- a/plugins/view-transitions/includes/settings.php +++ b/plugins/view-transitions/includes/settings.php @@ -350,9 +350,14 @@ static function (): void { 'description' => __( 'Choose the animation that is used for the default view transition type.', 'view-transitions' ), ), 'default_transition_animation_duration' => array( - 'section' => 'plvt_view_transitions', - 'title' => __( 'Transition Animation Duration', 'view-transitions' ), - 'description' => __( 'Control the duration of the view transition. Enter the value in milliseconds (e.g., 500, 1000, 2000).', 'view-transitions' ), + 'section' => 'plvt_view_transitions', + 'title' => __( 'Transition Animation Duration', 'view-transitions' ), + 'description' => __( 'Control the duration of the view transition. Enter the value in milliseconds (e.g., 500, 1000, 2000).', 'view-transitions' ), + 'min' => PLVT_MIN_ANIMATION_DURATION, + 'max' => PLVT_MAX_ANIMATION_DURATION, + 'step' => 50, + 'unit' => 'ms', + 'show_seconds' => true, ), 'header_selector' => array( 'section' => 'plvt_view_transitions', @@ -499,19 +504,29 @@ function plvt_render_settings_field( array $args ): void { " value="" class="small-text" - min="" - max="" - step="50" + min="" + + max="" + + step="" + aria-describedby="" @@ -519,23 +534,34 @@ class="small-text" } ?> > - - ' . esc_html( (string) $seconds ) . 's' - ); - ?> - + + ' . esc_html( (string) $seconds ) . 's' + ); + wp_print_inline_script_tag( + sprintf( + 'document.getElementById( %s ).addEventListener( "input", function( e ) { document.getElementById( %s ).textContent = ( parseInt( e.target.value, 10 ) / 1000 ) + "s"; } );', + wp_json_encode( $field_id ), + wp_json_encode( $field_id . '-seconds' ) + ) + ); + } else { + echo esc_html( $args['unit'] ); + } + ?> + + false ) ); + + ob_start(); + plvt_print_view_transitions_admin_style(); + $output = ob_get_clean(); + + $this->assertEmpty( $output ); + } + + /** + * @covers ::plvt_print_view_transitions_admin_style + */ + public function test_plvt_print_view_transitions_admin_style_enabled(): void { + update_option( + 'plvt_view_transitions', + array( + 'enable_admin_transitions' => true, + 'default_transition_animation_duration' => 500, + ) + ); + + ob_start(); + plvt_print_view_transitions_admin_style(); + $output = ob_get_clean(); + + $this->assertStringContainsString( '@view-transition { navigation: auto; }', $output ); + $this->assertStringContainsString( 'animation-duration: 0.5s', $output ); + } + + /** + * @covers ::plvt_print_view_transitions_admin_style + */ + public function test_plvt_print_view_transitions_admin_style_uses_default_duration(): void { + update_option( + 'plvt_view_transitions', + array( 'enable_admin_transitions' => true ) + ); + + ob_start(); + plvt_print_view_transitions_admin_style(); + $output = ob_get_clean(); + + // Default duration is 400ms = 0.4s. + $this->assertStringContainsString( 'animation-duration: 0.4s', $output ); + } +} diff --git a/plugins/view-transitions/tests/test-settings.php b/plugins/view-transitions/tests/test-settings.php new file mode 100644 index 0000000000..93954fce1c --- /dev/null +++ b/plugins/view-transitions/tests/test-settings.php @@ -0,0 +1,89 @@ +assertSame( plvt_get_setting_default(), plvt_sanitize_setting( null ) ); + $this->assertSame( plvt_get_setting_default(), plvt_sanitize_setting( 'string' ) ); + $this->assertSame( plvt_get_setting_default(), plvt_sanitize_setting( 123 ) ); + } + + /** + * @covers ::plvt_sanitize_setting + */ + public function test_plvt_sanitize_setting_clamps_duration_minimum(): void { + $input = array( 'default_transition_animation_duration' => 50 ); + $result = plvt_sanitize_setting( $input ); + $this->assertSame( PLVT_MIN_ANIMATION_DURATION, $result['default_transition_animation_duration'] ); + } + + /** + * @covers ::plvt_sanitize_setting + */ + public function test_plvt_sanitize_setting_clamps_duration_maximum(): void { + $input = array( 'default_transition_animation_duration' => 10000 ); + $result = plvt_sanitize_setting( $input ); + $this->assertSame( PLVT_MAX_ANIMATION_DURATION, $result['default_transition_animation_duration'] ); + } + + /** + * @covers ::plvt_sanitize_setting + */ + public function test_plvt_sanitize_setting_accepts_valid_duration(): void { + $input = array( 'default_transition_animation_duration' => 500 ); + $result = plvt_sanitize_setting( $input ); + $this->assertSame( 500, $result['default_transition_animation_duration'] ); + } + + /** + * @covers ::plvt_sanitize_setting + */ + public function test_plvt_sanitize_setting_handles_negative_duration(): void { + $input = array( 'default_transition_animation_duration' => -500 ); + $result = plvt_sanitize_setting( $input ); + // absint converts negative to positive, then clamps. + $this->assertSame( 500, $result['default_transition_animation_duration'] ); + } + + /** + * @covers ::plvt_sanitize_setting + */ + public function test_plvt_sanitize_setting_handles_string_duration(): void { + $input = array( 'default_transition_animation_duration' => '750' ); + $result = plvt_sanitize_setting( $input ); + $this->assertSame( 750, $result['default_transition_animation_duration'] ); + } + + /** + * @covers ::plvt_get_setting_default + */ + public function test_plvt_get_setting_default_has_valid_duration(): void { + $defaults = plvt_get_setting_default(); + $this->assertArrayHasKey( 'default_transition_animation_duration', $defaults ); + $this->assertIsInt( $defaults['default_transition_animation_duration'] ); + $this->assertGreaterThanOrEqual( PLVT_MIN_ANIMATION_DURATION, $defaults['default_transition_animation_duration'] ); + $this->assertLessThanOrEqual( PLVT_MAX_ANIMATION_DURATION, $defaults['default_transition_animation_duration'] ); + } + + /** + * @covers ::plvt_sanitize_setting + */ + public function test_plvt_sanitize_setting_validates_animation_type(): void { + $input = array( 'default_transition_animation' => 'invalid-animation' ); + $result = plvt_sanitize_setting( $input ); + $this->assertSame( 'fade', $result['default_transition_animation'] ); + + $input = array( 'default_transition_animation' => 'slide-from-right' ); + $result = plvt_sanitize_setting( $input ); + $this->assertSame( 'slide-from-right', $result['default_transition_animation'] ); + } +} From 58eee135611822bd8ab8d06900345ce3e84a94a8 Mon Sep 17 00:00:00 2001 From: Sarthak Jaiswal Date: Sun, 18 Jan 2026 22:04:23 +0530 Subject: [PATCH 3/7] Fixed phpstan errors --- plugins/view-transitions/includes/admin.php | 2 +- plugins/view-transitions/includes/settings.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/view-transitions/includes/admin.php b/plugins/view-transitions/includes/admin.php index 2bb299847d..c5132b7ab2 100644 --- a/plugins/view-transitions/includes/admin.php +++ b/plugins/view-transitions/includes/admin.php @@ -32,7 +32,7 @@ function plvt_print_view_transitions_admin_style(): void { ?> > Date: Sun, 25 Jan 2026 16:37:12 +0530 Subject: [PATCH 4/7] Removed seconds option from view transition duration --- plugins/view-transitions/includes/admin.php | 4 +- .../view-transitions/includes/settings.php | 40 ++++--------------- plugins/view-transitions/tests/test-admin.php | 6 +-- 3 files changed, 13 insertions(+), 37 deletions(-) diff --git a/plugins/view-transitions/includes/admin.php b/plugins/view-transitions/includes/admin.php index c5132b7ab2..bb3f3afd71 100644 --- a/plugins/view-transitions/includes/admin.php +++ b/plugins/view-transitions/includes/admin.php @@ -28,11 +28,11 @@ function plvt_print_view_transitions_admin_style(): void { return; } - $duration_seconds = absint( $options['default_transition_animation_duration'] ) / 1000; + $duration = absint( $options['default_transition_animation_duration'] ); ?> __( 'Choose the animation that is used for the default view transition type.', 'view-transitions' ), ), 'default_transition_animation_duration' => array( - 'section' => 'plvt_view_transitions', - 'title' => __( 'Transition Animation Duration', 'view-transitions' ), - 'description' => __( 'Control the duration of the view transition. Enter the value in milliseconds (e.g., 500, 1000, 2000).', 'view-transitions' ), - 'min' => PLVT_MIN_ANIMATION_DURATION, - 'max' => PLVT_MAX_ANIMATION_DURATION, - 'step' => 50, - 'unit' => 'ms', - 'show_seconds' => true, + 'section' => 'plvt_view_transitions', + 'title' => __( 'Transition Animation Duration', 'view-transitions' ), + 'description' => __( 'Control the duration of the view transition. Enter the value in milliseconds (e.g., 500, 1000, 2000).', 'view-transitions' ), + 'min' => PLVT_MIN_ANIMATION_DURATION, + 'max' => PLVT_MAX_ANIMATION_DURATION, + 'step' => 50, + 'unit' => 'ms', ), 'header_selector' => array( 'section' => 'plvt_view_transitions', @@ -537,29 +535,7 @@ class="small-text" - - ' . esc_html( (string) $seconds ) . 's' - ); - wp_print_inline_script_tag( - sprintf( - 'document.getElementById( %s ).addEventListener( "input", function( e ) { document.getElementById( %s ).textContent = ( parseInt( e.target.value, 10 ) / 1000 ) + "s"; } );', - wp_json_encode( $field_id ), - wp_json_encode( $field_id . '-seconds' ) - ) - ); - } else { - echo esc_html( $args['unit'] ); - } - ?> - + assertStringContainsString( '@view-transition { navigation: auto; }', $output ); - $this->assertStringContainsString( 'animation-duration: 0.5s', $output ); + $this->assertStringContainsString( '--plvt-view-transition-animation-duration: 500ms', $output ); } /** @@ -54,7 +54,7 @@ public function test_plvt_print_view_transitions_admin_style_uses_default_durati plvt_print_view_transitions_admin_style(); $output = ob_get_clean(); - // Default duration is 400ms = 0.4s. - $this->assertStringContainsString( 'animation-duration: 0.4s', $output ); + // Default duration is 400ms. + $this->assertStringContainsString( '--plvt-view-transition-animation-duration: 400ms', $output ); } } From 384c1ef1333a3b92e84e8400991c51e47ef5100e Mon Sep 17 00:00:00 2001 From: Sarthak Jaiswal Date: Sun, 25 Jan 2026 22:45:30 +0530 Subject: [PATCH 5/7] Add real-time CSS selector validation to View Transitions settings --- .../css/validator-selector.css | 34 ++++++ plugins/view-transitions/hooks.php | 1 + plugins/view-transitions/includes/admin.php | 35 ++++++ .../view-transitions/includes/settings.php | 17 +++ .../view-transitions/js/validator-selector.js | 108 ++++++++++++++++++ 5 files changed, 195 insertions(+) create mode 100644 plugins/view-transitions/css/validator-selector.css create mode 100644 plugins/view-transitions/js/validator-selector.js diff --git a/plugins/view-transitions/css/validator-selector.css b/plugins/view-transitions/css/validator-selector.css new file mode 100644 index 0000000000..c680f1e35c --- /dev/null +++ b/plugins/view-transitions/css/validator-selector.css @@ -0,0 +1,34 @@ +/** + * CSS Selector Validation Styles + * + * Styles for visual feedback of CSS selector validation. + * + * @since n.e.x.t + */ + +/* Valid selector indicator */ +input.plvt-selector-valid { + border-color: #46b450 !important; + background-color: #f0fdf4; +} + +input.plvt-selector-valid:focus { + border-color: #46b450 !important; +} + +/* Invalid selector indicator */ +input.plvt-selector-invalid { + border-color: #dc2626 !important; + background-color: #fdf2f2; +} + +input.plvt-selector-invalid:focus { + border-color: #dc2626 !important; +} + +/* Error message styling */ +.plvt-selector-error { + color: #dc2626; + margin-top: 5px; + font-style: italic; +} diff --git a/plugins/view-transitions/hooks.php b/plugins/view-transitions/hooks.php index 260cc7381a..b57106ec36 100644 --- a/plugins/view-transitions/hooks.php +++ b/plugins/view-transitions/hooks.php @@ -39,4 +39,5 @@ function plvt_render_generator(): void { add_action( 'init', 'plvt_register_setting' ); add_action( 'init', 'plvt_apply_settings_to_theme_support' ); add_action( 'load-options-reading.php', 'plvt_add_setting_ui' ); +add_action( 'admin_enqueue_scripts', 'plvt_enqueue_selector_validation' ); add_filter( 'plugin_action_links_' . plugin_basename( VIEW_TRANSITIONS_MAIN_FILE ), 'plvt_add_settings_action_link' ); diff --git a/plugins/view-transitions/includes/admin.php b/plugins/view-transitions/includes/admin.php index bb3f3afd71..d38d5f8772 100644 --- a/plugins/view-transitions/includes/admin.php +++ b/plugins/view-transitions/includes/admin.php @@ -37,3 +37,38 @@ function plvt_print_view_transitions_admin_style(): void { id ) { + return; + } + + // Enqueue validation CSS. + wp_enqueue_style( + 'plvt-selector-validator', + plugin_dir_url( VIEW_TRANSITIONS_MAIN_FILE ) . 'css/validator-selector.css', + array(), + VIEW_TRANSITIONS_VERSION + ); + + // Enqueue validation JS. + wp_enqueue_script( + 'plvt-selector-validator', + plugin_dir_url( VIEW_TRANSITIONS_MAIN_FILE ) . 'js/validator-selector.js', + array(), + VIEW_TRANSITIONS_VERSION, + array( 'in_footer' => false ) + ); +} diff --git a/plugins/view-transitions/includes/settings.php b/plugins/view-transitions/includes/settings.php index 911c5f9e1b..51fe736bba 100644 --- a/plugins/view-transitions/includes/settings.php +++ b/plugins/view-transitions/includes/settings.php @@ -539,6 +539,18 @@ class="small-text" + data-plvt-validate-selector + aria-describedby="" diff --git a/plugins/view-transitions/js/validator-selector.js b/plugins/view-transitions/js/validator-selector.js new file mode 100644 index 0000000000..e4a07f9b90 --- /dev/null +++ b/plugins/view-transitions/js/validator-selector.js @@ -0,0 +1,108 @@ +/** + * CSS Selector Validation for View Transitions Settings + * + * This script provides real-time validation for CSS selector input fields + * in the View Transitions settings panel. + * + * @since n.e.x.t + */ + +( () => { + /** + * Validates a CSS selector by attempting to use it with document.querySelector. + * + * @param {string} selector The CSS selector to validate. + * @return {Object} Object with 'valid' boolean and optional 'message' string. + */ + function validateSelector( selector ) { + // Empty selectors are allowed (they reset to default) + if ( '' === selector.trim() ) { + return { valid: true }; + } + + try { + document.querySelector( selector ); + return { valid: true }; + } catch ( error ) { + return { + valid: false, + message: 'Invalid CSS selector: ' + error.message, + }; + } + } + + /** + * Sets custom validity for a selector input field. + * + * @param {HTMLInputElement} input The input element to validate. + */ + function updateValidation( input ) { + const result = validateSelector( input.value ); + + if ( result.valid ) { + input.setCustomValidity( '' ); + input.classList.remove( 'plvt-selector-invalid' ); + input.classList.add( 'plvt-selector-valid' ); + + // Remove any existing error message + const existingError = input.parentNode.querySelector( + '.plvt-selector-error' + ); + if ( existingError ) { + existingError.remove(); + } + } else { + input.setCustomValidity( result.message ); + input.classList.remove( 'plvt-selector-valid' ); + input.classList.add( 'plvt-selector-invalid' ); + + // Show error message + let errorElement = input.parentNode.querySelector( + '.plvt-selector-error' + ); + if ( ! errorElement ) { + errorElement = document.createElement( 'p' ); + errorElement.className = 'plvt-selector-error description'; + input.parentNode.insertBefore( + errorElement, + input.nextSibling + ); + } + errorElement.textContent = result.message; + } + } + + /** + * Initializes validation for all selector input fields. + */ + function initValidation() { + // Target all text inputs for selectors + const selectorInputs = document.querySelectorAll( + 'input[data-plvt-validate-selector]' + ); + + selectorInputs.forEach( ( input ) => { + // Validate on blur + input.addEventListener( 'blur', () => { + updateValidation( input ); + } ); + + // Validate on input for real-time feedback + input.addEventListener( 'input', () => { + updateValidation( input ); + } ); + + // Validate on page load if field has a value + if ( '' !== input.value.trim() ) { + updateValidation( input ); + } + } ); + } + + // Initialize when DOM is ready + if ( 'loading' === document.readyState ) { + document.addEventListener( 'DOMContentLoaded', initValidation ); + } else { + initValidation(); + } +} )(); From 1004171c4d21c1b72e422e76104293be91e6b1d6 Mon Sep 17 00:00:00 2001 From: Sarthak Jaiswal Date: Sun, 25 Jan 2026 22:51:58 +0530 Subject: [PATCH 6/7] Fix js lint error --- plugins/view-transitions/js/validator-selector.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/view-transitions/js/validator-selector.js b/plugins/view-transitions/js/validator-selector.js index e4a07f9b90..43fd7f7d3b 100644 --- a/plugins/view-transitions/js/validator-selector.js +++ b/plugins/view-transitions/js/validator-selector.js @@ -81,7 +81,9 @@ 'input[data-plvt-validate-selector]' ); - selectorInputs.forEach( ( input ) => { + selectorInputs.forEach( ( element ) => { + const input = /** @type {HTMLInputElement} */ ( element ); + // Validate on blur input.addEventListener( 'blur', () => { updateValidation( input ); From f9fba526fc10a2fa05cfe813bd81691069f47efd Mon Sep 17 00:00:00 2001 From: Sarthak Jaiswal Date: Sun, 25 Jan 2026 23:20:25 +0530 Subject: [PATCH 7/7] Fix issue of failing unit test case --- plugins/view-transitions/includes/admin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/view-transitions/includes/admin.php b/plugins/view-transitions/includes/admin.php index d38d5f8772..1e00401da3 100644 --- a/plugins/view-transitions/includes/admin.php +++ b/plugins/view-transitions/includes/admin.php @@ -24,7 +24,7 @@ */ function plvt_print_view_transitions_admin_style(): void { $options = plvt_get_stored_setting_value(); - if ( ! isset( $options['enable_admin_transitions'] ) || true !== $options['enable_admin_transitions'] ) { + if ( ! isset( $options['enable_admin_transitions'] ) || ! (bool) $options['enable_admin_transitions'] ) { return; }