From 9a191e64bd7b0dd856c02c6109278680fbdaf9b2 Mon Sep 17 00:00:00 2001 From: rejaulalomkhan Date: Sun, 11 Oct 2026 09:49:28 +0600 Subject: [PATCH 1/3] Tests: Add unit test coverage for WP_Widget_Calendar Adds a test class for WP_Widget_Calendar, which had no tests. The tests cover the registration settings, the output from widget() with and without a title, the widget_title filter arguments, the private static instance counter omitting the calendar_wrap id attribute on subsequent calls, title sanitization in update(), and the settings form output in form(). This is a test-only change; no production code is modified. See #65819. --- .../tests/widgets/wpWidgetCalendar.php | 243 ++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 tests/phpunit/tests/widgets/wpWidgetCalendar.php diff --git a/tests/phpunit/tests/widgets/wpWidgetCalendar.php b/tests/phpunit/tests/widgets/wpWidgetCalendar.php new file mode 100644 index 0000000000000..56ba7bc9d4353 --- /dev/null +++ b/tests/phpunit/tests/widgets/wpWidgetCalendar.php @@ -0,0 +1,243 @@ + '
', + 'after_widget' => "
\n", + 'before_title' => '

', + 'after_title' => "

\n", + ); + + /** + * Reset the private static $instance counter on WP_Widget_Calendar. + */ + public function set_up() { + parent::set_up(); + $this->reset_calendar_widget_instance_count(); + } + + /** + * Reset the private static $instance counter after each test. + */ + public function tear_down() { + $this->reset_calendar_widget_instance_count(); + parent::tear_down(); + } + + /** + * Helper to reset private static $instance count using reflection. + */ + private function reset_calendar_widget_instance_count() { + $property = new ReflectionProperty( 'WP_Widget_Calendar', 'instance' ); + $property->setAccessible( true ); + $property->setValue( null, 0 ); + } + + /** + * Tests the constructor settings and options. + * + * @ticket 65819 + * + * @covers WP_Widget_Calendar::__construct + */ + public function test_construct() { + $widget = new WP_Widget_Calendar(); + + $this->assertSame( 'calendar', $widget->id_base ); + $this->assertSame( 'Calendar', $widget->name ); + $this->assertSame( 'widget_calendar', $widget->widget_options['classname'] ); + $this->assertSame( 'A calendar of your site’s posts.', $widget->widget_options['description'] ); + $this->assertTrue( $widget->widget_options['customize_selective_refresh'] ); + $this->assertTrue( $widget->widget_options['show_instance_in_rest'] ); + } + + /** + * Tests widget() output with default arguments and no title. + * + * @ticket 65819 + * + * @covers WP_Widget_Calendar::widget + */ + public function test_widget_output_without_title() { + $widget = new WP_Widget_Calendar(); + $instance = array(); + + $output = get_echo( array( $widget, 'widget' ), array( $this->args, $instance ) ); + + $this->assertStringStartsWith( '
', $output ); + $this->assertStringEndsWith( "
\n", $output ); + $this->assertStringNotContainsString( 'widget-title', $output ); + $this->assertStringContainsString( '
', $output ); + $this->assertStringContainsString( ' 'Events Calendar' ); + + $output = get_echo( array( $widget, 'widget' ), array( $this->args, $instance ) ); + + $this->assertStringContainsString( '

Events Calendar

', $output ); + $this->assertStringContainsString( '
', $output ); + } + + /** + * Tests that only the first widget instance receives the id attribute on calendar_wrap. + * + * @ticket 65819 + * + * @covers WP_Widget_Calendar::widget + */ + public function test_widget_instance_counter_omits_id_on_subsequent_calls() { + $widget = new WP_Widget_Calendar(); + $instance = array(); + + $output1 = get_echo( array( $widget, 'widget' ), array( $this->args, $instance ) ); + $output2 = get_echo( array( $widget, 'widget' ), array( $this->args, $instance ) ); + + $this->assertStringContainsString( '
', $output1 ); + $this->assertStringContainsString( '
', $output2 ); + $this->assertStringNotContainsString( '
'Initial Title' ); + + $filter = new MockAction(); + add_filter( 'widget_title', array( $filter, 'filter' ), 10, 3 ); + add_filter( + 'widget_title', + static function () { + return 'Filtered Calendar Title'; + }, + 20 + ); + + $output = get_echo( array( $widget, 'widget' ), array( $this->args, $instance ) ); + + $this->assertStringContainsString( '

Filtered Calendar Title

', $output ); + $this->assertSame( + array( array( 'Initial Title', $instance, 'calendar' ) ), + $filter->get_args(), + 'The widget_title filter should receive the title, instance, and id_base.' + ); + } + + /** + * Tests update() method sanitizes the title field. + * + * @ticket 65819 + * + * @covers WP_Widget_Calendar::update + * + * @dataProvider data_update_titles + * + * @param string $title Submitted title. + * @param string $expected Expected sanitized title. + */ + public function test_update_sanitizes_title( $title, $expected ) { + $widget = new WP_Widget_Calendar(); + + $new_instance = array( 'title' => $title ); + $old_instance = array( 'title' => 'Old Title' ); + + $result = $widget->update( $new_instance, $old_instance ); + + $this->assertSame( array( 'title' => $expected ), $result ); + } + + /** + * Data provider for test_update_sanitizes_title. + * + * @return array[] + */ + public static function data_update_titles() { + return array( + 'plain string' => array( 'My Calendar', 'My Calendar' ), + 'with html tags' => array( 'Calendar 2026 ', 'Calendar 2026' ), + 'with line breaks' => array( "Calendar\nYear", 'Calendar Year' ), + 'surrounding whitespace' => array( ' Calendar ', 'Calendar' ), + 'empty string' => array( '', '' ), + 'numeric string zero' => array( '0', '0' ), + ); + } + + /** + * Tests form() output displays title field correctly. + * + * @ticket 65819 + * + * @covers WP_Widget_Calendar::form + * + * @dataProvider data_form_titles + * + * @param array $instance Widget settings instance. + * @param string $expected Expected value in the input field. + */ + public function test_form_displays_title_field( $instance, $expected ) { + $widget = new WP_Widget_Calendar(); + $widget->_set( 2 ); + + $output = get_echo( array( $widget, 'form' ), array( $instance ) ); + $processor = new WP_HTML_Tag_Processor( $output ); + + $this->assertTrue( $processor->next_tag( 'LABEL' ), 'The form should contain a label tag.' ); + $this->assertSame( 'widget-calendar-2-title', $processor->get_attribute( 'for' ) ); + + $this->assertTrue( $processor->next_tag( 'INPUT' ), 'The form should contain an input tag.' ); + $this->assertSame( 'widget-calendar-2-title', $processor->get_attribute( 'id' ) ); + $this->assertSame( 'widget-calendar[2][title]', $processor->get_attribute( 'name' ) ); + $this->assertSame( 'text', $processor->get_attribute( 'type' ) ); + $this->assertSame( $expected, $processor->get_attribute( 'value' ) ); + $this->assertFalse( $processor->next_tag( 'INPUT' ), 'The form should contain only one input field.' ); + } + + /** + * Data provider for test_form_displays_title_field. + * + * @return array[] + */ + public static function data_form_titles() { + return array( + 'with title' => array( array( 'title' => 'Events Calendar' ), 'Events Calendar' ), + 'with quotes' => array( array( 'title' => 'Calendar "2026" & Beyond' ), 'Calendar "2026" & Beyond' ), + 'empty title' => array( array( 'title' => '' ), '' ), + 'no title key' => array( array(), '' ), + 'zero string title' => array( array( 'title' => '0' ), '0' ), + ); + } +} From 145d5ecafb04c1bd57136a5adf8230b0801dfe36 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Oct 2026 04:39:55 +0000 Subject: [PATCH 2/3] Tests: Fix `WP_Widget_Calendar` tests on PHP 8.5 and with no posts. `ReflectionProperty::setAccessible()` is deprecated as of PHP 8.5 and has had no effect since PHP 8.1, so only call it on older versions, matching the existing convention in the test suite. `get_calendar()` outputs nothing when the site has no posts, so the assertion for the calendar table depended on posts leaking in from elsewhere. Create a post in that test and add a separate test for the no-posts case. See #65819. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01F9qLmSDTekkoHLL78tqtBH --- .../tests/widgets/wpWidgetCalendar.php | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/tests/phpunit/tests/widgets/wpWidgetCalendar.php b/tests/phpunit/tests/widgets/wpWidgetCalendar.php index 56ba7bc9d4353..d96a0a78a2684 100644 --- a/tests/phpunit/tests/widgets/wpWidgetCalendar.php +++ b/tests/phpunit/tests/widgets/wpWidgetCalendar.php @@ -48,7 +48,9 @@ public function tear_down() { */ private function reset_calendar_widget_instance_count() { $property = new ReflectionProperty( 'WP_Widget_Calendar', 'instance' ); - $property->setAccessible( true ); + if ( PHP_VERSION_ID < 80100 ) { + $property->setAccessible( true ); + } $property->setValue( null, 0 ); } @@ -78,6 +80,9 @@ public function test_construct() { * @covers WP_Widget_Calendar::widget */ public function test_widget_output_without_title() { + // get_calendar() outputs nothing unless at least one post exists. + self::factory()->post->create(); + $widget = new WP_Widget_Calendar(); $instance = array(); @@ -90,6 +95,23 @@ public function test_widget_output_without_title() { $this->assertStringContainsString( '
args, $instance ) ); + + $this->assertStringContainsString( '
', $output ); + $this->assertStringNotContainsString( '
Date: Sun, 11 Oct 2026 05:28:23 +0000 Subject: [PATCH 3/3] Tests: Isolate the no-posts `WP_Widget_Calendar` test from global state. `get_calendar()` only runs its "no posts" check when the global `$posts` is empty, so the test failed when the full suite ran after a test that left that global populated. Unset it before rendering. See #65819. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01F9qLmSDTekkoHLL78tqtBH --- tests/phpunit/tests/widgets/wpWidgetCalendar.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/phpunit/tests/widgets/wpWidgetCalendar.php b/tests/phpunit/tests/widgets/wpWidgetCalendar.php index d96a0a78a2684..0c63aed241e76 100644 --- a/tests/phpunit/tests/widgets/wpWidgetCalendar.php +++ b/tests/phpunit/tests/widgets/wpWidgetCalendar.php @@ -103,6 +103,12 @@ public function test_widget_output_without_title() { * @covers WP_Widget_Calendar::widget */ public function test_widget_output_without_posts() { + /* + * get_calendar() skips its "no posts" check when the global $posts is populated, + * which can happen when it is left over from a previous test. + */ + unset( $GLOBALS['posts'] ); + $widget = new WP_Widget_Calendar(); $instance = array();