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';