From 377c1e6b6a9a3f7d0b572469c8aed3b81448f0ae Mon Sep 17 00:00:00 2001 From: Morne Alberts Date: Fri, 12 Jun 2026 14:34:49 +0200 Subject: [PATCH 01/10] CI: detect the PHPUnit runner and add MediaWiki 1.46 MediaWiki 1.46 removed tests/phpunit/phpunit.php in favour of generating a config with `composer phpunit:config` and running vendor/bin/phpunit. Pick the runner by detecting whether the old entry point still exists, so every current and future MediaWiki version is covered without per-version bookkeeping. Force the integration-test bootstrap with MEDIAWIKI_HAS_INTEGRATION_TESTS so the path-based bootstrap selection (unit-only vs integration) doesn't drop MediaWiki settings on Unit-suite tests that construct objects using MediaWiki services. Add an experimental REL1_46 row; it exercises the new runner. Backported from master commits 6d36544 and 345595a. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/ci.yml | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df5ea06..973aa88 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,6 +37,10 @@ jobs: php: 8.3 type: normal experimental: true + - mw: 'REL1_46' + php: 8.4 + type: normal + experimental: true - mw: 'master' php: 8.4 type: normal @@ -103,12 +107,36 @@ jobs: run: bash EarlyCopy/.github/workflows/uploadImages.sh - if: env.TYPE != 'coverage' - name: Run PHPUnit w/o coverage - run: php tests/phpunit/phpunit.php -c extensions/BootstrapComponents/ --testsuite bootstrap-components-unit + name: Run PHPUnit + env: + MEDIAWIKI_HAS_INTEGRATION_TESTS: '1' + run: | + if [ -f tests/phpunit/phpunit.php ]; then + php tests/phpunit/phpunit.php -c extensions/BootstrapComponents/ --testsuite bootstrap-components-unit + else + # MW 1.46 and later + if [ ! -f phpunit.xml.template ]; then + wget -q -O phpunit.xml.template "https://raw.githubusercontent.com/wikimedia/mediawiki/${{ matrix.mw }}/phpunit.xml.template" + fi + composer phpunit:config + vendor/bin/phpunit -c phpunit.xml extensions/BootstrapComponents/tests/phpunit/Unit + fi - if: env.TYPE == 'coverage' - name: Run PHPUnit w/ coverage - run: php tests/phpunit/phpunit.php -c extensions/BootstrapComponents/ --testsuite bootstrap-components-unit --coverage-clover coverage.clover + name: Run PHPUnit (coverage) + env: + MEDIAWIKI_HAS_INTEGRATION_TESTS: '1' + run: | + if [ -f tests/phpunit/phpunit.php ]; then + php tests/phpunit/phpunit.php -c extensions/BootstrapComponents/ --testsuite bootstrap-components-unit --coverage-clover coverage.clover + else + # MW 1.46 and later + if [ ! -f phpunit.xml.template ]; then + wget -q -O phpunit.xml.template "https://raw.githubusercontent.com/wikimedia/mediawiki/${{ matrix.mw }}/phpunit.xml.template" + fi + composer phpunit:config + vendor/bin/phpunit -c phpunit.xml extensions/BootstrapComponents/tests/phpunit/Unit --coverage-clover coverage.clover + fi - if: env.TYPE == 'coverage' name: upload coverage report From 2bae97191f027062f7ced5d9122039b4e36b1879 Mon Sep 17 00:00:00 2001 From: Morne Alberts Date: Fri, 12 Jun 2026 11:25:59 +0200 Subject: [PATCH 02/10] tests: declare the Lua engine for the LuaLibrary tests Scribunto 1.46 requires each LuaEngineTestBase subclass to declare its engine via getEngineName(); the previous "one class exercises every engine" data-provider pattern is deprecated. Without it the LuaLibrary tests error on 1.46+ with "must implement getEngineName()". Target LuaStandalone, whose interpreter Scribunto bundles, so the tests run in CI without installing a Lua extension. Pre-1.46 MediaWiki does not call getEngineName() (it runs every configured engine through its own suite() machinery), so the method is inert there: the REL1_43 to REL1_45 rows are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/phpunit/Unit/LuaLibraryTestBase.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/phpunit/Unit/LuaLibraryTestBase.php b/tests/phpunit/Unit/LuaLibraryTestBase.php index 971548b..5fe254a 100644 --- a/tests/phpunit/Unit/LuaLibraryTestBase.php +++ b/tests/phpunit/Unit/LuaLibraryTestBase.php @@ -22,6 +22,10 @@ abstract class LuaLibraryTestBase extends Scribunto_LuaEngineTestBase */ private $luaLibrary; + protected function getEngineName(): string { + return 'LuaStandalone'; + } + /** * @throws \MWException */ From f1c06b6a417520f39cea26525b84a2a580e395e7 Mon Sep 17 00:00:00 2001 From: Morne Alberts Date: Fri, 12 Jun 2026 11:42:01 +0200 Subject: [PATCH 03/10] tests: return a string from the mocked Parser::recursiveTagParse MediaWiki master declares Parser::recursiveTagParse() and recursiveTagParseFully() with a `string` return type. The component test mocks stub them with returnArgument(0), echoing the input verbatim. When a component passes a non-string attribute value (a bool or int) to the parser, the mock returns that non-string and PHPUnit's typed mock throws "Return value must be of type string". Cast the echoed value to string so the mocks honour the method's contract. Behaviour is unchanged for string inputs, and the renderErrorMessage mock is left as-is. Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/phpunit/Unit/CarouselGalleryTest.php | 4 +++- tests/phpunit/Unit/ComponentsTestBase.php | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/phpunit/Unit/CarouselGalleryTest.php b/tests/phpunit/Unit/CarouselGalleryTest.php index be566f6..85c6b41 100644 --- a/tests/phpunit/Unit/CarouselGalleryTest.php +++ b/tests/phpunit/Unit/CarouselGalleryTest.php @@ -53,7 +53,9 @@ public function testToHtml( $imageList, $additionalAttributes, $expectedOutput ) ->getMock(); $instance->mParser->expects( $this->any() ) ->method( 'recursiveTagParse' ) - ->will( $this->returnArgument( 0 ) ); + ->will( $this->returnCallback( function ( $text ) { + return (string)$text; + } ) ); foreach ( $imageList as $imageData ) { $instance->add( Title::newFromText( $imageData[0] ), $imageData[1], $imageData[2], $imageData[3], $imageData[4] ); diff --git a/tests/phpunit/Unit/ComponentsTestBase.php b/tests/phpunit/Unit/ComponentsTestBase.php index c7ef2ab..14357e4 100644 --- a/tests/phpunit/Unit/ComponentsTestBase.php +++ b/tests/phpunit/Unit/ComponentsTestBase.php @@ -61,10 +61,14 @@ public function setUp(): void { $this->parser = $this->createMock( Parser::class ); $this->parser->expects( $this->any() ) ->method( 'recursiveTagParse' ) - ->will( $this->returnArgument( 0 ) ); + ->will( $this->returnCallback( function ( $text ) { + return (string)$text; + } ) ); $this->parser->expects( $this->any() ) ->method( 'recursiveTagParseFully' ) - ->will( $this->returnArgument( 0 ) ); + ->will( $this->returnCallback( function ( $text ) { + return (string)$text; + } ) ); $this->parserOutputHelper = $this->createMock( ParserOutputHelper::class ); $this->parserOutputHelper->expects( $this->any() ) ->method( 'renderErrorMessage' ) From 2af3e688f99d0144026da45d7dc262888bec83ff Mon Sep 17 00:00:00 2001 From: Morne Alberts Date: Fri, 12 Jun 2026 12:55:09 +0200 Subject: [PATCH 04/10] tests: add @covers annotations to the service-wiring tests The three service-wiring construction tests lacked @covers annotations, so MediaWiki's generated PHPUnit config (which sets forceCoversAnnotation) flagged them as risky. Annotate each test method with the class it exercises through the extension's service wiring: BootstrapComponentsService, ComponentLibrary, and NestingController. Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/phpunit/Unit/ServiceWiringTest.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/phpunit/Unit/ServiceWiringTest.php b/tests/phpunit/Unit/ServiceWiringTest.php index 0a0675f..f812c9f 100644 --- a/tests/phpunit/Unit/ServiceWiringTest.php +++ b/tests/phpunit/Unit/ServiceWiringTest.php @@ -22,6 +22,9 @@ class ServiceWiringTest extends TestCase { + /** + * @covers \MediaWiki\Extension\BootstrapComponents\BootstrapComponentsService + */ public function testCanConstructBootstrapComponentsService() { $this->assertInstanceOf( BootstrapComponentsService::class, @@ -29,6 +32,9 @@ public function testCanConstructBootstrapComponentsService() { ); } + /** + * @covers \MediaWiki\Extension\BootstrapComponents\ComponentLibrary + */ public function testCanConstructComponentLibrary() { $this->assertInstanceOf( ComponentLibrary::class, @@ -36,6 +42,9 @@ public function testCanConstructComponentLibrary() { ); } + /** + * @covers \MediaWiki\Extension\BootstrapComponents\NestingController + */ public function testCanConstructNestingController() { $this->assertInstanceOf( NestingController::class, From 89d6748fbc21995c429f1d510056510bf9153c8d Mon Sep 17 00:00:00 2001 From: Morne Alberts Date: Fri, 12 Jun 2026 13:12:38 +0200 Subject: [PATCH 05/10] tests: return a ParserOutput from the mocked getOutput MediaWiki master declares Parser::getOutput(): ParserOutput, so the two ParserOutputHelper tests that stub it with willReturn( false ) trip PHPUnit's typed mock (IncompatibleReturnValueException). The false only ever drove a guard in ParserOutputHelper that the code comments mark as test-only; the real getOutput() always returns a ParserOutput. Return one from the mock, matching the contract and setUp's existing ParserOutput. Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/phpunit/Unit/ParserOutputHelperTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/phpunit/Unit/ParserOutputHelperTest.php b/tests/phpunit/Unit/ParserOutputHelperTest.php index 5fda1a2..0f5df01 100644 --- a/tests/phpunit/Unit/ParserOutputHelperTest.php +++ b/tests/phpunit/Unit/ParserOutputHelperTest.php @@ -59,7 +59,7 @@ public function testCanAddErrorTrackingCategory() { $parser = $this->createMock( 'Parser' ); $parser->expects( $this->once() ) ->method( 'getOutput' ) - ->willReturn( false ); + ->willReturn( new ParserOutput( 'ParserOutputMockText' ) ); $instance = new ParserOutputHelper( $parser ); @@ -71,7 +71,7 @@ public function testCanAddTrackingCategory() { $parser = $this->createMock( 'Parser' ); $parser->expects( $this->once() ) ->method( 'getOutput' ) - ->willReturn( false ); + ->willReturn( new ParserOutput( 'ParserOutputMockText' ) ); $instance = new ParserOutputHelper( $parser ); From 044f2482fac50b4813f7f53e333ae594df0cafda Mon Sep 17 00:00:00 2001 From: Morne Alberts Date: Fri, 12 Jun 2026 13:27:31 +0200 Subject: [PATCH 06/10] tests: clear the PHP 8.5 deprecation and filename warnings PHP 8.5 deprecates ReflectionMethod::setAccessible(); it has been a no-op since 8.1, so the reflective invokeArgs() in BootstrapComponentsServiceTest works without it. Drop the call to clear the deprecation on the master/PHP 8.5 row; it is a no-op on the older rows. Rename three test files whose names did not match the class they declare, which PHPUnit flags as "test case class not matching filename" (the class names are already correct and are left unchanged): - BootstrapComponentServiceTest.php -> BootstrapComponentsServiceTest.php - BootstrapComponentsJsonTestCaseScriptRunnerTest.php -> BootstrapComponentsJSONScriptTestCaseRunnerTest.php - readmeContentsBuilder.php -> ReadmeContentsBuilder.php Co-Authored-By: Claude Opus 4.8 (1M context) --- ...t.php => BootstrapComponentsJSONScriptTestCaseRunnerTest.php} | 0 .../{readmeContentsBuilder.php => ReadmeContentsBuilder.php} | 0 ...mponentServiceTest.php => BootstrapComponentsServiceTest.php} | 1 - 3 files changed, 1 deletion(-) rename tests/phpunit/Integration/JSONScript/{BootstrapComponentsJsonTestCaseScriptRunnerTest.php => BootstrapComponentsJSONScriptTestCaseRunnerTest.php} (100%) rename tests/phpunit/Integration/JSONScript/{readmeContentsBuilder.php => ReadmeContentsBuilder.php} (100%) rename tests/phpunit/Unit/{BootstrapComponentServiceTest.php => BootstrapComponentsServiceTest.php} (98%) diff --git a/tests/phpunit/Integration/JSONScript/BootstrapComponentsJsonTestCaseScriptRunnerTest.php b/tests/phpunit/Integration/JSONScript/BootstrapComponentsJSONScriptTestCaseRunnerTest.php similarity index 100% rename from tests/phpunit/Integration/JSONScript/BootstrapComponentsJsonTestCaseScriptRunnerTest.php rename to tests/phpunit/Integration/JSONScript/BootstrapComponentsJSONScriptTestCaseRunnerTest.php diff --git a/tests/phpunit/Integration/JSONScript/readmeContentsBuilder.php b/tests/phpunit/Integration/JSONScript/ReadmeContentsBuilder.php similarity index 100% rename from tests/phpunit/Integration/JSONScript/readmeContentsBuilder.php rename to tests/phpunit/Integration/JSONScript/ReadmeContentsBuilder.php diff --git a/tests/phpunit/Unit/BootstrapComponentServiceTest.php b/tests/phpunit/Unit/BootstrapComponentsServiceTest.php similarity index 98% rename from tests/phpunit/Unit/BootstrapComponentServiceTest.php rename to tests/phpunit/Unit/BootstrapComponentsServiceTest.php index 2c1d8e8..6c6beb4 100644 --- a/tests/phpunit/Unit/BootstrapComponentServiceTest.php +++ b/tests/phpunit/Unit/BootstrapComponentsServiceTest.php @@ -70,7 +70,6 @@ public function testPrivateCanDetectSkinInUse() { $reflection = new ReflectionClass( BootstrapComponentsService::class ); $method = $reflection->getMethod( 'detectSkinInUse' ); - $method->setAccessible( true ); // this is default $this->assertEquals( From 047c40ae9b17703b11d19d3a16d6105d88cf4bd6 Mon Sep 17 00:00:00 2001 From: Morne Alberts Date: Fri, 12 Jun 2026 13:40:08 +0200 Subject: [PATCH 07/10] CI: bump the Node-based actions off the deprecated Node 20 runtime GitHub is removing the Node 20 runtime; actions/checkout@v4 and actions/cache@v4 run on it and emit a deprecation warning in the logs. Bump both to v5, which run on Node 24. shivammathur/setup-php stays on its current rolling major (v2), which already runs on Node 24 and was not flagged. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 973aa88..0689f59 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,7 +72,7 @@ jobs: - name: Cache MediaWiki id: cache-mediawiki - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: | mediawiki @@ -81,12 +81,12 @@ jobs: key: mw_${{ matrix.mw }}-php${{ matrix.php }} - name: Cache Composer cache - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ~/.composer/cache key: composer-php${{ matrix.php }} - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: path: EarlyCopy @@ -95,7 +95,7 @@ jobs: working-directory: ~ run: bash EarlyCopy/.github/workflows/installWiki.sh ${{ matrix.mw }} BootstrapComponents - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: path: mediawiki/extensions/BootstrapComponents From 78e2629b31db711d94f4ec43f20af717fc17f3be Mon Sep 17 00:00:00 2001 From: Morne Alberts Date: Thu, 11 Jun 2026 19:56:20 +0200 Subject: [PATCH 08/10] tests: extend Scribunto's namespaced LuaEngineTestBase Scribunto namespaced its Lua test base as MediaWiki\Extension\Scribunto\Tests\Engines\LuaCommon\LuaEngineTestBase and dropped the legacy Scribunto_LuaEngineTestBase alias on MediaWiki master. The namespaced class is present on every supported MediaWiki (1.43+), so use it directly. Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/phpunit/Unit/LuaLibraryTestBase.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/phpunit/Unit/LuaLibraryTestBase.php b/tests/phpunit/Unit/LuaLibraryTestBase.php index 5fe254a..d66139a 100644 --- a/tests/phpunit/Unit/LuaLibraryTestBase.php +++ b/tests/phpunit/Unit/LuaLibraryTestBase.php @@ -3,7 +3,7 @@ namespace MediaWiki\Extension\BootstrapComponents\Tests\Unit; use MediaWiki\Extension\BootstrapComponents\LuaLibrary; -use \Scribunto_LuaEngineTestBase; +use MediaWiki\Extension\Scribunto\Tests\Engines\LuaCommon\LuaEngineTestBase; /** * @ingroup Test @@ -15,7 +15,7 @@ * @since 1.1 * @author Tobias Oetterer */ -abstract class LuaLibraryTestBase extends Scribunto_LuaEngineTestBase +abstract class LuaLibraryTestBase extends LuaEngineTestBase { /** * @var LuaLibrary From 0c7d7a80e5c7fed563c018a9dab10000bc439ebf Mon Sep 17 00:00:00 2001 From: Morne Alberts Date: Fri, 12 Jun 2026 14:44:14 +0200 Subject: [PATCH 09/10] CI: don't fail-fast so REL1_46 runs even when older MW jobs error The MediaWiki REL1_39-REL1_42 rows have been bitrotting on the 5.x branch (the tarball install or composer step fails for reasons unrelated to this PR). With fail-fast on, those failures cancel the REL1_46 job mid-run before the new PHPUnit runner gets exercised. Disable fail-fast so each MW row reports its own result independently. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0689f59..f671865 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,7 @@ jobs: name: "PHPUnit: MW ${{ matrix.mw }}, PHP ${{ matrix.php }} (TYPE ${{ matrix.type }})" strategy: + fail-fast: false matrix: include: - mw: 'REL1_39' From 05a6cc314b17f3c8dbacf9105f17d6799e96af91 Mon Sep 17 00:00:00 2001 From: Morne Alberts Date: Fri, 12 Jun 2026 14:47:26 +0200 Subject: [PATCH 10/10] CI: move MEDIAWIKI_HAS_INTEGRATION_TESTS inline on the vendor/bin/phpunit calls Matches the master pattern; the step-level env block I had in the initial backport didn't take effect (the new-runner job hit "Class Title not found" on all 99 service-using unit tests). Co-Authored-By: Claude Opus 4.7 --- .github/workflows/ci.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f671865..8f52052 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -109,8 +109,6 @@ jobs: - if: env.TYPE != 'coverage' name: Run PHPUnit - env: - MEDIAWIKI_HAS_INTEGRATION_TESTS: '1' run: | if [ -f tests/phpunit/phpunit.php ]; then php tests/phpunit/phpunit.php -c extensions/BootstrapComponents/ --testsuite bootstrap-components-unit @@ -120,13 +118,12 @@ jobs: wget -q -O phpunit.xml.template "https://raw.githubusercontent.com/wikimedia/mediawiki/${{ matrix.mw }}/phpunit.xml.template" fi composer phpunit:config - vendor/bin/phpunit -c phpunit.xml extensions/BootstrapComponents/tests/phpunit/Unit + # Temporary integration-test workaround; see master issue #98. + MEDIAWIKI_HAS_INTEGRATION_TESTS=1 vendor/bin/phpunit -c phpunit.xml extensions/BootstrapComponents/tests/phpunit/Unit fi - if: env.TYPE == 'coverage' name: Run PHPUnit (coverage) - env: - MEDIAWIKI_HAS_INTEGRATION_TESTS: '1' run: | if [ -f tests/phpunit/phpunit.php ]; then php tests/phpunit/phpunit.php -c extensions/BootstrapComponents/ --testsuite bootstrap-components-unit --coverage-clover coverage.clover @@ -136,7 +133,8 @@ jobs: wget -q -O phpunit.xml.template "https://raw.githubusercontent.com/wikimedia/mediawiki/${{ matrix.mw }}/phpunit.xml.template" fi composer phpunit:config - vendor/bin/phpunit -c phpunit.xml extensions/BootstrapComponents/tests/phpunit/Unit --coverage-clover coverage.clover + # Temporary integration-test workaround; see master issue #98. + MEDIAWIKI_HAS_INTEGRATION_TESTS=1 vendor/bin/phpunit -c phpunit.xml extensions/BootstrapComponents/tests/phpunit/Unit --coverage-clover coverage.clover fi - if: env.TYPE == 'coverage'