diff --git a/plugins/view-transitions/includes/admin.php b/plugins/view-transitions/includes/admin.php
index 83e81e45e3..c5132b7ab2 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,
),
@@ -325,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',
@@ -473,10 +503,68 @@ function plvt_render_settings_field( array $args ): void {
+ "
+ value=""
+ class="small-text"
+
+ min=""
+
+ max=""
+
+ step=""
+
+ aria-describedby=""
+
+ >
+
+
+ ' . 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'] );
+ }
+ ?>
+
+
id=""
name=""
value=""
diff --git a/plugins/view-transitions/tests/test-admin.php b/plugins/view-transitions/tests/test-admin.php
new file mode 100644
index 0000000000..3edc8beca7
--- /dev/null
+++ b/plugins/view-transitions/tests/test-admin.php
@@ -0,0 +1,60 @@
+ 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'] );
+ }
+}
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 );
+ }
}