From 75630884090eba3928ded550acfdc5bb59575ae4 Mon Sep 17 00:00:00 2001 From: John Rayes Date: Tue, 1 Sep 2026 22:12:30 -0700 Subject: [PATCH] Add more tests for the page index --- tests/Unit/PageIndexTest.php | 263 +++++++++++++++++++++++++++++++---- tests/bootstrap.php | 1 + 2 files changed, 238 insertions(+), 26 deletions(-) diff --git a/tests/Unit/PageIndexTest.php b/tests/Unit/PageIndexTest.php index 08b85ac679..4af80dc144 100644 --- a/tests/Unit/PageIndexTest.php +++ b/tests/Unit/PageIndexTest.php @@ -5,6 +5,7 @@ namespace SMF\Tests\Unit; use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use SMF\Config; use SMF\PageIndex; @@ -39,41 +40,63 @@ class PageIndexTest extends TestCase ****************/ /** - * A negative start is how a caller says "no particular page was asked - * for". The message index passes one for a topic whose first page is being - * linked, and the page index answers by clamping the start to zero and - * linking page 1 rather than marking it as the page you are on. - * - * fixStart() records that verdict as a side effect of clamping, and - * __toString() calls it again on a start that has already been clamped, so - * the second answer is always "valid" and the first was thrown away. Page 1 - * came out as plain text with no link on it, and a "next page" link - * appeared beside it pointing at page 2. + * Negative starts are invalid, but are clamped to zero. The invalid state + * is retained so that page 1 is rendered as a link rather than as the + * current page. */ + #[DataProvider('pageIndexProvider')] + public function testStartIsNormalised(int $start, int $num_items, int $num_per_page, int $expected_start, int $expected_page): void + { + $page_index = new PageIndex('querystring', $start, $num_items, $num_per_page); + + $this->assertSame($expected_start, $start); + $this->assertSame($expected_start, $page_index->start); + $this->assertSame($expected_page, Utils::$context['current_page']); + + // Force __toString() to exercise the second fixStart() call as well. + (string) $page_index; + + $this->assertSame($expected_start, $start); + $this->assertSame($expected_start, $page_index->start); + } + + /** + * The rendered current page is one-based, while current_page in the + * context is zero-based. + */ + #[DataProvider('pageRenderingProvider')] + public function testCurrentPageIsRendered(int $start, int $num_items, int $num_per_page, int $expected_page): void + { + $page_index = new PageIndex('querystring', $start, $num_items, $num_per_page); + + $this->assertSame($expected_page - 1, Utils::$context['current_page']); + + $this->assertStringContainsString(\sprintf('%d', $expected_page), (string) $page_index); + } + public function testANegativeStartLinksTheFirstPageInsteadOfMarkingIt(): void { $start = -1; $page_index = new PageIndex('https://example.com/index.php?board=1.0', $start, 100, 20); - $this->assertStringContainsString( - '1', - (string) $page_index, - ); + $page_index = (string) $page_index; + + $this->assertSame(0, $start); + + $this->assertStringContainsString('1', $page_index); - $this->assertStringNotContainsString('current_page', (string) $page_index); + $this->assertStringNotContainsString('current_page', $page_index); } - /** - * Nothing was navigated away from, so there is nowhere to go back to and - * nothing to go on to. - */ public function testANegativeStartShowsNeitherPreviousNorNextLinks(): void { $start = -1; $page_index = new PageIndex('https://example.com/index.php?board=1.0', $start, 100, 20); - $this->assertStringNotContainsString('previous_page', (string) $page_index); - $this->assertStringNotContainsString('next_page', (string) $page_index); + $page_index = (string) $page_index; + + $this->assertStringNotContainsString('previous_page', $page_index); + $this->assertStringNotContainsString('next_page', $page_index); } /** @@ -92,6 +115,7 @@ public function testAskingTwiceGivesTheSameAnswer(): void public function testTheStartValueIsClampedAndHandedBackToTheCaller(): void { $start = -1; + new PageIndex('https://example.com/index.php?board=1.0', $start, 100, 20); $this->assertSame(0, $start); @@ -106,9 +130,12 @@ public function testAStartThatNamesAPageMarksThatPageAsCurrent(): void $start = 40; $page_index = new PageIndex('https://example.com/index.php?board=1.0', $start, 100, 20); - $this->assertStringContainsString('3', (string) $page_index); - $this->assertStringContainsString('previous_page', (string) $page_index); - $this->assertStringContainsString('next_page', (string) $page_index); + $page_index = (string) $page_index; + + $this->assertSame(2, Utils::$context['current_page']); + $this->assertStringContainsString('3', $page_index); + $this->assertStringContainsString('previous_page', $page_index); + $this->assertStringContainsString('next_page', $page_index); } /** @@ -121,6 +148,9 @@ public function testAStartInTheMiddleOfAPageIsMovedToItsStart(): void $page_index = new PageIndex('https://example.com/index.php?board=1.0', $start, 100, 20); $this->assertSame(40, $start); + $this->assertSame(40, $page_index->start); + $this->assertSame(2, Utils::$context['current_page']); + $this->assertStringContainsString('3', (string) $page_index); } @@ -134,8 +164,189 @@ public function testAStartPastTheEndLandsOnTheLastPage(): void $page_index = new PageIndex('https://example.com/index.php?board=1.0', $start, 100, 20); $this->assertSame(80, $start); - $this->assertStringContainsString('5', (string) $page_index); - $this->assertStringNotContainsString('next_page', (string) $page_index); + $this->assertSame(80, $page_index->start); + $this->assertSame(4, Utils::$context['current_page']); + + $page_index = (string) $page_index; + + $this->assertStringContainsString('5', $page_index); + $this->assertStringNotContainsString('next_page', $page_index); + } + + public function testShortFormatUsesOffsetInTheUrl(): void + { + $start = 20; + $page_index = new PageIndex('index.php?board=1.%1$d', $start, 100, 20, true); + + $page_index = (string) $page_index; + + $this->assertStringContainsString('href="index.php?board=1.0"', $page_index); + $this->assertStringContainsString('href="index.php?board=1.80">5', $page_index); + } + + public function testDefaultFormatUsesStartParameterInTheUrl(): void + { + $start = 20; + $page_index = new PageIndex('index.php?board=1', $start, 100, 20); + + $page_index = (string) $page_index; + + $this->assertStringContainsString('href="index.php?board=1;start=0"', $page_index); + + $this->assertStringContainsString('href="index.php?board=1;start=40"', $page_index); + } + + public function testPreviousAndNextLinksCanBeDisabled(): void + { + $start = 40; + $page_index = new PageIndex('index.php?board=1', $start, 100, 20, false, false); + + $page_index = (string) $page_index; + + $this->assertStringNotContainsString('previous_page', $page_index); + $this->assertStringNotContainsString('next_page', $page_index); + $this->assertStringContainsString('3', $page_index); + } + + public function testTemplateOverridesReplaceDefaultTemplates(): void + { + $start = 20; + + $page_index = new PageIndex('index.php?board=1', $start, 100, 20, false, true, [ + 'current_page' => '%1$d ', + 'page' => '%2$s ', + 'previous_page' => 'PREVIOUS ', + 'next_page' => 'NEXT ', + ]); + + $page_index = (string) $page_index; + + $this->assertStringContainsString('2', $page_index); + $this->assertStringContainsString('1', $page_index); + $this->assertStringContainsString('PREVIOUS', $page_index); + $this->assertStringContainsString('NEXT', $page_index); + } + + public function testUnknownTemplateOverrideIsIgnored(): void + { + $start = 0; + + $page_index = new PageIndex('index.php?board=1', $start, 40, 20, false, true, [ + 'does_not_exist' => 'unexpected', + ]); + + $this->assertStringNotContainsString('unexpected', (string) $page_index); + } + + public function testSetTemplateOverridesCanBeAppliedAfterConstruction(): void + { + $start = 20; + + $page_index = new PageIndex('index.php?board=1', $start, 100, 20); + + $page_index->setTemplateOverrides([ + 'current_page' => '%1$d ', + 'previous_page' => 'BACK ', + 'next_page' => 'FORWARD ', + ]); + + $page_index = (string) $page_index; + + $this->assertStringContainsString('2', $page_index); + $this->assertStringContainsString('BACK', $page_index); + $this->assertStringContainsString('FORWARD', $page_index); + } + + public function testCompactPagesCanBeDisabled(): void + { + Config::$modSettings['compactTopicPagesEnable'] = 0; + + $start = 40; + $page_index = new PageIndex('index.php?board=1', $start, 300, 20); + + $page_index = (string) $page_index; + + for ($page = 1; $page <= 15; $page++) { + $this->assertMatchesRegularExpression(\sprintf('/>%d<\/(?:span|a)>/', $page), $page_index); + } + + $this->assertStringNotContainsString('expandPages', $page_index); + } + + public function testCompactPagesCanBeEnabled(): void + { + Config::$modSettings['compactTopicPagesEnable'] = 1; + Config::$modSettings['compactTopicPagesContiguous'] = 5; + + $start = 140; + $page_index = new PageIndex('index.php?board=1', $start, 300, 20); + + $page_index = (string) $page_index; + + $this->assertStringContainsString('8', $page_index); + + $this->assertStringContainsString('expandPages', $page_index); + } + + public function testOddCompactPageCountIsRoundedDown(): void + { + Config::$modSettings['compactTopicPagesEnable'] = 1; + Config::$modSettings['compactTopicPagesContiguous'] = 4; + + $start = 140; + $page_index = new PageIndex('index.php?board=1', $start, 300, 20); + + $page_index = (string) $page_index; + + $this->assertStringContainsString('8', $page_index); + } + + /*********************** + * Public static methods + ***********************/ + + /** + * Provides values that exercise start normalisation. + * + * @return array + */ + public static function pageIndexProvider(): array + { + return [ + 'negative two' => [-2, 15, 5, 0, 0], + 'negative one' => [-1, 15, 5, 0, 0], + 'zero' => [0, 15, 5, 0, 0], + + 'first page' => [1, 15, 5, 0, 0], + 'page two' => [5, 15, 5, 5, 1], + 'page two through four' => [6, 15, 5, 5, 1], + 'page three' => [10, 15, 5, 10, 2], + + 'past last page' => [15, 15, 5, 10, 2], + 'far past last page' => [21, 15, 5, 10, 2], + + 'large values' => [3000001, 4205, 42, 4200, 100], + + 'max sixteen' => [6, 16, 5, 5, 1], + 'max seventeen' => [6, 17, 5, 5, 1], + 'max eighteen' => [6, 18, 5, 5, 1], + 'max nineteen' => [6, 19, 5, 5, 1], + ]; + } + + /** + * Provides starts that should identify a particular rendered page. + * + * @return array + */ + public static function pageRenderingProvider(): array + { + return [ + 'first page' => [0, 100, 20, 1], + 'second page' => [20, 100, 20, 2], + 'middle page' => [40, 100, 20, 3], + 'last page' => [80, 100, 20, 5], + ]; } /****************** diff --git a/tests/bootstrap.php b/tests/bootstrap.php index a97e864c99..6b232ec369 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -64,3 +64,4 @@ SMF\Config::$languagesdir = SMF\Config::$boarddir . '/Languages'; SMF\Config::$cachedir = SMF\Config::$boarddir . '/cache'; SMF\Config::$language = 'en_US'; +SMF\Config::$scripturl = 'test.local/index.php';