From f45b1b6fd240772df3bad956055941ab5d4c732a Mon Sep 17 00:00:00 2001 From: Morne Alberts Date: Wed, 20 May 2026 16:28:38 +0200 Subject: [PATCH 1/9] Migrate BootstrapComponents to Bootstrap 5.3 Port the parser-hook output of every component from Bootstrap 4 to Bootstrap 5.3: - All data-attribute names switched to the data-bs-* family (data-toggle/target/dismiss/ride/slide/parent/content/placement/trigger). - Badge: .badge-pill replaced with the .rounded-pill utility. - Button: .btn-default is gone in BS5; color='default' now maps to btn-secondary (Modal, Popover apply the same mapping for their trigger buttons). - Alert / Modal close buttons: .close + inner × replaced with the BS5 .btn-close button. - Jumbotron: the .jumbotron class was removed in BS5. The parser function is retained for backward compatibility but now emits the BS5 utility-class approximation per the official migration guide ('p-5 mb-4 bg-body-tertiary rounded-3'). - JavaScript modules for carousel/popover/tooltip rewritten from jQuery to vanilla JS (BS5 dropped jQuery as a dependency). - Test expectations updated to the new emitted markup. Floor versions bumped to MediaWiki >= 1.43, PHP >= 8.1, and mediawiki/bootstrap ^6.0 (the BS5.3-bearing major). Extension version 6.0.0-dev. README mention updated. Co-Authored-By: Claude Opus 4.7 --- README.md | 11 ++- composer.json | 13 ++- docs/migration-guide.md | 92 ++++++++++++++++++- docs/release-notes.md | 34 +++++++ extension.json | 4 +- .../ext.bootstrapComponents.button.fix.css | 12 +-- modules/ext.bootstrapComponents.carousel.js | 28 +++++- modules/ext.bootstrapComponents.popover.js | 33 +++++-- modules/ext.bootstrapComponents.tooltip.js | 28 ++++-- src/Components/Alert.php | 17 +--- src/Components/Badge.php | 2 +- src/Components/Button.php | 7 +- src/Components/Card.php | 12 +-- src/Components/Carousel.php | 30 +++--- src/Components/Collapse.php | 2 +- src/Components/Jumbotron.php | 12 ++- src/Components/Modal.php | 13 ++- src/Components/Popover.php | 17 ++-- src/Components/Tooltip.php | 6 +- src/ModalBuilder.php | 33 +++---- 20 files changed, 296 insertions(+), 110 deletions(-) diff --git a/README.md b/README.md index b4683f7..1c259d6 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Bootstrap Components is a [MediaWiki] extension that aims to provide editors with easy access to certain components introduced by -[Twitter Bootstrap 4][Bootstrap]. +[Twitter Bootstrap 5][Bootstrap]. Depending on your configuration, editors can utilize several _tag extensions_ and _parser functions_ inside wiki code to place certain @@ -16,8 +16,8 @@ configuration it can add a new [gallery][Gallery] mode, and replace normal [image rendering][Image] with an image modal. ## Requirements -* PHP 8.0 or later -* MediaWiki 1.39 or later +* PHP 8.1 or later +* MediaWiki 1.43 or later ## Documentation - [Installation and configuration](docs/installation-configuration.md) @@ -31,8 +31,9 @@ configuration it can add a new [gallery][Gallery] mode, and replace normal Please also see the [known issues][known-issues] section. -There is also a [migration guide](docs/migration-guide.md) for users switching -from bootstrap3 (BootstrapComponents ~1.2) to bootstrap4 (BootstrapComponents ~4.0). +There is also a [migration guide](docs/migration-guide.md) for users switching between versions: +- Bootstrap 3 → Bootstrap 4: BootstrapComponents ~1.2 → ~4.0 +- Bootstrap 4 → Bootstrap 5: BootstrapComponents ~5.x → ~6.0 ## Contact For bug reports and feature requests, please see if it is already reported on diff --git a/composer.json b/composer.json index 3691d76..9b5b049 100644 --- a/composer.json +++ b/composer.json @@ -25,15 +25,15 @@ "source": "https://github.com/oetterer/BootstrapComponents" }, "require": { - "php": ">=8.0", + "php": ">=8.1", "composer/installers": "^2|^1.0.1", - "mediawiki/bootstrap": "^5.0" + "mediawiki/bootstrap": "^6.0" }, "require-dev": { "mediawiki/mediawiki-codesniffer": "46.0.0", - "mediawiki/mediawiki-phan-config": "0.15.1", + "mediawiki/mediawiki-phan-config": "0.15.1", "phpmd/phpmd": "~2.1", - "php": ">=8.0" + "php": ">=8.1" }, "suggest": { "mediawiki/scribunto": "Framework for embedding scripting languages into MediaWiki pages" @@ -58,5 +58,10 @@ "composer phpunit", "composer cs" ] + }, + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } } } diff --git a/docs/migration-guide.md b/docs/migration-guide.md index 8a84850..526663a 100644 --- a/docs/migration-guide.md +++ b/docs/migration-guide.md @@ -1,7 +1,97 @@ ## Migration Guide +### Migrating from Bootstrap 4 (BootstrapComponents 5.x) to Bootstrap 5 (BootstrapComponents 6.0) + +BootstrapComponents 6.0 upgrades the underlying Bootstrap framework from version 4 to version 5.3. This guide helps you understand the changes and migrate your wiki. + +#### System Requirements +- **MediaWiki:** 1.43 or later (upgraded from 1.39) +- **PHP:** 8.1 or later (upgraded from 8.0) +- **Bootstrap Extension:** mediawiki/bootstrap 6.x-dev from [ProfessionalWiki/Bootstrap](https://github.com/ProfessionalWiki/Bootstrap) + +#### User Impact +**Good News:** Most existing wiki markup using BootstrapComponents should continue to work without any changes! The extension handles most Bootstrap 5 migrations internally. + +#### Component Changes + +##### Jumbotron +The Jumbotron component still works but now uses Bootstrap 5 utility classes instead of the removed `.jumbotron` class. The visual appearance should be similar but may have minor differences. Consider: +- Using utility classes directly: `
` +- Or continue using `` tag which is now implemented using these utilities +- Reference: https://getbootstrap.com/docs/5.3/examples/jumbotron/ + +##### Button Colors +The `color="default"` attribute is automatically mapped to `color="secondary"` in Bootstrap 5. If you prefer different colors: +- Use `color="light"` for light gray buttons +- Use `color="secondary"` explicitly for the standard secondary color + +##### Badge Pill +If you're using custom CSS targeting `.badge-pill`, update to `.rounded-pill`: +```css +/* Old */ +.badge-pill { ... } + +/* New */ +.rounded-pill { ... } +``` + +##### Alert and Modal Close Buttons +Close buttons now use Bootstrap 5's `.btn-close` class. If you have custom CSS targeting `.close`: +```css +/* Old */ +.close { ... } + +/* New */ +.btn-close { ... } +``` + +#### JavaScript Changes +Bootstrap 5 removed jQuery dependency. If you have custom JavaScript interacting with Bootstrap components: + +**Old (Bootstrap 4 + jQuery):** +```javascript +$('.carousel').carousel(); +$('[data-toggle="tooltip"]').tooltip(); +``` + +**New (Bootstrap 5 vanilla JS):** +```javascript +document.querySelectorAll('.carousel').forEach(el => { + new bootstrap.Carousel(el); +}); +document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(el => { + new bootstrap.Tooltip(el); +}); +``` + +#### Data Attributes +If you're using custom HTML with Bootstrap data attributes, update to the `data-bs-*` prefix: +- `data-toggle` → `data-bs-toggle` +- `data-target` → `data-bs-target` +- `data-dismiss` → `data-bs-dismiss` +- `data-slide` → `data-bs-slide` +- `data-parent` → `data-bs-parent` +- `data-content` → `data-bs-content` +- `data-placement` → `data-bs-placement` +- `data-trigger` → `data-bs-trigger` + +#### Testing Your Upgrade +After upgrading: +1. Test all pages using BootstrapComponents +2. Verify modals, tooltips, popovers, and carousels work correctly +3. Check custom CSS for compatibility +4. Test any custom JavaScript interacting with Bootstrap +5. Verify with different MediaWiki skins (Vector, Vector-2022) + +#### Rollback +If you encounter issues, you can rollback to BootstrapComponents 5.x which uses Bootstrap 4. + +--- + +### Migrating from Bootstrap 3 (BootstrapComponents 1.x) to Bootstrap 4 (BootstrapComponents 4.x-5.x) + There have been some changes between versions ~1.0 and ~4.0. Foremost is that -the new BootstrapComponents utilizes Twitter Bootstrap4. Therefore, it mirrors +the new BootstrapComponents utilizes Twitter Bootstrap 4. Therefore, it mirrors changes made by Bootstrap. Also, extension loading must now be done manually in your LocalSettings, no diff --git a/docs/release-notes.md b/docs/release-notes.md index 12bd99c..e0f6feb 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,5 +1,39 @@ ## Release Notes +### BootstrapComponents 6.0.0 + +Released on _TBD_ + +**BREAKING CHANGES:** +* Requires MediaWiki 1.43 or later (upgraded from 1.39) +* Requires PHP 8.1 or later (upgraded from 8.0) +* Upgraded from Bootstrap 4 to Bootstrap 5.3 +* Changed Bootstrap dependency from `mediawiki/bootstrap ^5.0` to `mediawiki/bootstrap ^6.0` + +**Bootstrap 5 Migration Changes:** +* All data attributes updated to Bootstrap 5 format with `data-bs-*` prefix: + - `data-toggle` → `data-bs-toggle` + - `data-target` → `data-bs-target` + - `data-dismiss` → `data-bs-dismiss` + - `data-slide` → `data-bs-slide` + - `data-parent` → `data-bs-parent` + - `data-content` → `data-bs-content` + - `data-placement` → `data-bs-placement` + - `data-trigger` → `data-bs-trigger` +* JavaScript modules migrated from jQuery to vanilla JavaScript (Bootstrap 5 requirement) +* Badge component: `badge-pill` class changed to `rounded-pill` +* Button component: `btn-default` automatically mapped to `btn-secondary` +* Alert and Modal close buttons: Migrated from `.close` class with `×` character to `.btn-close` class +* Jumbotron component: Recreated using Bootstrap 5 utility classes (`p-5 mb-4 bg-body-tertiary rounded-3`); the parser function is retained for compatibility but is now `@deprecated` +* All CSS fixes reviewed and updated for Bootstrap 5 compatibility + +**User Impact:** +* Most existing wiki markup using BootstrapComponents should continue to work without changes +* JavaScript initialization now uses Bootstrap 5 native API +* Custom CSS may need review if it targets Bootstrap 4-specific classes + +See [migration guide](migration-guide.md) for detailed upgrade instructions. + ### BootstrapComponents 5.2.2 Released on 01-April-2026 diff --git a/extension.json b/extension.json index a634f02..80eb3e8 100644 --- a/extension.json +++ b/extension.json @@ -1,13 +1,13 @@ { "name": "BootstrapComponents", - "version": "5.2.2", + "version": "6.0.0-dev", "author": [ "Tobias Oetterer" ], "url": "https://www.mediawiki.org/wiki/Extension:BootstrapComponents", "descriptionmsg": "bootstrap-components-desc", "license-name": "GPL-3.0-or-later", "type": "parserhook", "requires": { - "MediaWiki": ">= 1.39.0" + "MediaWiki": ">= 1.43.0" }, "ConfigRegistry": { "BootstrapComponents": "GlobalVarConfig::newInstance" diff --git a/modules/ext.bootstrapComponents.button.fix.css b/modules/ext.bootstrapComponents.button.fix.css index d5e78ac..2b537f1 100644 --- a/modules/ext.bootstrapComponents.button.fix.css +++ b/modules/ext.bootstrapComponents.button.fix.css @@ -25,12 +25,8 @@ /* * a href prioritizes the mw link color. overwrite this here to a more suitable one + * Bootstrap 5: btn-default removed */ -a.btn-default, -a.btn-default:link, -a.btn-default:visited, -a.btn-default:active, -a.btn-default:hover, a.btn-light, a.btn-light:link, a.btn-light:visited, @@ -79,19 +75,15 @@ a.btn-dark:hover { /** * outline link colors have to be defined separately + * Bootstrap 5: btn-outline-default removed */ -a.btn-outline-default, -a.btn-outline-default:link, -a.btn-outline-default:visited, -a.btn-outline-default:active, a.btn-outline-light, a.btn-outline-light:link, a.btn-outline-light:visited, a.btn-outline-light:active { color: var(--white); } -a.btn-outline-default:hover, a.btn-outline-light:hover { color: #212529; } diff --git a/modules/ext.bootstrapComponents.carousel.js b/modules/ext.bootstrapComponents.carousel.js index 941f604..eef7783 100644 --- a/modules/ext.bootstrapComponents.carousel.js +++ b/modules/ext.bootstrapComponents.carousel.js @@ -1,5 +1,7 @@ /** - * Contains javascript code executed when tooltips are used. + * Contains javascript code executed when carousels are used. + * + * Bootstrap 5 migration: Converted from jQuery to vanilla JavaScript. * * @copyright (C) 2018, Tobias Oetterer, Paderborn University * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License, version 3 (or later) @@ -23,6 +25,24 @@ * @author Tobias Oetterer */ -$(function () { - $('.carousel').carousel(); -}); +( function () { + 'use strict'; + + // Wait for DOM to be ready + if ( document.readyState === 'loading' ) { + document.addEventListener( 'DOMContentLoaded', initCarousels ); + } else { + initCarousels(); + } + + function initCarousels() { + // Bootstrap 5 automatically initializes carousels with data-bs-ride="carousel" + // Manual initialization for additional control + var carouselElements = document.querySelectorAll( '.carousel' ); + carouselElements.forEach( function ( element ) { + if ( typeof bootstrap !== 'undefined' && bootstrap.Carousel ) { + new bootstrap.Carousel( element ); + } + } ); + } +}() ); diff --git a/modules/ext.bootstrapComponents.popover.js b/modules/ext.bootstrapComponents.popover.js index 82a03ea..11f5f42 100644 --- a/modules/ext.bootstrapComponents.popover.js +++ b/modules/ext.bootstrapComponents.popover.js @@ -1,6 +1,8 @@ /** * Contains javascript code executed when popovers are used. * + * Bootstrap 5 migration: Converted from jQuery to vanilla JavaScript. + * * @copyright (C) 2018, Tobias Oetterer, Paderborn University * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License, version 3 (or later) * @@ -22,11 +24,26 @@ * @ingroup BootstrapComponents * @author Tobias Oetterer */ -$( function() { - $(document).ready(function(){ - $('[data-toggle="popover"]').popover({ - html: true - }); - }); - } -); + +( function () { + 'use strict'; + + // Wait for DOM to be ready + if ( document.readyState === 'loading' ) { + document.addEventListener( 'DOMContentLoaded', initPopovers ); + } else { + initPopovers(); + } + + function initPopovers() { + // Initialize all popovers with HTML enabled (Bootstrap 5 vanilla JS API) + var popoverTriggerList = document.querySelectorAll( '[data-bs-toggle="popover"]' ); + popoverTriggerList.forEach( function ( popoverTriggerEl ) { + if ( typeof bootstrap !== 'undefined' && bootstrap.Popover ) { + new bootstrap.Popover( popoverTriggerEl, { + html: true + } ); + } + } ); + } +}() ); diff --git a/modules/ext.bootstrapComponents.tooltip.js b/modules/ext.bootstrapComponents.tooltip.js index 575e021..0610aab 100644 --- a/modules/ext.bootstrapComponents.tooltip.js +++ b/modules/ext.bootstrapComponents.tooltip.js @@ -1,6 +1,8 @@ /** * Contains javascript code executed when tooltips are used. * + * Bootstrap 5 migration: Converted from jQuery to vanilla JavaScript. + * * @copyright (C) 2018, Tobias Oetterer, Paderborn University * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License, version 3 (or later) * @@ -23,9 +25,23 @@ * @author Tobias Oetterer */ -$( function() { - $(document).ready(function(){ - $('[data-toggle="tooltip"]').tooltip(); - }); - } -); \ No newline at end of file +( function () { + 'use strict'; + + // Wait for DOM to be ready + if ( document.readyState === 'loading' ) { + document.addEventListener( 'DOMContentLoading', initTooltips ); + } else { + initTooltips(); + } + + function initTooltips() { + // Initialize all tooltips with Bootstrap 5 vanilla JS API + var tooltipTriggerList = document.querySelectorAll( '[data-bs-toggle="tooltip"]' ); + tooltipTriggerList.forEach( function ( tooltipTriggerEl ) { + if ( typeof bootstrap !== 'undefined' && bootstrap.Tooltip ) { + new bootstrap.Tooltip( tooltipTriggerEl ); + } + } ); + } +}() ); \ No newline at end of file diff --git a/src/Components/Alert.php b/src/Components/Alert.php index aa55c00..210db46 100644 --- a/src/Components/Alert.php +++ b/src/Components/Alert.php @@ -109,18 +109,11 @@ private function renderDismissButton() { return Html::rawElement( 'button', [ - 'type' => 'button', - 'class' => 'close', - 'data-dismiss' => 'alert', - 'aria-label' => wfMessage( 'bootstrap-components-close-element' )->inContentLanguage()->text(), - ], - Html::rawElement( - 'span', - [ - 'aria-hidden' => 'true', - ], - '×' - ) + 'type' => 'button', + 'class' => 'btn-close', + 'data-bs-dismiss' => 'alert', + 'aria-label' => wfMessage( 'bootstrap-components-close-element' )->inContentLanguage()->text(), + ] ); } } diff --git a/src/Components/Badge.php b/src/Components/Badge.php index 1267cf2..94fdeb1 100644 --- a/src/Components/Badge.php +++ b/src/Components/Badge.php @@ -72,7 +72,7 @@ private function calculateClassAttribute() { $class = [ 'badge' ]; if ( (bool)$this->getValueFor( 'pill' ) ) { - $class[] = 'badge-pill'; + $class[] = 'rounded-pill'; } $class[] = 'badge-' . $this->getValueFor( 'color', 'primary' ); diff --git a/src/Components/Button.php b/src/Components/Button.php index e4e0597..574ae7d 100644 --- a/src/Components/Button.php +++ b/src/Components/Button.php @@ -106,7 +106,12 @@ private function calculateClassAttribute() { if ( (bool)$this->getValueFor( 'outline' ) ) { $colorClass .= 'outline-'; } - $class[] = $colorClass . $this->getValueFor( 'color', 'primary' ); + // Bootstrap 5 doesn't have btn-default, map to btn-secondary + $color = $this->getValueFor( 'color', 'primary' ); + if ( $color === 'default' ) { + $color = 'secondary'; + } + $class[] = $colorClass . $color; if ( $size = $this->getValueFor( 'size' ) ) { $class[] = "btn-" . $size; } diff --git a/src/Components/Card.php b/src/Components/Card.php index 9d41e9a..786fc2e 100644 --- a/src/Components/Card.php +++ b/src/Components/Card.php @@ -93,7 +93,7 @@ protected function placeMe( $input ) { 'class' => $this->arrayToString( $innerClass, ' ' ), ]; if ( $this->isCollapsible() ) { - $innerAttributes['data-parent'] = $this->getDataParent(); + $innerAttributes['data-bs-parent'] = $this->getDataParent(); $innerAttributes['aria-labelledby'] = $this->getId() . '_header'; } @@ -250,11 +250,11 @@ private function processAdditionToCard( string $type ): string { if ( $type == 'header' ) { if ( $this->isCollapsible() ) { $newAttributes += [ - 'data-toggle' => 'collapse', - 'data-target' => '#' . $this->getId(), - 'aria-controls' => $this->getId(), - 'aria-expanded' => $this->getValueFor( 'active' ) ? 'true' : 'false', - 'id' => $this->getId() . '_header' + 'data-bs-toggle' => 'collapse', + 'data-bs-target' => '#' . $this->getId(), + 'aria-controls' => $this->getId(), + 'aria-expanded' => $this->getValueFor( 'active' ) ? 'true' : 'false', + 'id' => $this->getId() . '_header' ]; } $inside = Html::rawElement( diff --git a/src/Components/Carousel.php b/src/Components/Carousel.php index 936ec79..688f159 100644 --- a/src/Components/Carousel.php +++ b/src/Components/Carousel.php @@ -60,10 +60,10 @@ protected function placeMe( $input ) { Html::rawElement( 'div', [ - 'class' => $this->arrayToString( $class, ' ' ), - 'style' => $this->arrayToString( $style, ';' ), - 'id' => $this->getId(), - 'data-ride' => 'carousel', + 'class' => $this->arrayToString( $class, ' ' ), + 'style' => $this->arrayToString( $style, ';' ), + 'id' => $this->getId(), + 'data-bs-ride' => 'carousel', ], $this->generateIndicators( count( $images ) ) . Html::rawElement( @@ -87,19 +87,19 @@ private function buildControls() { return Html::rawElement( 'a', [ - 'class' => 'carousel-control-prev', - 'href' => '#' . $this->getId(), - 'role' => 'button', - 'data-slide' => 'prev', + 'class' => 'carousel-control-prev', + 'href' => '#' . $this->getId(), + 'role' => 'button', + 'data-bs-slide' => 'prev', ], Html::rawElement( 'span', [ 'class' => 'carousel-control-prev-icon', 'aria-hidden' => 'true' ] ) ) . Html::rawElement( 'a', [ - 'class' => 'carousel-control-next', - 'href' => '#' . $this->getId(), - 'role' => 'button', - 'data-slide' => 'next', + 'class' => 'carousel-control-next', + 'href' => '#' . $this->getId(), + 'role' => 'button', + 'data-bs-slide' => 'next', ], Html::rawElement( 'span', [ 'class' => 'carousel-control-next-icon', 'aria-hidden' => 'true' ] ) ); @@ -188,9 +188,9 @@ private function generateIndicators( $num ) { $inner .= "\t" . Html::rawElement( 'li', [ - 'data-target' => '#' . $this->getId(), - 'data-slide-to' => $i, - 'class' => $class, + 'data-bs-target' => '#' . $this->getId(), + 'data-bs-slide-to' => $i, + 'class' => $class, ] ) . PHP_EOL; $class = false; diff --git a/src/Components/Collapse.php b/src/Components/Collapse.php index 838393c..af5fe2a 100644 --- a/src/Components/Collapse.php +++ b/src/Components/Collapse.php @@ -74,7 +74,7 @@ protected function placeMe( $input ) { */ private function generateButton( ParserRequest $parserRequest ) { $button = new Button( $this->getComponentLibrary(), $this->getParserOutputHelper(), $this->getNestingController() ); - $button->injectRawAttributes( [ 'data-toggle' => 'collapse' ] ); + $button->injectRawAttributes( [ 'data-bs-toggle' => 'collapse' ] ); $buttonAttributes = $parserRequest->getAttributes(); unset( $buttonAttributes['id'] ); diff --git a/src/Components/Jumbotron.php b/src/Components/Jumbotron.php index fc75de7..5a88de7 100644 --- a/src/Components/Jumbotron.php +++ b/src/Components/Jumbotron.php @@ -44,9 +44,17 @@ class Jumbotron extends AbstractComponent { * @param string $input */ protected function placeMe( $input ) { - list ( $class, $style ) = $this->processCss( 'jumbotron', [] ); + // Bootstrap 5 removed .jumbotron class + // Recreate it using Bootstrap 5 utility classes as per https://getbootstrap.com/docs/5.3/examples/jumbotron/ + $class = [ + 'p-5', // padding + 'mb-4', // margin-bottom + 'bg-body-tertiary', // background color (Bootstrap 5.3) + 'rounded-3' // border-radius + ]; + + list ( $class, $style ) = $this->processCss( $class, [] ); # @hack: the outer container is a workaround, to get all the necessary css if not inside a grid container - # @fixme: used inside mw content, the width calculation for smaller screens is broken (as of Bootstrap 1.2.3) return Html::rawElement( 'div', [ diff --git a/src/Components/Modal.php b/src/Components/Modal.php index 935bc61..a9a51ce 100644 --- a/src/Components/Modal.php +++ b/src/Components/Modal.php @@ -102,13 +102,18 @@ private function calculateInnerClass() { * @return string */ private function generateButton( $text ) { + // Bootstrap 5 doesn't have btn-default, map to btn-secondary + $color = $this->getValueFor( 'color', 'secondary' ); + if ( $color === 'default' ) { + $color = 'secondary'; + } return Html::rawElement( 'button', [ - 'type' => 'button', - 'class' => 'modal-trigger btn btn-' . $this->getValueFor( 'color', 'default' ), - 'data-toggle' => 'modal', - 'data-target' => '#' . $this->getId(), + 'type' => 'button', + 'class' => 'modal-trigger btn btn-' . $color, + 'data-bs-toggle' => 'modal', + 'data-bs-target' => '#' . $this->getId(), ], $text ); diff --git a/src/Components/Popover.php b/src/Components/Popover.php index 15b4195..e9fa8d9 100644 --- a/src/Components/Popover.php +++ b/src/Components/Popover.php @@ -87,11 +87,11 @@ private function buildHtmlElements( $input, $text, $heading ) { $attributes = array_merge( $attributes, [ - 'data-toggle' => 'popover', - 'title' => $heading, - 'data-content' => str_replace( "\n", " ", trim( $input ) ), - 'data-placement' => $this->getValueFor( 'placement' ), - 'data-trigger' => $this->getValueFor( 'trigger' ), + 'data-bs-toggle' => 'popover', + 'title' => $heading, + 'data-bs-content' => str_replace( "\n", " ", trim( $input ) ), + 'data-bs-placement' => $this->getValueFor( 'placement' ), + 'data-bs-trigger' => $this->getValueFor( 'trigger' ), ] ); $tag = "button"; @@ -109,7 +109,12 @@ private function buildHtmlElements( $input, $text, $heading ) { * @return string[] */ private function calculatePopoverClassAttribute() { - $class = [ 'btn', 'btn-' . $this->getValueFor( 'color', 'info' ) ]; + // Bootstrap 5 doesn't have btn-default, map to btn-secondary + $color = $this->getValueFor( 'color', 'info' ); + if ( $color === 'default' ) { + $color = 'secondary'; + } + $class = [ 'btn', 'btn-' . $color ]; if ( $size = $this->getValueFor( 'size' ) ) { $class[] = 'btn-' . $size; } diff --git a/src/Components/Tooltip.php b/src/Components/Tooltip.php index a73ba0e..7626a80 100644 --- a/src/Components/Tooltip.php +++ b/src/Components/Tooltip.php @@ -85,9 +85,9 @@ private function buildHtmlElements( $input, $tooltip ) { $attributes = array_merge( $attributes, [ - 'data-placement' => $this->getValueFor( 'placement' ), - 'data-toggle' => 'tooltip', - 'title' => $tooltip, + 'data-bs-placement' => $this->getValueFor( 'placement' ), + 'data-bs-toggle' => 'tooltip', + 'title' => $tooltip, ] ); $tag = "span"; diff --git a/src/ModalBuilder.php b/src/ModalBuilder.php index d1b6f23..c9080e5 100644 --- a/src/ModalBuilder.php +++ b/src/ModalBuilder.php @@ -123,9 +123,9 @@ public static function wrapTriggerElement( $element, $id ) { return Html::rawElement( 'span', [ - 'class' => 'modal-trigger', - 'data-toggle' => 'modal', - 'data-target' => '#' . $id, + 'class' => 'modal-trigger', + 'data-bs-toggle' => 'modal', + 'data-bs-target' => '#' . $id, ], $element ); @@ -331,8 +331,8 @@ protected function buildModal() { */ protected function buildTrigger() { $trigger = $this->getTrigger(); - if ( preg_match( '/data-toggle[^"]+"modal/', $trigger ) - && preg_match( '/data-target[^"]+"#' . $this->getId() . '"/', $trigger ) + if ( preg_match( '/data-bs-toggle[^"]+"modal/', $trigger ) + && preg_match( '/data-bs-target[^"]+"#' . $this->getId() . '"/', $trigger ) && preg_match( '/class[^"]+"[^"]*modal-trigger' . '/', $trigger ) ) { return $trigger; @@ -472,10 +472,10 @@ private function generateFooter( $footer = '' ) { $footer . Html::rawElement( 'button', [ - 'type' => 'button', - 'class' => 'btn btn-default', - 'data-dismiss' => 'modal', - 'aria-label' => $close, + 'type' => 'button', + 'class' => 'btn btn-secondary', + 'data-bs-dismiss' => 'modal', + 'aria-label' => $close, ], $close ) @@ -502,16 +502,11 @@ private function generateHeader( $header = '' ) { $button = Html::rawElement( 'button', [ - 'type' => 'button', - 'class' => 'close', - 'data-dismiss' => 'modal', - 'aria-label' => wfMessage( 'bootstrap-components-close-element' )->inContentLanguage()->text(), - ], - Html::rawElement( - 'span', - [ 'aria-hidden' => 'true' ], - '×' - ) + 'type' => 'button', + 'class' => 'btn-close', + 'data-bs-dismiss' => 'modal', + 'aria-label' => wfMessage( 'bootstrap-components-close-element' )->inContentLanguage()->text(), + ] ); return Html::rawElement( 'div', From 3d8601ee622930e7fe5b829f1c2b5111fe565d70 Mon Sep 17 00:00:00 2001 From: Morne Alberts Date: Wed, 20 May 2026 16:28:46 +0200 Subject: [PATCH 2/9] Run CI on more versions of MediaWiki and PHP Update the CI matrix to cover the MediaWiki and PHP versions supported by the Bootstrap 5 release. Also disable Scrutinizer Ocular coverage upload (the upstream service is broken). Co-Authored-By: Claude Opus 4.7 --- .github/workflows/ci.yml | 27 ++++++++++----------------- .scrutinizer.yml | 5 +++-- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8fe1ea1..db5e9fe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,34 +13,27 @@ jobs: strategy: matrix: include: - - mw: 'REL1_39' - php: 8.0 - type: coverage - experimental: false - - mw: 'REL1_39' - php: 8.1 - type: normal - experimental: false - - mw: 'REL1_40' - php: 8.1 - type: normal - experimental: false - - mw: 'REL1_41' + - mw: 'REL1_43' php: 8.1 + # TODO: Scrutinzer Ocular overage disabled due to https://github.com/scrutinizer-ci/ocular/issues/51 type: normal - experimental: false - - mw: 'REL1_42' + experimental: true + - mw: 'REL1_44' php: 8.2 type: normal experimental: false - - mw: 'REL1_43' + - mw: 'REL1_45' php: 8.3 type: normal - experimental: true + experimental: false - mw: 'master' php: 8.4 type: normal experimental: true + - mw: 'master' + php: 8.5 + type: normal + experimental: true runs-on: ubuntu-latest continue-on-error: ${{ matrix.experimental }} diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 871b49f..0a9a12a 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -14,8 +14,9 @@ tools: php_loc: true php_analyzer: true sensiolabs_security_checker: true - external_code_coverage: - timeout: '1200' # timeout in seconds + # TODO: Scrutinzer Ocular overage disabled due to https://github.com/scrutinizer-ci/ocular/issues/51 + # external_code_coverage: + # timeout: '1200' # timeout in seconds checks: php: From 840432d34f71ac9321fdef97e5cb496bc932095f Mon Sep 17 00:00:00 2001 From: Morne Alberts Date: Wed, 20 May 2026 16:28:58 +0200 Subject: [PATCH 3/9] Modernise namespaced class imports With the floor at MediaWiki 1.43, the deprecated global-namespace fallbacks (e.g. \Html, \Title, \Parser) can be dropped in favour of the namespaced equivalents (MediaWiki\Html\Html, MediaWiki\Title\Title, MediaWiki\Parser\Parser). Replace deprecated aliases across the extension and tidy up unused / redundant use statements in passing. Co-Authored-By: Claude Opus 4.7 --- src/AbstractComponent.php | 2 +- src/BootstrapComponents.php | 1 - src/CarouselGallery.php | 4 ++-- src/ComponentLibrary.php | 3 +-- src/Components/Accordion.php | 2 +- src/Components/Alert.php | 2 +- src/Components/Badge.php | 2 +- src/Components/Button.php | 4 ++-- src/Components/Card.php | 6 +++--- src/Components/Carousel.php | 2 +- src/Components/Collapse.php | 6 +++--- src/Components/Jumbotron.php | 2 +- src/Components/Modal.php | 2 +- src/Components/Popover.php | 2 +- src/Components/Tooltip.php | 2 +- src/Hooks/OutputPageParserOutput.php | 5 ----- src/Hooks/ParserFirstCallInit.php | 2 +- src/HooksHandler.php | 4 +--- src/ImageModal.php | 4 ++-- src/ImageModalTrigger.php | 11 +++++------ src/LuaLibrary.php | 10 +++++----- src/ModalBuilder.php | 3 +-- src/ParserOutputHelper.php | 9 --------- src/ParserRequest.php | 2 +- tests/PhpUnitEnvironment.php | 2 +- .../phpunit/Integration/I18nJsonFileIntegrityTest.php | 2 +- ...ootstrapComponentsJsonTestCaseScriptRunnerTest.php | 2 +- tests/phpunit/Unit/CarouselGalleryTest.php | 5 ++--- tests/phpunit/Unit/Components/AccordionTest.php | 2 +- tests/phpunit/Unit/Components/AlertTest.php | 2 +- tests/phpunit/Unit/Components/BadgeTest.php | 2 +- tests/phpunit/Unit/Components/ButtonTest.php | 2 +- tests/phpunit/Unit/Components/CardTest.php | 2 +- tests/phpunit/Unit/Components/CarouselTest.php | 2 +- tests/phpunit/Unit/Components/CollapseTest.php | 2 +- tests/phpunit/Unit/Components/JumbotronTest.php | 2 +- tests/phpunit/Unit/Components/ModalTest.php | 2 +- tests/phpunit/Unit/Components/PopoverTest.php | 2 +- tests/phpunit/Unit/Components/TooltipTest.php | 2 +- tests/phpunit/Unit/ComponentsTestBase.php | 2 +- .../phpunit/Unit/Hooks/OutputPageParserOutputTest.php | 4 ++-- tests/phpunit/Unit/Hooks/ParserFirstCallInitTest.php | 4 ++-- tests/phpunit/Unit/ImageModalTest.php | 6 +++--- tests/phpunit/Unit/ImageModalTriggerTest.php | 2 +- tests/phpunit/Unit/ParserOutputHelperTest.php | 7 +++---- tests/phpunit/Unit/ParserRequestTest.php | 4 ++-- 46 files changed, 66 insertions(+), 88 deletions(-) diff --git a/src/AbstractComponent.php b/src/AbstractComponent.php index e3ae555..7700bc2 100644 --- a/src/AbstractComponent.php +++ b/src/AbstractComponent.php @@ -26,7 +26,7 @@ namespace MediaWiki\Extension\BootstrapComponents; -use \MWException; +use MWException; /** * Class AbstractComponent diff --git a/src/BootstrapComponents.php b/src/BootstrapComponents.php index 628c5d8..6595ae2 100644 --- a/src/BootstrapComponents.php +++ b/src/BootstrapComponents.php @@ -43,7 +43,6 @@ namespace MediaWiki\Extension\BootstrapComponents; -use ConfigException; use Exception; use MWException; diff --git a/src/CarouselGallery.php b/src/CarouselGallery.php index dd05855..764b7b6 100644 --- a/src/CarouselGallery.php +++ b/src/CarouselGallery.php @@ -26,10 +26,10 @@ namespace MediaWiki\Extension\BootstrapComponents; -use MediaWiki\Extension\BootstrapComponents\Components\Carousel; use ImageGalleryBase; +use MediaWiki\Extension\BootstrapComponents\Components\Carousel; use MediaWiki\MediaWikiServices; -use Title; +use MediaWiki\Title\Title; /** * Class CarouselGallery diff --git a/src/ComponentLibrary.php b/src/ComponentLibrary.php index 382326c..c496968 100644 --- a/src/ComponentLibrary.php +++ b/src/ComponentLibrary.php @@ -26,8 +26,7 @@ namespace MediaWiki\Extension\BootstrapComponents; -use MediaWiki\MediaWikiServices; -use \MWException; +use MWException; /** * Class ComponentLibrary diff --git a/src/Components/Accordion.php b/src/Components/Accordion.php index 0bb5ce3..8674562 100644 --- a/src/Components/Accordion.php +++ b/src/Components/Accordion.php @@ -27,7 +27,7 @@ namespace MediaWiki\Extension\BootstrapComponents\Components; use MediaWiki\Extension\BootstrapComponents\AbstractComponent; -use \Html; +use MediaWiki\Html\Html; /** * Class Accordion diff --git a/src/Components/Alert.php b/src/Components/Alert.php index 210db46..77dffea 100644 --- a/src/Components/Alert.php +++ b/src/Components/Alert.php @@ -27,7 +27,7 @@ namespace MediaWiki\Extension\BootstrapComponents\Components; use MediaWiki\Extension\BootstrapComponents\AbstractComponent; -use \Html; +use MediaWiki\Html\Html; /** * Class Alert diff --git a/src/Components/Badge.php b/src/Components/Badge.php index 94fdeb1..36643d2 100644 --- a/src/Components/Badge.php +++ b/src/Components/Badge.php @@ -27,7 +27,7 @@ namespace MediaWiki\Extension\BootstrapComponents\Components; use MediaWiki\Extension\BootstrapComponents\AbstractComponent; -use \Html; +use MediaWiki\Html\Html; /** * Class Badge diff --git a/src/Components/Button.php b/src/Components/Button.php index 574ae7d..24d79f4 100644 --- a/src/Components/Button.php +++ b/src/Components/Button.php @@ -27,8 +27,8 @@ namespace MediaWiki\Extension\BootstrapComponents\Components; use MediaWiki\Extension\BootstrapComponents\AbstractComponent; -use \Html; -use \Title; +use MediaWiki\Html\Html; +use MediaWiki\Title\Title; /** * Class Button diff --git a/src/Components/Card.php b/src/Components/Card.php index 786fc2e..5b6d9a3 100644 --- a/src/Components/Card.php +++ b/src/Components/Card.php @@ -26,12 +26,12 @@ namespace MediaWiki\Extension\BootstrapComponents\Components; -use MediaWiki\Extension\BootstrapComponents\ComponentLibrary; use MediaWiki\Extension\BootstrapComponents\AbstractComponent; +use MediaWiki\Extension\BootstrapComponents\ComponentLibrary; use MediaWiki\Extension\BootstrapComponents\NestingController; use MediaWiki\Extension\BootstrapComponents\ParserOutputHelper; -use \Html; -use \MWException; +use MediaWiki\Html\Html; +use MWException; /** * Class Card diff --git a/src/Components/Carousel.php b/src/Components/Carousel.php index 688f159..f11f168 100644 --- a/src/Components/Carousel.php +++ b/src/Components/Carousel.php @@ -28,7 +28,7 @@ use MediaWiki\Extension\BootstrapComponents\AbstractComponent; use MediaWiki\Extension\BootstrapComponents\ParserRequest; -use \Html; +use MediaWiki\Html\Html; /** * Class Carousel diff --git a/src/Components/Collapse.php b/src/Components/Collapse.php index af5fe2a..130b5f8 100644 --- a/src/Components/Collapse.php +++ b/src/Components/Collapse.php @@ -26,11 +26,11 @@ namespace MediaWiki\Extension\BootstrapComponents\Components; -use MediaWiki\Extension\BootstrapComponents\ApplicationFactory; use MediaWiki\Extension\BootstrapComponents\AbstractComponent; +use MediaWiki\Extension\BootstrapComponents\ApplicationFactory; use MediaWiki\Extension\BootstrapComponents\ParserRequest; -use \Html; -use \MWException; +use MediaWiki\Html\Html; +use MWException; /** * Class Collapse diff --git a/src/Components/Jumbotron.php b/src/Components/Jumbotron.php index 5a88de7..eea89a1 100644 --- a/src/Components/Jumbotron.php +++ b/src/Components/Jumbotron.php @@ -27,7 +27,7 @@ namespace MediaWiki\Extension\BootstrapComponents\Components; use MediaWiki\Extension\BootstrapComponents\AbstractComponent; -use \Html; +use MediaWiki\Html\Html; /** * Class Jumbotron diff --git a/src/Components/Modal.php b/src/Components/Modal.php index a9a51ce..7870215 100644 --- a/src/Components/Modal.php +++ b/src/Components/Modal.php @@ -29,7 +29,7 @@ use MediaWiki\Extension\BootstrapComponents\AbstractComponent; use MediaWiki\Extension\BootstrapComponents\ApplicationFactory; use MediaWiki\Extension\BootstrapComponents\ModalBuilder; -use \Html; +use MediaWiki\Html\Html; /** * Class Modal diff --git a/src/Components/Popover.php b/src/Components/Popover.php index e9fa8d9..f222ab9 100644 --- a/src/Components/Popover.php +++ b/src/Components/Popover.php @@ -27,7 +27,7 @@ namespace MediaWiki\Extension\BootstrapComponents\Components; use MediaWiki\Extension\BootstrapComponents\AbstractComponent; -use \Html; +use MediaWiki\Html\Html; /** * Class Popover diff --git a/src/Components/Tooltip.php b/src/Components/Tooltip.php index 7626a80..ebe5d9b 100644 --- a/src/Components/Tooltip.php +++ b/src/Components/Tooltip.php @@ -27,7 +27,7 @@ namespace MediaWiki\Extension\BootstrapComponents\Components; use MediaWiki\Extension\BootstrapComponents\AbstractComponent; -use \Html; +use MediaWiki\Html\Html; /** * Class Tooltip diff --git a/src/Hooks/OutputPageParserOutput.php b/src/Hooks/OutputPageParserOutput.php index 64dae49..fd72a76 100644 --- a/src/Hooks/OutputPageParserOutput.php +++ b/src/Hooks/OutputPageParserOutput.php @@ -28,13 +28,8 @@ use MediaWiki\Extension\BootstrapComponents\BootstrapComponents; use MediaWiki\Extension\BootstrapComponents\BootstrapComponentsService; -/* - * TODO switch to these, wehen we drop support for mw < 1.40 use MediaWiki\Output\OutputPage; use MediaWiki\Parser\ParserOutput; - */ -use \OutputPage; -use \ParserOutput; /** * Class OutputPageParserOutput diff --git a/src/Hooks/ParserFirstCallInit.php b/src/Hooks/ParserFirstCallInit.php index 0adbe14..888ce71 100644 --- a/src/Hooks/ParserFirstCallInit.php +++ b/src/Hooks/ParserFirstCallInit.php @@ -31,7 +31,7 @@ use MediaWiki\Extension\BootstrapComponents\ComponentLibrary; use MediaWiki\Extension\BootstrapComponents\NestingController; use MediaWiki\Extension\BootstrapComponents\ParserOutputHelper; -use Parser; +use MediaWiki\Parser\Parser; use ReflectionClass; /** diff --git a/src/HooksHandler.php b/src/HooksHandler.php index fa01e95..47ac878 100644 --- a/src/HooksHandler.php +++ b/src/HooksHandler.php @@ -14,9 +14,7 @@ use MediaWiki\Hook\ParserFirstCallInitHook; use MediaWiki\Hook\SetupAfterCacheHook; use MediaWiki\MediaWikiServices; -use Parser; -// TODO switch to then when dropping support for mw < 1.40 -// use MediaWiki\Parser\Parser; +use MediaWiki\Parser\Parser; use SMW\Utils\File; use StripState; diff --git a/src/ImageModal.php b/src/ImageModal.php index 455f0cb..8b78d72 100644 --- a/src/ImageModal.php +++ b/src/ImageModal.php @@ -27,11 +27,11 @@ namespace MediaWiki\Extension\BootstrapComponents; use File; -use Html; use MediaTransformOutput; +use MediaWiki\Html\Html; use MediaWiki\MediaWikiServices; +use MediaWiki\Title\Title; use MWException; -use Title; /** * Class ImageModal diff --git a/src/ImageModalTrigger.php b/src/ImageModalTrigger.php index 0555c8b..91ef141 100644 --- a/src/ImageModalTrigger.php +++ b/src/ImageModalTrigger.php @@ -29,12 +29,11 @@ use Config; use ConfigException; use Exception; -use \Linker; -use \Html; -use \MediaWiki\MediaWikiServices; -use MediaWiki\User\UserOptionsLookup; -use \RequestContext; -use \Title; +use MediaWiki\Html\Html; +use MediaWiki\Linker\Linker; +use MediaWiki\MediaWikiServices; +use MediaWiki\Title\Title; +use RequestContext; /** * Class ImageModal diff --git a/src/LuaLibrary.php b/src/LuaLibrary.php index 0a0266a..73a4d0d 100644 --- a/src/LuaLibrary.php +++ b/src/LuaLibrary.php @@ -26,12 +26,12 @@ namespace MediaWiki\Extension\BootstrapComponents; +use MediaWiki\Extension\Scribunto\Engines\LuaCommon\LibraryBase; +use MediaWiki\Extension\Scribunto\Engines\LuaCommon\LuaEngine; use MediaWiki\MediaWikiServices; use MWException; use ReflectionClass; use ReflectionException; -use Scribunto_LuaEngine; -use Scribunto_LuaLibraryBase; /** * Class LuaLibrary @@ -40,7 +40,7 @@ * * @since 1.1 */ -class LuaLibrary extends Scribunto_LuaLibraryBase { +class LuaLibrary extends LibraryBase { /** * @var ApplicationFactory $applicationFactory; @@ -55,9 +55,9 @@ class LuaLibrary extends Scribunto_LuaLibraryBase { /** * LuaLibrary constructor. * - * @param Scribunto_LuaEngine $engine + * @param LuaEngine $engine */ - public function __construct( Scribunto_LuaEngine $engine ) { + public function __construct( LuaEngine $engine ) { parent::__construct( $engine ); $this->applicationFactory = ApplicationFactory::getInstance(); $this->bootstrapComponentService = MediaWikiServices::getInstance()->getService( 'BootstrapComponentsService' ); diff --git a/src/ModalBuilder.php b/src/ModalBuilder.php index c9080e5..9226367 100644 --- a/src/ModalBuilder.php +++ b/src/ModalBuilder.php @@ -26,8 +26,7 @@ namespace MediaWiki\Extension\BootstrapComponents; -use \Html; -use MediaWiki\MediaWikiServices; +use MediaWiki\Html\Html; /** * Class ModalBase diff --git a/src/ParserOutputHelper.php b/src/ParserOutputHelper.php index 9877ff0..0c1962e 100644 --- a/src/ParserOutputHelper.php +++ b/src/ParserOutputHelper.php @@ -26,20 +26,11 @@ namespace MediaWiki\Extension\BootstrapComponents; -/* - * TODO: When dropping support for MW1.39, use these class imports: use MediaWiki\Html\Html; use MediaWiki\Message\Message; use MediaWiki\Parser\Parser; use MediaWiki\Parser\ParserOutput; use MediaWiki\Title\Title; -*/ - -use Html; -use Message; -use Parser; -use ParserOutput; -use Title; /** diff --git a/src/ParserRequest.php b/src/ParserRequest.php index 20dfa3f..691f9ae 100644 --- a/src/ParserRequest.php +++ b/src/ParserRequest.php @@ -26,8 +26,8 @@ namespace MediaWiki\Extension\BootstrapComponents; +use MediaWiki\Parser\Parser; use MWException; -use Parser; use PPFrame; /** diff --git a/tests/PhpUnitEnvironment.php b/tests/PhpUnitEnvironment.php index f4dbc06..e98be58 100644 --- a/tests/PhpUnitEnvironment.php +++ b/tests/PhpUnitEnvironment.php @@ -2,7 +2,7 @@ namespace MediaWiki\Extension\BootstrapComponents\Tests; -use \GitInfo; +use GitInfo; /** * @private diff --git a/tests/phpunit/Integration/I18nJsonFileIntegrityTest.php b/tests/phpunit/Integration/I18nJsonFileIntegrityTest.php index 474dbb6..af5e1b3 100644 --- a/tests/phpunit/Integration/I18nJsonFileIntegrityTest.php +++ b/tests/phpunit/Integration/I18nJsonFileIntegrityTest.php @@ -2,7 +2,7 @@ namespace MediaWiki\Extension\BootstrapComponents\Tests\Integration; -use \MediaWiki\MediaWikiServices; +use MediaWiki\MediaWikiServices; use SMW\Tests\PHPUnitCompat; use SMW\Tests\Utils\UtilityFactory; diff --git a/tests/phpunit/Integration/JSONScript/BootstrapComponentsJsonTestCaseScriptRunnerTest.php b/tests/phpunit/Integration/JSONScript/BootstrapComponentsJsonTestCaseScriptRunnerTest.php index 574ccd2..e1e44d5 100644 --- a/tests/phpunit/Integration/JSONScript/BootstrapComponentsJsonTestCaseScriptRunnerTest.php +++ b/tests/phpunit/Integration/JSONScript/BootstrapComponentsJsonTestCaseScriptRunnerTest.php @@ -3,8 +3,8 @@ namespace MediaWiki\Extension\BootstrapComponents\Tests\Integration; use MediaWiki\Extension\BootstrapComponents\ApplicationFactory; -use MediaWiki\Extension\BootstrapComponents\Hooks\OutputPageParserOutput; use MediaWiki\Extension\BootstrapComponents\HookRegistry; +use MediaWiki\Extension\BootstrapComponents\Hooks\OutputPageParserOutput; use SMW\DIWikiPage; use SMW\Tests\JSONScriptTestCaseRunner; use SMW\Tests\Utils\JSONScript\JsonTestCaseFileHandler; diff --git a/tests/phpunit/Unit/CarouselGalleryTest.php b/tests/phpunit/Unit/CarouselGalleryTest.php index be566f6..04b16d9 100644 --- a/tests/phpunit/Unit/CarouselGalleryTest.php +++ b/tests/phpunit/Unit/CarouselGalleryTest.php @@ -3,10 +3,9 @@ namespace MediaWiki\Extension\BootstrapComponents\Tests\Unit; use MediaWiki\Extension\BootstrapComponents\CarouselGallery; -use MediaWiki\Extension\BootstrapComponents\ParserRequest; -use \MWException; +use MediaWiki\Title\Title; +use MWException; use PHPUnit\Framework\TestCase; -use \Title; /** * @covers \MediaWiki\Extension\BootstrapComponents\CarouselGallery diff --git a/tests/phpunit/Unit/Components/AccordionTest.php b/tests/phpunit/Unit/Components/AccordionTest.php index 657cc11..9e7977e 100644 --- a/tests/phpunit/Unit/Components/AccordionTest.php +++ b/tests/phpunit/Unit/Components/AccordionTest.php @@ -4,7 +4,7 @@ use MediaWiki\Extension\BootstrapComponents\Components\Accordion as Accordion; use MediaWiki\Extension\BootstrapComponents\Tests\Unit\ComponentsTestBase; -use \MWException; +use MWException; /** * @covers \MediaWiki\Extension\BootstrapComponents\Components\Accordion diff --git a/tests/phpunit/Unit/Components/AlertTest.php b/tests/phpunit/Unit/Components/AlertTest.php index 55d7de8..192119d 100644 --- a/tests/phpunit/Unit/Components/AlertTest.php +++ b/tests/phpunit/Unit/Components/AlertTest.php @@ -4,7 +4,7 @@ use MediaWiki\Extension\BootstrapComponents\Components\Alert; use MediaWiki\Extension\BootstrapComponents\Tests\Unit\ComponentsTestBase; -use \MWException; +use MWException; /** * @covers \MediaWiki\Extension\BootstrapComponents\Components\Alert diff --git a/tests/phpunit/Unit/Components/BadgeTest.php b/tests/phpunit/Unit/Components/BadgeTest.php index 1550ad4..c4bde3b 100644 --- a/tests/phpunit/Unit/Components/BadgeTest.php +++ b/tests/phpunit/Unit/Components/BadgeTest.php @@ -4,7 +4,7 @@ use MediaWiki\Extension\BootstrapComponents\Components\Badge; use MediaWiki\Extension\BootstrapComponents\Tests\Unit\ComponentsTestBase; -use \MWException; +use MWException; /** * @covers \MediaWiki\Extension\BootstrapComponents\Components\Badge diff --git a/tests/phpunit/Unit/Components/ButtonTest.php b/tests/phpunit/Unit/Components/ButtonTest.php index da6e560..066545c 100644 --- a/tests/phpunit/Unit/Components/ButtonTest.php +++ b/tests/phpunit/Unit/Components/ButtonTest.php @@ -4,7 +4,7 @@ use MediaWiki\Extension\BootstrapComponents\Components\Button; use MediaWiki\Extension\BootstrapComponents\Tests\Unit\ComponentsTestBase; -use \MWException; +use MWException; /** * @covers \MediaWiki\Extension\BootstrapComponents\Components\Button diff --git a/tests/phpunit/Unit/Components/CardTest.php b/tests/phpunit/Unit/Components/CardTest.php index f7d4563..e517929 100644 --- a/tests/phpunit/Unit/Components/CardTest.php +++ b/tests/phpunit/Unit/Components/CardTest.php @@ -6,7 +6,7 @@ use MediaWiki\Extension\BootstrapComponents\Components\Card; use MediaWiki\Extension\BootstrapComponents\NestingController; use MediaWiki\Extension\BootstrapComponents\Tests\Unit\ComponentsTestBase; -use \MWException; +use MWException; /** * @covers \MediaWiki\Extension\BootstrapComponents\Components\Card diff --git a/tests/phpunit/Unit/Components/CarouselTest.php b/tests/phpunit/Unit/Components/CarouselTest.php index 5be1384..87b4850 100644 --- a/tests/phpunit/Unit/Components/CarouselTest.php +++ b/tests/phpunit/Unit/Components/CarouselTest.php @@ -4,7 +4,7 @@ use MediaWiki\Extension\BootstrapComponents\Components\Carousel; use MediaWiki\Extension\BootstrapComponents\Tests\Unit\ComponentsTestBase; -use \MWException; +use MWException; /** * @covers \MediaWiki\Extension\BootstrapComponents\Components\Carousel diff --git a/tests/phpunit/Unit/Components/CollapseTest.php b/tests/phpunit/Unit/Components/CollapseTest.php index 04c4ba9..39c631c 100644 --- a/tests/phpunit/Unit/Components/CollapseTest.php +++ b/tests/phpunit/Unit/Components/CollapseTest.php @@ -4,7 +4,7 @@ use MediaWiki\Extension\BootstrapComponents\Components\Collapse; use MediaWiki\Extension\BootstrapComponents\Tests\Unit\ComponentsTestBase; -use \MWException; +use MWException; /** * @covers \MediaWiki\Extension\BootstrapComponents\Components\Collapse diff --git a/tests/phpunit/Unit/Components/JumbotronTest.php b/tests/phpunit/Unit/Components/JumbotronTest.php index 024457b..323eef9 100644 --- a/tests/phpunit/Unit/Components/JumbotronTest.php +++ b/tests/phpunit/Unit/Components/JumbotronTest.php @@ -4,7 +4,7 @@ use MediaWiki\Extension\BootstrapComponents\Components\Jumbotron; use MediaWiki\Extension\BootstrapComponents\Tests\Unit\ComponentsTestBase; -use \MWException; +use MWException; /** * @covers \MediaWiki\Extension\BootstrapComponents\Components\Jumbotron diff --git a/tests/phpunit/Unit/Components/ModalTest.php b/tests/phpunit/Unit/Components/ModalTest.php index f0d6991..0c1dd0e 100644 --- a/tests/phpunit/Unit/Components/ModalTest.php +++ b/tests/phpunit/Unit/Components/ModalTest.php @@ -4,7 +4,7 @@ use MediaWiki\Extension\BootstrapComponents\Components\Modal; use MediaWiki\Extension\BootstrapComponents\Tests\Unit\ComponentsTestBase; -use \MWException; +use MWException; /** * @covers \MediaWiki\Extension\BootstrapComponents\Components\Modal diff --git a/tests/phpunit/Unit/Components/PopoverTest.php b/tests/phpunit/Unit/Components/PopoverTest.php index ef44556..4f3b39b 100644 --- a/tests/phpunit/Unit/Components/PopoverTest.php +++ b/tests/phpunit/Unit/Components/PopoverTest.php @@ -4,7 +4,7 @@ use MediaWiki\Extension\BootstrapComponents\Components\Popover; use MediaWiki\Extension\BootstrapComponents\Tests\Unit\ComponentsTestBase; -use \MWException; +use MWException; /** * @covers \MediaWiki\Extension\BootstrapComponents\Components\Popover diff --git a/tests/phpunit/Unit/Components/TooltipTest.php b/tests/phpunit/Unit/Components/TooltipTest.php index 8fee9eb..002ce55 100644 --- a/tests/phpunit/Unit/Components/TooltipTest.php +++ b/tests/phpunit/Unit/Components/TooltipTest.php @@ -5,7 +5,7 @@ use MediaWiki\Extension\BootstrapComponents\Components\Tooltip; use MediaWiki\Extension\BootstrapComponents\NestingController; use MediaWiki\Extension\BootstrapComponents\Tests\Unit\ComponentsTestBase; -use \MWException; +use MWException; /** * @covers \MediaWiki\Extension\BootstrapComponents\Components\Tooltip diff --git a/tests/phpunit/Unit/ComponentsTestBase.php b/tests/phpunit/Unit/ComponentsTestBase.php index c7ef2ab..fe87f60 100644 --- a/tests/phpunit/Unit/ComponentsTestBase.php +++ b/tests/phpunit/Unit/ComponentsTestBase.php @@ -6,8 +6,8 @@ use MediaWiki\Extension\BootstrapComponents\NestingController; use MediaWiki\Extension\BootstrapComponents\ParserOutputHelper; use MediaWiki\Extension\BootstrapComponents\ParserRequest; +use MediaWiki\Parser\Parser; use PHPUnit\Framework\TestCase; -use Parser; use PPFrame; /** diff --git a/tests/phpunit/Unit/Hooks/OutputPageParserOutputTest.php b/tests/phpunit/Unit/Hooks/OutputPageParserOutputTest.php index e7d97fb..0ece4ac 100644 --- a/tests/phpunit/Unit/Hooks/OutputPageParserOutputTest.php +++ b/tests/phpunit/Unit/Hooks/OutputPageParserOutputTest.php @@ -4,8 +4,8 @@ use MediaWiki\Extension\BootstrapComponents\BootstrapComponentsService; use MediaWiki\Extension\BootstrapComponents\Hooks\OutputPageParserOutput; -use OutputPage; -use ParserOutput; +use MediaWiki\Output\OutputPage; +use MediaWiki\Parser\ParserOutput; use PHPUnit\Framework\TestCase; /** diff --git a/tests/phpunit/Unit/Hooks/ParserFirstCallInitTest.php b/tests/phpunit/Unit/Hooks/ParserFirstCallInitTest.php index e1b32ea..90d1fbf 100644 --- a/tests/phpunit/Unit/Hooks/ParserFirstCallInitTest.php +++ b/tests/phpunit/Unit/Hooks/ParserFirstCallInitTest.php @@ -2,9 +2,9 @@ namespace MediaWiki\Extension\BootstrapComponents\Tests\Unit\Hooks; -use MediaWiki\Extension\BootstrapComponents\Hooks\ParserFirstCallInit as ParserFirstCallInit; use MediaWiki\Extension\BootstrapComponents\ComponentLibrary; -use \Parser; +use MediaWiki\Extension\BootstrapComponents\Hooks\ParserFirstCallInit as ParserFirstCallInit; +use MediaWiki\Parser\Parser; use PHPUnit\Framework\TestCase; /** diff --git a/tests/phpunit/Unit/ImageModalTest.php b/tests/phpunit/Unit/ImageModalTest.php index a4ef194..8c1de6d 100644 --- a/tests/phpunit/Unit/ImageModalTest.php +++ b/tests/phpunit/Unit/ImageModalTest.php @@ -2,17 +2,17 @@ namespace MediaWiki\Extension\BootstrapComponents\Tests\Unit; +use ConfigException; use File; use LocalFile; use MediaWiki\Extension\BootstrapComponents\BootstrapComponentsService; use MediaWiki\Extension\BootstrapComponents\ImageModal; -use \ConfigException; use MediaWiki\Extension\BootstrapComponents\NestingController; use MediaWiki\Extension\BootstrapComponents\ParserOutputHelper; -use \MediaWiki\MediaWikiServices; +use MediaWiki\MediaWikiServices; +use MediaWiki\Title\Title; use PHPUnit\Framework\TestCase; use ThumbnailImage; -use Title; /** * @covers \MediaWiki\Extension\BootstrapComponents\ImageModal diff --git a/tests/phpunit/Unit/ImageModalTriggerTest.php b/tests/phpunit/Unit/ImageModalTriggerTest.php index f80e549..c151541 100644 --- a/tests/phpunit/Unit/ImageModalTriggerTest.php +++ b/tests/phpunit/Unit/ImageModalTriggerTest.php @@ -3,7 +3,7 @@ namespace MediaWiki\Extension\BootstrapComponents\Tests\Unit; use MediaWiki\Extension\BootstrapComponents\ImageModalTrigger; -use \MediaWiki\MediaWikiServices; +use MediaWiki\MediaWikiServices; use PHPUnit\Framework\TestCase; /** diff --git a/tests/phpunit/Unit/ParserOutputHelperTest.php b/tests/phpunit/Unit/ParserOutputHelperTest.php index 5fda1a2..a5c4b9c 100644 --- a/tests/phpunit/Unit/ParserOutputHelperTest.php +++ b/tests/phpunit/Unit/ParserOutputHelperTest.php @@ -4,10 +4,9 @@ use MediaWiki\Extension\BootstrapComponents\ComponentLibrary; use MediaWiki\Extension\BootstrapComponents\ParserOutputHelper; -use \MWException; -// TODO: when dropping 1.39, switch to MediaWiki\Parser\Parser and MediaWiki\Parser\ParserOutput -use Parser; -use ParserOutput; +use MediaWiki\Parser\Parser; +use MediaWiki\Parser\ParserOutput; +use MWException; use PHPUnit\Framework\TestCase; /** diff --git a/tests/phpunit/Unit/ParserRequestTest.php b/tests/phpunit/Unit/ParserRequestTest.php index a448625..28bfb50 100644 --- a/tests/phpunit/Unit/ParserRequestTest.php +++ b/tests/phpunit/Unit/ParserRequestTest.php @@ -3,9 +3,9 @@ namespace MediaWiki\Extension\BootstrapComponents\Tests\Unit; use MediaWiki\Extension\BootstrapComponents\ParserRequest; +use MediaWiki\Parser\Parser; use PHPUnit\Framework\TestCase; -use \Parser; -use \PPFrame; +use PPFrame; /** * @covers \MediaWiki\Extension\BootstrapComponents\ParserRequest From f757c7da8b77f08ea471612e662acf90d2340c73 Mon Sep 17 00:00:00 2001 From: Morne Alberts Date: Wed, 20 May 2026 16:29:06 +0200 Subject: [PATCH 4/9] Update tests for Bootstrap 5 output PHPUnit expectations across the Components, ImageModal and ModalBuilder tests updated to match the BS5 emission switched on by the migration commit (data-bs-* attributes, .btn-close close buttons, .rounded-pill badges, btn-secondary default-color, jumbotron utility classes). Co-Authored-By: Claude Opus 4.7 --- tests/phpunit/Unit/CarouselGalleryTest.php | 18 +++---- tests/phpunit/Unit/Components/AlertTest.php | 6 +-- tests/phpunit/Unit/Components/BadgeTest.php | 2 +- tests/phpunit/Unit/Components/ButtonTest.php | 6 +-- tests/phpunit/Unit/Components/CardTest.php | 10 ++-- .../phpunit/Unit/Components/CarouselTest.php | 18 +++---- .../phpunit/Unit/Components/CollapseTest.php | 10 ++-- .../phpunit/Unit/Components/JumbotronTest.php | 6 +-- tests/phpunit/Unit/Components/ModalTest.php | 18 +++---- tests/phpunit/Unit/Components/PopoverTest.php | 4 +- tests/phpunit/Unit/Components/TooltipTest.php | 4 +- tests/phpunit/Unit/ImageModalTest.php | 48 +++++++++---------- tests/phpunit/Unit/ImageModalTriggerTest.php | 20 ++++---- tests/phpunit/Unit/LuaLibraryTestBase.php | 4 +- tests/phpunit/Unit/ModalBuilderTest.php | 10 ++-- .../phpunit/Unit/mw.bootstrap.parse.tests.lua | 2 +- 16 files changed, 93 insertions(+), 93 deletions(-) diff --git a/tests/phpunit/Unit/CarouselGalleryTest.php b/tests/phpunit/Unit/CarouselGalleryTest.php index 04b16d9..70cd108 100644 --- a/tests/phpunit/Unit/CarouselGalleryTest.php +++ b/tests/phpunit/Unit/CarouselGalleryTest.php @@ -82,17 +82,17 @@ public function galleryDataProvider() { 'fade' => '', ], [ - 0 => '
', 'isHTML' => true, 'noparse' => true, ], @@ -104,15 +104,15 @@ public function galleryDataProvider() { ], [], [ - 0 => '', 'isHTML' => true, 'noparse' => true, ], diff --git a/tests/phpunit/Unit/Components/AlertTest.php b/tests/phpunit/Unit/Components/AlertTest.php index 192119d..4e0abfa 100644 --- a/tests/phpunit/Unit/Components/AlertTest.php +++ b/tests/phpunit/Unit/Components/AlertTest.php @@ -91,17 +91,17 @@ public function placeMeArgumentsProvider() { 'dismiss_arbitrary' => [ $this->input, [ 'dismissible' => 'bla' ], - '', + '', ], 'dismiss' => [ $this->input, [ 'dismissible' => true ], - '', + '', ], 'fading' => [ $this->input, [ 'dismissible' => 'fade', 'color' => 'warning' ], - '', + '', ], 'manual id, no dismiss' => [ $this->input, diff --git a/tests/phpunit/Unit/Components/BadgeTest.php b/tests/phpunit/Unit/Components/BadgeTest.php index c4bde3b..3603f1b 100644 --- a/tests/phpunit/Unit/Components/BadgeTest.php +++ b/tests/phpunit/Unit/Components/BadgeTest.php @@ -89,7 +89,7 @@ public function placeMeArgumentsProvider() { 'pill' => [ $this->input, [ 'pill' => 'true' ], - '' . $this->input . '', + '' . $this->input . '', ], 'no pill' => [ $this->input, diff --git a/tests/phpunit/Unit/Components/ButtonTest.php b/tests/phpunit/Unit/Components/ButtonTest.php index 066545c..12234e1 100644 --- a/tests/phpunit/Unit/Components/ButtonTest.php +++ b/tests/phpunit/Unit/Components/ButtonTest.php @@ -85,7 +85,7 @@ public function testCanInjectRawAttributes() { ); $instance->injectRawAttributes( - [ 'data-toggle' => 'foo', 'data-target' => '#bar' ] + [ 'data-bs-toggle' => 'foo', 'data-bs-target' => '#bar' ] ); $generatedOutput = $instance->parseComponent( $parserRequest ); @@ -98,14 +98,14 @@ public function testCanInjectRawAttributes() { $this->assertRegExp( '~^' . $this->input . '$~', + . '" data-bs-toggle="foo" data-bs-target="#bar">' . $this->input . '$~', $generatedOutput ); } else { $this->assertMatchesRegularExpression( '~^' . $this->input . '$~', + . '" data-bs-toggle="foo" data-bs-target="#bar">' . $this->input . '$~', $generatedOutput ); } diff --git a/tests/phpunit/Unit/Components/CardTest.php b/tests/phpunit/Unit/Components/CardTest.php index e517929..994d21a 100644 --- a/tests/phpunit/Unit/Components/CardTest.php +++ b/tests/phpunit/Unit/Components/CardTest.php @@ -148,7 +148,7 @@ public function placeMeArgumentsProvider() { 'footer-style'=> 'padding:5px', ], '
' - . '
' + . '
' . '

HEADING TEXT

[[File:Serenity.png]]' . '
' . $this->input . '
[[File:Serenity.png|class=card-img-bottom]]
', @@ -179,12 +179,12 @@ public function placeMeInsideAccordionArgumentsProvider() { 'simple' => [ $this->input, [], - '
' . $this->input . '
', + '
' . $this->input . '
', ], 'text missing' => [ '', [ 'header' => 'watch this', 'footer' => 'watch what?', 'collapsible' => 'false', ], - '
', + '
', ], 'all attributes' => [ $this->input, @@ -198,12 +198,12 @@ public function placeMeInsideAccordionArgumentsProvider() { 'heading' => 'HEADING TEXT', 'footer' => 'FOOTER TEXT', ], - '

HEADING TEXT

' . $this->input . '
', + '

HEADING TEXT

' . $this->input . '
', ], 'collapsible false' => [ $this->input, [ 'collapsible' => 'false', ], - '
' . $this->input . '
', + '
' . $this->input . '
', ], ]; } diff --git a/tests/phpunit/Unit/Components/CarouselTest.php b/tests/phpunit/Unit/Components/CarouselTest.php index 87b4850..1c10d12 100644 --- a/tests/phpunit/Unit/Components/CarouselTest.php +++ b/tests/phpunit/Unit/Components/CarouselTest.php @@ -79,17 +79,17 @@ public function placeMeArgumentsProvider() { 'simple' => [ '[[File:Mal.jpg|Malcolm Reynolds_0]]', [ '[[File:Mal.jpg|Malcolm Reynolds]]' => true, '[[File:Wash.jpg|link' => '|Hoban Washburne]]' ], - '
', ], 'images missing' => [ $this->input, @@ -105,15 +105,15 @@ public function placeMeArgumentsProvider() { 'fade' => true, 'style' => 'float:none;background-color:black', ], - '', ], ]; } diff --git a/tests/phpunit/Unit/Components/CollapseTest.php b/tests/phpunit/Unit/Components/CollapseTest.php index 39c631c..557a0ea 100644 --- a/tests/phpunit/Unit/Components/CollapseTest.php +++ b/tests/phpunit/Unit/Components/CollapseTest.php @@ -69,27 +69,27 @@ public function placeMeArgumentsProvider() { 'simple' => [ $this->input, [], - '#bsc_collapse_NULL
' . $this->input . '
', + '#bsc_collapse_NULL
' . $this->input . '
', ], 'color_unknown' => [ $this->input, [ 'color' => 'unknown' ], - '#bsc_collapse_NULL
' . $this->input . '
', + '#bsc_collapse_NULL
' . $this->input . '
', ], 'button text' => [ $this->input, [ 'text' => 'BUTTON' ], - 'BUTTON
' . $this->input . '
', + 'BUTTON
' . $this->input . '
', ], 'manual id' => [ $this->input, [ 'color' => 'success', 'id' => 'alliance' ], - '#alliance
' . $this->input . '
', + '#alliance
' . $this->input . '
', ], 'style and class' => [ $this->input, [ 'class' => 'dummy nice', 'style' => 'float:right;background-color:green' ], - '#bsc_collapse_NULL
' . $this->input . '
', + '#bsc_collapse_NULL
' . $this->input . '
', ], ]; } diff --git a/tests/phpunit/Unit/Components/JumbotronTest.php b/tests/phpunit/Unit/Components/JumbotronTest.php index 323eef9..7d15ebf 100644 --- a/tests/phpunit/Unit/Components/JumbotronTest.php +++ b/tests/phpunit/Unit/Components/JumbotronTest.php @@ -69,17 +69,17 @@ public function placeMeArgumentsProvider() { 'simple' => [ $this->input, [], - '
' . $this->input . '
', + '
' . $this->input . '
', ], 'manual id' => [ $this->input, [ 'id' => 'hms_dortmunder' ], - '
' . $this->input . '
', + '
' . $this->input . '
', ], 'style and class' => [ $this->input, [ 'class' => 'dummy nice', 'style' => 'float:right;background-color:green' ], - '
' . $this->input . '
', + '
' . $this->input . '
', ], ]; } diff --git a/tests/phpunit/Unit/Components/ModalTest.php b/tests/phpunit/Unit/Components/ModalTest.php index 0c1dd0e..d6f9be7 100644 --- a/tests/phpunit/Unit/Components/ModalTest.php +++ b/tests/phpunit/Unit/Components/ModalTest.php @@ -89,9 +89,9 @@ public function placeMeArgumentsProvider() { 'simple' => [ $this->input, [ 'text' => 'BUTTON' ], - '', - '' . "\n", + '', + '' . "\n", ], 'text missing' => [ $this->input, @@ -105,9 +105,9 @@ public function placeMeArgumentsProvider() { 'text' => 'beforeSerenityafter', 'size' => 'none', ], - 'beforeSerenityafter', - '' . "\n", + 'beforeSerenityafter', + '' . "\n", ], 'all attributes' => [ $this->input, @@ -116,9 +116,9 @@ public function placeMeArgumentsProvider() { 'id' => 'firefly0', 'size' => 'lg', 'class' => 'shiny', 'style' => 'float:right;background-color:black', 'heading' => 'You can\'t take the sky from me!', ], - 'Serenity', - '' . "\n", + 'Serenity', + '' . "\n", ], ]; } diff --git a/tests/phpunit/Unit/Components/PopoverTest.php b/tests/phpunit/Unit/Components/PopoverTest.php index 4f3b39b..3714c8a 100644 --- a/tests/phpunit/Unit/Components/PopoverTest.php +++ b/tests/phpunit/Unit/Components/PopoverTest.php @@ -69,7 +69,7 @@ public function placeMeArgumentsProvider() { 'simple' => [ $this->input, [ 'heading' => 'heading', 'text' => 'BUTTON' ], - '', + '', ], 'heading empty' => [ $this->input, @@ -87,7 +87,7 @@ public function placeMeArgumentsProvider() { 'heading' => 'heading', 'text' => 'BUTTON', 'class' => 'dummy nice', 'style' => 'float:right;background-color:green', 'placement' => 'right', 'trigger' => 'hover', 'id' => 'cudgel', 'size' => 'sm' ], - '', + '', ], ]; } diff --git a/tests/phpunit/Unit/Components/TooltipTest.php b/tests/phpunit/Unit/Components/TooltipTest.php index 002ce55..5d76d00 100644 --- a/tests/phpunit/Unit/Components/TooltipTest.php +++ b/tests/phpunit/Unit/Components/TooltipTest.php @@ -74,7 +74,7 @@ public function placeMeArgumentsProvider() { 'simple' => [ $this->input, [ 'text' => 'simple' ], - '' . $this->input . '', + '' . $this->input . '', ], 'empty' => [ '', @@ -89,7 +89,7 @@ public function placeMeArgumentsProvider() { 'id, style and class' => [ $this->input, [ 'text' => 'simple', 'class' => 'dummy nice', 'style' => 'float:right;background-color:#80266e', 'id' => 'vera' ], - '' . $this->input . '', + '' . $this->input . '', ], ]; } diff --git a/tests/phpunit/Unit/ImageModalTest.php b/tests/phpunit/Unit/ImageModalTest.php index 8c1de6d..69306f5 100644 --- a/tests/phpunit/Unit/ImageModalTest.php +++ b/tests/phpunit/Unit/ImageModalTest.php @@ -309,9 +309,9 @@ public function canParseDataProvider(): array 'no params' => [ [], [], - '~~', - '' . "\n", + '~~', + '' . "\n", ], 'frame params w/o thumbnail' => [ [ @@ -323,9 +323,9 @@ public function canParseDataProvider(): array 'valign' => 'text-top', ], [], - '~
test_alt
~', - '' . "\n", + '~
test_alt
~', + '' . "\n", ], 'manual width, frameless' => [ [ @@ -336,9 +336,9 @@ public function canParseDataProvider(): array 'width' => 200, 'page' => 7, ], - '~
~', - '' . "\n", + '~
~', + '' . "\n", ], 'thumbnail, manual width' => [ [ @@ -349,9 +349,9 @@ public function canParseDataProvider(): array 'width' => 200, 'page' => 7, ], - '~
~', - '' . "\n", + '~
~', + '' . "\n", ], 'manual thumbnail, NOT centered' => [ [ @@ -360,9 +360,9 @@ public function canParseDataProvider(): array 'framed' => false, ], [], - '~
()?()?
~', - '' . "\n", + '~
~', + '' . "\n", ], 'framed' => [ [ @@ -370,9 +370,9 @@ public function canParseDataProvider(): array 'framed' => false, ], [], - '~
~', - '' . "\n", + '~
~', + '' . "\n", ], 'centered' => [ [ @@ -381,9 +381,9 @@ public function canParseDataProvider(): array [ 'width' => 200, ], - '~
~', - '' . "\n", + '~
~', + '' . "\n", ], 'manual thumbnail, upright' => [ [ @@ -392,9 +392,9 @@ public function canParseDataProvider(): array 'manualthumb' => 'Shuttle.png', ], [], - '~
()?()?
~', - '' . "\n", + '~
~', + '' . "\n", ], ]; } diff --git a/tests/phpunit/Unit/ImageModalTriggerTest.php b/tests/phpunit/Unit/ImageModalTriggerTest.php index c151541..d64d37b 100644 --- a/tests/phpunit/Unit/ImageModalTriggerTest.php +++ b/tests/phpunit/Unit/ImageModalTriggerTest.php @@ -172,7 +172,7 @@ public function canParseProvider() { 'page' => false, ], [ - '~~', + '~~', ] ], 'frame params w/o thumbnail' => [ @@ -192,7 +192,7 @@ public function canParseProvider() { 'page' => false, ], [ - '~
~', + '~
~', '~test_alt~', ] ], @@ -214,7 +214,7 @@ public function canParseProvider() { 'page' => 7, ], [ - '~
~', + '~
~', '~~', ] ], @@ -236,7 +236,7 @@ public function canParseProvider() { 'page' => 7, ], [ - '~
~', + '~
~', '~
~', '~
~', ] @@ -260,9 +260,9 @@ public function canParseProvider() { 'page' => false, ], [ - '~
~', + '~
~', '~
~', - '~~', + '~~', ] ], 'framed' => [ @@ -282,7 +282,7 @@ public function canParseProvider() { 'page' => false, ], [ - '~
~', + '~
~', '~
~', ] ], @@ -304,7 +304,7 @@ public function canParseProvider() { 'page' => false, ], [ - '~
~', + '~
~', ] ], 'manual thumbnail, upright' => [ @@ -326,8 +326,8 @@ public function canParseProvider() { 'page' => false, ], [ - '~
~', - '~~', + '~' . "\n", ], 'scarce' => [ 'id1', @@ -113,9 +113,9 @@ public function parseDataProvider() { '', '', '', - 'trigger1', - '' . "\n", + 'trigger1', + '' . "\n", ], ]; } diff --git a/tests/phpunit/Unit/mw.bootstrap.parse.tests.lua b/tests/phpunit/Unit/mw.bootstrap.parse.tests.lua index a62be76..cdd035f 100644 --- a/tests/phpunit/Unit/mw.bootstrap.parse.tests.lua +++ b/tests/phpunit/Unit/mw.bootstrap.parse.tests.lua @@ -55,7 +55,7 @@ local tests = { return removeId ( mw.bootstrap.parse( component, input, args ) ) end, args = { 'alert', 'Alert content', { color = 'success', dismissible = 'fade', noStrip = true } }, - expect = { '' } + expect = { '' } }, } From 1961f913c0faaac46c3a4d14b045005c53ed4047 Mon Sep 17 00:00:00 2001 From: Morne Alberts Date: Wed, 20 May 2026 14:32:25 +0200 Subject: [PATCH 5/9] Fix DOMContentLoading typo in tooltip JS initialiser The tooltip JS module added in the BS5 migration listens for 'DOMContentLoading' (which is not a real event) instead of 'DOMContentLoaded'. The bug guard branch is reached when the script loads before the DOM is parsed; with the typo the listener never fires and the tooltip initialiser is never called on first-render. Co-Authored-By: Claude Opus 4.7 --- modules/ext.bootstrapComponents.tooltip.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ext.bootstrapComponents.tooltip.js b/modules/ext.bootstrapComponents.tooltip.js index 0610aab..895cc07 100644 --- a/modules/ext.bootstrapComponents.tooltip.js +++ b/modules/ext.bootstrapComponents.tooltip.js @@ -30,7 +30,7 @@ // Wait for DOM to be ready if ( document.readyState === 'loading' ) { - document.addEventListener( 'DOMContentLoading', initTooltips ); + document.addEventListener( 'DOMContentLoaded', initTooltips ); } else { initTooltips(); } From 90ff0adda8f1d9a12655c20a180b647d58129d56 Mon Sep 17 00:00:00 2001 From: Morne Alberts Date: Wed, 20 May 2026 16:29:27 +0200 Subject: [PATCH 6/9] Resolve upstream #68 by emitting modal markup inline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The old design path was: parser hook → ModalBuilder::parse() → ParserOutputHelper::injectLater() → $parserOutput->setExtensionData(DEFERRED_CONTENT_KEY, ...), and at render time → OutputPageParserOutput hook → $parserOutput->getExtensionData(DEFERRED_CONTENT_KEY) → $outputPage->addHTML($deferredText). Under MediaWiki 1.43+, the set-side and get-side operate on two different ParserOutput objects (an effect of ParserOutputAccess changes between 1.39 and 1.43), so the deferred-content array is stored on the parse-time object and never reaches the render-time one. Net effect: modal trigger HTML reaches the page but the modal container HTML does not, leaving the trigger with nothing to open. This is the symptom described in upstream issue #68 (open since 2025-08-08). The defensive design rationale for body-end injection (Tidy's hostility to
inside

; BS4 modal stacking concerns) no longer applies: MediaWiki 1.40+ uses RemexHtml which auto-closes the enclosing inline element rather than mangling block-in-inline, and Bootstrap 5 modals use position: fixed plus an ID-resolving data-bs-target attribute, so DOM placement is no longer significant. Refactor: - ModalBuilder::parse() returns the trigger HTML and modal HTML concatenated; modal markup emits as a sibling of its trigger at the wikitext tag's natural position. - Remove ParserOutputHelper::injectLater() and the unused EXTENSION_DATA_DEFERRED_CONTENT_KEY constant. - Trim OutputPageParserOutput::process() down to what survives the parse → render lifecycle: OutputPage::addModules() for the Bootstrap library JS module and BC's per-component JS init modules (OutputPage IS the persistent object — modules added there reach the page; modules added to the parse-time ParserOutput do not). - Add a new ext.bootstrapComponents.modal.js resource module that instantiates bootstrap.Modal on each .modal element (BS5 dropped jQuery-driven modal autoinit; without an explicit instantiation the data-bs-toggle='modal' triggers don't fire). Closes oetterer/BootstrapComponents#68. Co-Authored-By: Claude Opus 4.7 --- extension.json | 4 +- modules/ext.bootstrapComponents.modal.js | 51 +++++++++++++++++++++ src/BootstrapComponents.php | 2 - src/Hooks/OutputPageParserOutput.php | 56 +++++++----------------- src/HooksHandler.php | 4 ++ src/ModalBuilder.php | 12 ++--- src/ParserOutputHelper.php | 24 ---------- 7 files changed, 82 insertions(+), 71 deletions(-) create mode 100644 modules/ext.bootstrapComponents.modal.js diff --git a/extension.json b/extension.json index 80eb3e8..7958374 100644 --- a/extension.json +++ b/extension.json @@ -109,7 +109,9 @@ "scripts": "ext.bootstrapComponents.carousel.js" }, "ext.bootstrapComponents.modal.fix": { - "styles": "ext.bootstrapComponents.modal.fix.css" + "dependencies": "ext.bootstrap.scripts", + "styles": "ext.bootstrapComponents.modal.fix.css", + "scripts": "ext.bootstrapComponents.modal.js" }, "ext.bootstrapComponents.modal.vector-fix": { "styles": "ext.bootstrapComponents.modal.vector-fix.css" diff --git a/modules/ext.bootstrapComponents.modal.js b/modules/ext.bootstrapComponents.modal.js new file mode 100644 index 0000000..3553140 --- /dev/null +++ b/modules/ext.bootstrapComponents.modal.js @@ -0,0 +1,51 @@ +/** + * Contains javascript code executed when modals are used. + * + * Bootstrap 5 migration: BS5 dropped the jQuery `.modal()` plugin, and + * modal triggers (elements with `data-bs-toggle="modal"`) are no longer + * auto-initialised the way BS4 did. Instantiate a `bootstrap.Modal` for each + * modal container so the data-attribute triggers will actually open it. + * + * @copyright (C) 2018, Tobias Oetterer, Paderborn University + * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License, version 3 (or later) + * + * This file is part of the MediaWiki extension BootstrapComponents. + * The BootstrapComponents extension is free software: you can redistribute it + * and/or modify it under the terms of the GNU General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The BootstrapComponents extension is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * @file + * @ingroup BootstrapComponents + * @author Tobias Oetterer + */ + +( function () { + 'use strict'; + + // Wait for DOM to be ready + if ( document.readyState === 'loading' ) { + document.addEventListener( 'DOMContentLoaded', initModals ); + } else { + initModals(); + } + + function initModals() { + // Instantiate every .modal element so trigger clicks (or programmatic + // bootstrap.Modal.getOrCreateInstance(el).show()) work as expected. + var modalList = document.querySelectorAll( '.modal' ); + modalList.forEach( function ( modalEl ) { + if ( typeof bootstrap !== 'undefined' && bootstrap.Modal ) { + bootstrap.Modal.getOrCreateInstance( modalEl ); + } + } ); + } +}() ); diff --git a/src/BootstrapComponents.php b/src/BootstrapComponents.php index 6595ae2..8c7b224 100644 --- a/src/BootstrapComponents.php +++ b/src/BootstrapComponents.php @@ -55,8 +55,6 @@ */ class BootstrapComponents { - const EXTENSION_DATA_DEFERRED_CONTENT_KEY = 'bsc_deferredContent'; - const EXTENSION_DATA_NO_IMAGE_MODAL = 'bsc_no_image_modal'; diff --git a/src/Hooks/OutputPageParserOutput.php b/src/Hooks/OutputPageParserOutput.php index fd72a76..c6d3507 100644 --- a/src/Hooks/OutputPageParserOutput.php +++ b/src/Hooks/OutputPageParserOutput.php @@ -26,7 +26,6 @@ namespace MediaWiki\Extension\BootstrapComponents\Hooks; -use MediaWiki\Extension\BootstrapComponents\BootstrapComponents; use MediaWiki\Extension\BootstrapComponents\BootstrapComponentsService; use MediaWiki\Output\OutputPage; use MediaWiki\Parser\ParserOutput; @@ -34,10 +33,8 @@ /** * Class OutputPageParserOutput * - * Called after parse, before the HTML is added to the output. - * - * Method delegated to separate class to fix missing (deferred) content in - * {@see \MediaWiki\Extension\BootstrapComponents\Tests\Integration\BootstrapComponentsJSONScriptTestCaseRunnerTest::assertParserOutputForCase} + * Called after parse, before the HTML is added to the output. Loads the Vector + * compat module when running under the Vector skin. * * @see https://www.mediawiki.org/wiki/Manual:Hooks/OutputPageParserOutput * @@ -45,16 +42,6 @@ */ class OutputPageParserOutput { - /** - * @var string - */ - const INJECTION_PREFIX = ''; - - /** - * @var string - */ - const INJECTION_SUFFIX = ''; - /** * @var BootstrapComponentsService */ @@ -89,36 +76,27 @@ public function __construct( * @return void */ public function process(): void { - $deferredText = $this->getContentForLaterInjection( $this->getParserOutput() ); - if ( !empty( $deferredText ) ) { - $this->getOutputPage()->addHTML( $deferredText ); - } + // Bootstrap library JS (modals, popovers, tooltips, carousels, etc. all use + // `bootstrap.X.getOrCreateInstance(...)` under the BS5 vanilla-JS API). + $this->getOutputPage()->addModules( [ 'ext.bootstrap.scripts' ] ); + + // BC's per-component JS initialisers. Loaded here (not via addModules + // during ParserAfterParse) because addModules data set on the parse-time + // ParserOutput doesn't survive into the OutputPage lifecycle under + // MediaWiki 1.43+ — OutputPage is the persistent object, parse-time + // ParserOutput identity is not preserved. + $this->getOutputPage()->addModules( [ + 'ext.bootstrapComponents.modal.fix', + 'ext.bootstrapComponents.popover.fix', + 'ext.bootstrapComponents.tooltip.fix', + 'ext.bootstrapComponents.carousel.fix', + ] ); if ( $this->getBootstrapComponentsService()->vectorSkinInUse() ) { $this->getOutputPage()->addModules( [ 'ext.bootstrapComponents.vector-fix' ] ); } } - /** - * Returns the raw html that is to be inserted at the end of the page. - * - * @param ParserOutput $parserOutput - * - * @return string - */ - protected function getContentForLaterInjection( ParserOutput $parserOutput ): string { - $deferredContent = $parserOutput - ->getExtensionData(BootstrapComponents::EXTENSION_DATA_DEFERRED_CONTENT_KEY ); - - if ( empty( $deferredContent ) || !is_array( $deferredContent ) ) { - return ''; - } - - // clearing extension data for unit and integration tests to work - $parserOutput->setExtensionData( BootstrapComponents::EXTENSION_DATA_DEFERRED_CONTENT_KEY, null ); - return self::INJECTION_PREFIX . implode( array_values( $deferredContent ) ) . self::INJECTION_SUFFIX; - } - protected function getBootstrapComponentsService(): BootstrapComponentsService { return $this->bootstrapComponentService; } diff --git a/src/HooksHandler.php b/src/HooksHandler.php index 47ac878..c109093 100644 --- a/src/HooksHandler.php +++ b/src/HooksHandler.php @@ -225,6 +225,10 @@ public function onParserAfterParse( $parser, &$text, $stripState ): bool { continue; } foreach ( $this->getComponentLibrary()->getModulesFor( $activeComponent ) as $module ) { + // addModuleStyles for CSS only — the JS half of each component + // module is loaded centrally from OutputPageParserOutput::process() + // because addModules data does not survive the parse→OutputPage + // lifecycle under MediaWiki 1.43+. $parser->getOutput()->addModuleStyles( [ $module ] ); } } diff --git a/src/ModalBuilder.php b/src/ModalBuilder.php index 9226367..b4d949f 100644 --- a/src/ModalBuilder.php +++ b/src/ModalBuilder.php @@ -162,14 +162,16 @@ public function __construct( $id, $trigger, $content, $parserOutputHelper ) { /** * Parses the modal. * + * Emits the trigger button followed by the modal container inline. Bootstrap 5 + * modals use `position: fixed` and locate themselves via `data-bs-target="#id"`, + * so DOM placement is no longer significant the way it was for BS4 + Tidy. This + * sidesteps the parser-output-extension-data lifecycle issues that broke the + * old deferred-injection pattern under MediaWiki 1.43+ (oetterer/BootstrapComponents#68). + * * @return string */ public function parse() { - $this->parserOutputHelper->injectLater( - $this->getId(), - $this->buildModal() - ); - return $this->buildTrigger(); + return $this->buildTrigger() . $this->buildModal(); } /** diff --git a/src/ParserOutputHelper.php b/src/ParserOutputHelper.php index 0c1962e..077ff61 100644 --- a/src/ParserOutputHelper.php +++ b/src/ParserOutputHelper.php @@ -129,30 +129,6 @@ public function areImageModalsSuppressed() { ->getExtensionData( BootstrapComponents::EXTENSION_DATA_NO_IMAGE_MODAL ); } - /** - * Allows for html fragments for a given container id to be stored, so that it can be added to the page at a later time. - * - * @param string $id - * @param string $rawHtml - * - * @return ParserOutputHelper $this (fluid) - */ - public function injectLater( $id, $rawHtml ) { - if ( empty( $this->getParser()->getOutput() ) ) { - # fix issues with PageForms file upload (issue #20) - return $this; - } - if ( !empty( $rawHtml ) ) { - $deferredContent = $this->getParser()->getOutput()->getExtensionData( BootstrapComponents::EXTENSION_DATA_DEFERRED_CONTENT_KEY ); - if ( empty( $deferredContent ) ) { - $deferredContent = []; - } - $deferredContent[$id] = $rawHtml; - $this->getParser()->getOutput()->setExtensionData( BootstrapComponents::EXTENSION_DATA_DEFERRED_CONTENT_KEY, $deferredContent ); - } - return $this; - } - /** * Formats a text as error text, so it can be added to the output. * From 7fec1b8fcd63196d33b383b7b817898c6a2f556b Mon Sep 17 00:00:00 2001 From: Morne Alberts Date: Wed, 20 May 2026 15:42:13 +0200 Subject: [PATCH 7/9] Emit Bootstrap 5 text-bg- for badges, drop the .badge- family MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bootstrap 5.3 removed the .badge- class family. Replacement is .text-bg-, which sets both background-color and a contrasting text color (so a primary-coloured badge gets readable text on it without manual intervention). Update Badge::calculateClassAttribute() to emit the new family, and update BadgeTest expectations to match. This was a gap in PR #69 — that PR updated almost all components for BS5 but missed Badge's color-class emission. Co-Authored-By: Claude Opus 4.7 --- src/Components/Badge.php | 5 ++++- tests/phpunit/Unit/Components/BadgeTest.php | 10 +++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Components/Badge.php b/src/Components/Badge.php index 36643d2..cbc309e 100644 --- a/src/Components/Badge.php +++ b/src/Components/Badge.php @@ -75,7 +75,10 @@ private function calculateClassAttribute() { $class[] = 'rounded-pill'; } - $class[] = 'badge-' . $this->getValueFor( 'color', 'primary' ); + // Bootstrap 5 dropped the `.badge-` family in favour of + // `.text-bg-` (which sets background-color AND a contrasting + // foreground color). See https://getbootstrap.com/docs/5.3/migration/. + $class[] = 'text-bg-' . $this->getValueFor( 'color', 'primary' ); return $class; } } diff --git a/tests/phpunit/Unit/Components/BadgeTest.php b/tests/phpunit/Unit/Components/BadgeTest.php index 3603f1b..5c7ee0e 100644 --- a/tests/phpunit/Unit/Components/BadgeTest.php +++ b/tests/phpunit/Unit/Components/BadgeTest.php @@ -69,7 +69,7 @@ public function placeMeArgumentsProvider() { 'simple' => [ $this->input, [], - '' . $this->input . '', + '' . $this->input . '', ], 'empty' => [ '', @@ -79,22 +79,22 @@ public function placeMeArgumentsProvider() { 'manual id' => [ $this->input, [ 'id' => 'book' ], - '' . $this->input . '', + '' . $this->input . '', ], 'style and class' => [ $this->input, [ 'class' => 'dummy nice', 'style' => 'float:right;background-color:#80266e' ], - '' . $this->input . '', + '' . $this->input . '', ], 'pill' => [ $this->input, [ 'pill' => 'true' ], - '' . $this->input . '', + '' . $this->input . '', ], 'no pill' => [ $this->input, [ 'pill' => 0 ], - '' . $this->input . '', + '' . $this->input . '', ], ]; } From bd92503eb51f1488af1ac4f2b2c50f49e2c1bae1 Mon Sep 17 00:00:00 2001 From: Morne Alberts Date: Wed, 20 May 2026 16:00:30 +0200 Subject: [PATCH 8/9] Update tests to match inline modal emission MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ModalTest, ImageModalTest, ModalBuilderTest, and OutputPageParserOutputTest all assumed the old deferred-content pattern — they mocked parserOutputHelper->injectLater() to capture the modal markup separately from the parser-hook return value. After the inline-emission refactor in 5f65471 the modal markup is concatenated into the parse output and injectLater() no longer exists. Updates: - ModalTest::testCanRender asserts the parse output equals the trigger HTML and the modal HTML concatenated. - ImageModalTest::testCanParseImage asserts the expected modal markup is a substring of the parse result. - ModalBuilderTest::testCanParse asserts ModalBuilder::parse() returns trigger HTML concatenated with the modal HTML. - OutputPageParserOutputTest rewritten: asserts that OutputPage::addModules is called for ext.bootstrap.scripts + the four BC component JS modules, plus vector-fix when the Vector skin is active. Drops the old expectation that the hook calls addHTML with deferred content (the deferred-content branch was removed). 96 of BC's PHPUnit tests now pass on bs5-followup (excluding the Lua test files, which require Scribunto in the test environment). Co-Authored-By: Claude Opus 4.7 --- tests/phpunit/Unit/Components/ModalTest.php | 16 ++-- .../Unit/Hooks/OutputPageParserOutputTest.php | 77 +++++++++++++------ tests/phpunit/Unit/ImageModalTest.php | 13 ++-- tests/phpunit/Unit/ModalBuilderTest.php | 16 ++-- 4 files changed, 69 insertions(+), 53 deletions(-) diff --git a/tests/phpunit/Unit/Components/ModalTest.php b/tests/phpunit/Unit/Components/ModalTest.php index d6f9be7..75e4420 100644 --- a/tests/phpunit/Unit/Components/ModalTest.php +++ b/tests/phpunit/Unit/Components/ModalTest.php @@ -49,15 +49,9 @@ public function testCanConstruct() { */ public function testCanRender( $input, $arguments, $expectedTriggerOutput, $expectedModalOutput ) { - $modalInjection = ''; $parserOutputHelper = $this->getMockBuilder( 'MediaWiki\\Extension\\BootstrapComponents\\ParserOutputHelper' ) ->disableOriginalConstructor() ->getMock(); - $parserOutputHelper->expects( $this->any() ) - ->method( 'injectLater' ) - ->will( $this->returnCallback( function( $id, $text ) use ( &$modalInjection ) { - $modalInjection .= $text; - } ) ); $parserOutputHelper->expects( $this->any() ) ->method( 'renderErrorMessage' ) ->will( $this->returnArgument( 0 ) ); @@ -74,11 +68,11 @@ public function testCanRender( $input, $arguments, $expectedTriggerOutput, $expe /** @noinspection PhpParamsInspection */ $generatedOutput = $instance->parseComponent( $parserRequest ); - $this->assertEquals( $expectedTriggerOutput, $generatedOutput ); - $this->assertEquals( - $expectedModalOutput, - $modalInjection - ); + // Inline-emission refactor: the parser hook output is now the trigger + // HTML followed by the modal container HTML, concatenated. Previously + // they were returned separately (trigger via the parser return value, + // modal via parserOutputHelper->injectLater()). Asserts the union here. + $this->assertEquals( $expectedTriggerOutput . $expectedModalOutput, $generatedOutput ); } /** diff --git a/tests/phpunit/Unit/Hooks/OutputPageParserOutputTest.php b/tests/phpunit/Unit/Hooks/OutputPageParserOutputTest.php index 0ece4ac..ce7a2b5 100644 --- a/tests/phpunit/Unit/Hooks/OutputPageParserOutputTest.php +++ b/tests/phpunit/Unit/Hooks/OutputPageParserOutputTest.php @@ -39,40 +39,71 @@ public function testCanConstruct() { ); } - public function testHookOutputPageParserOutput() { - $content = 'CONTENT'; + public function testHookOutputPageParserOutputLoadsModules() { + // Collect every addModules() call so we can assert the union of module sets + $loadedModules = []; $outputPage = $this->createMock( OutputPage::class ); - $outputPage->expects( $this->once() ) - ->method( 'addHTML' ) - ->will( $this->returnCallback( function( $injection ) use ( &$content ) { - $content .= $injection; - } ) ); - $outputPage->expects( $this->once() ) + $outputPage->expects( $this->atLeastOnce() ) ->method( 'addModules' ) - ->with( - $this->equalTo( [ 'ext.bootstrapComponents.vector-fix' ] ) - ); - - $observerParserOutput = $this->createMock( ParserOutput::class ); - $observerParserOutput->expects( $this->exactly( 1 ) ) - ->method( 'getExtensionData' ) - ->with( - $this->stringContains( 'bsc_deferredContent' ) - ) - ->willReturn( [ 'test' ] ); + ->will( $this->returnCallback( function( $modules ) use ( &$loadedModules ) { + $loadedModules = array_merge( $loadedModules, (array)$modules ); + } ) ); + + // addHTML should NOT be called any more — the deferred-content injection + // pattern was replaced by inline emission in ModalBuilder::parse(). + $outputPage->expects( $this->never() )->method( 'addHTML' ); $bootstrapService = $this->createMock( BootstrapComponentsService::class ); $bootstrapService->expects( $this->once() ) ->method( 'vectorSkinInUse' ) ->willReturn( true ); - $instance = new OutputPageParserOutput( $outputPage, $observerParserOutput, $bootstrapService ); + $instance = new OutputPageParserOutput( + $outputPage, + $this->createMock( ParserOutput::class ), + $bootstrapService + ); $instance->process(); - $this->assertEquals( - 'CONTENTtest', - $content + // Bootstrap library JS, BC's per-component JS init modules, and the + // Vector-fix module (because vectorSkinInUse returns true above) should + // all reach OutputPage::addModules(). + $expected = [ + 'ext.bootstrap.scripts', + 'ext.bootstrapComponents.modal.fix', + 'ext.bootstrapComponents.popover.fix', + 'ext.bootstrapComponents.tooltip.fix', + 'ext.bootstrapComponents.carousel.fix', + 'ext.bootstrapComponents.vector-fix', + ]; + sort( $loadedModules ); + sort( $expected ); + $this->assertEquals( $expected, $loadedModules ); + } + + public function testVectorFixSkippedWhenNotVectorSkin() { + $loadedModules = []; + + $outputPage = $this->createMock( OutputPage::class ); + $outputPage->expects( $this->atLeastOnce() ) + ->method( 'addModules' ) + ->will( $this->returnCallback( function( $modules ) use ( &$loadedModules ) { + $loadedModules = array_merge( $loadedModules, (array)$modules ); + } ) ); + + $bootstrapService = $this->createMock( BootstrapComponentsService::class ); + $bootstrapService->expects( $this->once() ) + ->method( 'vectorSkinInUse' ) + ->willReturn( false ); + + $instance = new OutputPageParserOutput( + $outputPage, + $this->createMock( ParserOutput::class ), + $bootstrapService ); + $instance->process(); + + $this->assertNotContains( 'ext.bootstrapComponents.vector-fix', $loadedModules ); } } diff --git a/tests/phpunit/Unit/ImageModalTest.php b/tests/phpunit/Unit/ImageModalTest.php index 69306f5..da40e8e 100644 --- a/tests/phpunit/Unit/ImageModalTest.php +++ b/tests/phpunit/Unit/ImageModalTest.php @@ -254,13 +254,7 @@ function( $component ) { } ) ); - $modalInjection = ''; $parserOutputHelper = $this->createMock( ParserOutputHelper::class ); - $parserOutputHelper->expects( $this->any() ) - ->method( 'injectLater' ) - ->will( $this->returnCallback( function( $id, $text ) use ( &$modalInjection ) { - $modalInjection .= $text; - } ) ); $instance = $this->createImageModalWithMocks( null, $title, $file, $nestingController, null, $parserOutputHelper ); $time = false; @@ -283,9 +277,12 @@ function( $component ) { . '-- ' . ($resultOfParseCall ?: $res) ); } - $this->assertEquals( + // Inline-emission refactor: modal markup is concatenated into the parse + // result instead of being stashed via parserOutputHelper->injectLater(). + // Assert the modal substring appears in the same output the trigger does. + $this->assertStringContainsString( $expectedModal, - $modalInjection, + $resultOfParseCall ?: $res, 'failed modal with test data:' . $this->generatePhpCodeForManualProviderDataOneCase( $fp, $hp ) ); } diff --git a/tests/phpunit/Unit/ModalBuilderTest.php b/tests/phpunit/Unit/ModalBuilderTest.php index 744224d..8f7783c 100644 --- a/tests/phpunit/Unit/ModalBuilderTest.php +++ b/tests/phpunit/Unit/ModalBuilderTest.php @@ -49,15 +49,9 @@ public function testCanConstruct() { */ public function testCanParse( $id, $trigger, $content, $header, $footer, $outerClass, $outerStyle, $innerClass, $expectedTrigger, $expectedModal ) { - $modalInjection = ''; $parserOutputHelper = $this->getMockBuilder( 'MediaWiki\\Extension\\BootstrapComponents\\ParserOutputHelper' ) ->disableOriginalConstructor() ->getMock(); - $parserOutputHelper->expects( $this->any() ) - ->method( 'injectLater' ) - ->will( $this->returnCallback( function( $id, $text ) use ( &$modalInjection ) { - $modalInjection .= $text; - } ) ); /** @noinspection PhpParamsInspection */ $instance = new ModalBuilder( $id, $trigger, $content, $parserOutputHelper ); @@ -76,14 +70,14 @@ public function testCanParse( $id, $trigger, $content, $header, $footer, $outerC if ( $innerClass ) { $instance->setDialogClass( $innerClass ); } + // Inline-emission refactor: parse() now returns the trigger HTML and + // modal container HTML concatenated, so they emit as siblings at the + // wikitext tag's natural position. Previously the trigger was returned + // and the modal was stashed via parserOutputHelper->injectLater(). $this->assertEquals( - $expectedTrigger, + $expectedTrigger . $expectedModal, $instance->parse() ); - $this->assertEquals( - $expectedModal, - $modalInjection - ); } /** From c7f92447ab1f6b769b54dd7660f1ae8bb38cd655 Mon Sep 17 00:00:00 2001 From: Morne Alberts Date: Wed, 20 May 2026 16:17:57 +0200 Subject: [PATCH 9/9] docs: update component references for Bootstrap 5 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sweep through docs/components.md and the 12 per-component .md files under docs/components/ to bring them in line with what BS5 BC actually emits, plus a few related docs touch-ups: - All external 'see also' links pointing to https://getbootstrap.com/docs/4.1/ bumped to /docs/5.3/. Jumbotron's outdated direct link now points at the BS5 migration guide entry instead (BS5 doesn't have a jumbotron page). - jumbotron.md: prepended a deprecation banner noting the BS5 removal and the utility-class approximation BC now emits. Also clarified that the BS4-era 'enlarges fonts' behaviour does not survive the utility-class reimplementation (apply display-* utilities for that look). - components.md: inline note next to the Jumbotron list entry calling out the deprecation. - button.md / badge.md / modal.md / popover.md: clarified next to the color='default' list item that this maps to 'secondary' under Bootstrap 5 (matches the runtime behaviour Button.php, Modal.php, Popover.php already implemented). - known-issues.md: top-of-file note that the long-standing modal/popover/ tooltip 'stop working after a cache purge on MW 1.43.3' class of issues (upstream #68) was resolved in 6.0 by the inline-emission refactor. - release-notes.md: 6.0.0 entry expanded with the bug-fix section describing the deferred-content → inline-emission rewrite, the DOMContentLoading typo fix, the new modal JS module, and the OutputPageParserOutput addModules change. Also tweaked the badge bullet to mention the text-bg- family and the default→secondary mapping. - migration-guide.md: bumped 'mediawiki/bootstrap 6.x-dev' reference to '^6.0' to match the released constraint. Code-level: Badge::calculateClassAttribute now maps color='default' to 'secondary' for backward-compat (matches Button/Modal/Popover behaviour). BadgeTest still 7/7. Co-Authored-By: Claude Opus 4.7 --- docs/components.md | 3 +++ docs/components/accordion.md | 2 +- docs/components/alert.md | 4 ++-- docs/components/badge.md | 6 +++--- docs/components/button.md | 6 +++--- docs/components/card.md | 4 ++-- docs/components/carousel.md | 2 +- docs/components/collapse.md | 2 +- docs/components/jumbotron.md | 16 +++++++++++++--- docs/components/modal.md | 6 +++--- docs/components/popover.md | 6 +++--- docs/components/tooltip.md | 2 +- docs/known-issues.md | 11 +++++++++++ docs/migration-guide.md | 2 +- docs/release-notes.md | 8 +++++++- src/Components/Badge.php | 8 +++++++- 16 files changed, 62 insertions(+), 26 deletions(-) diff --git a/docs/components.md b/docs/components.md index d02f2b0..1917ade 100644 --- a/docs/components.md +++ b/docs/components.md @@ -22,6 +22,9 @@ the following components are available to be used inside the wiki text: want to hide and show large amount of content. * **[Jumbotron](components/jumbotron.md)**: A jumbotron indicates a big box for calling extra attention to some special content or information. + _Bootstrap 5 removed `.jumbotron`; the parser function is retained + (now emitting BS5 utility classes) but is deprecated and may be removed + in a future release._ * **[Modal](components/modal.md)**: The Modal component is a dialog box/popup window that is displayed on top of the current page. * **[Popover](components/popover.md)**: The Popover component produces a diff --git a/docs/components/accordion.md b/docs/components/accordion.md index ca1ffdd..36bdcd5 100644 --- a/docs/components/accordion.md +++ b/docs/components/accordion.md @@ -40,6 +40,6 @@ add multiple css styles, separate them by a semicolon. ### Links -* https://getbootstrap.com/docs/4.1/components/collapse/#accordion-example +* https://getbootstrap.com/docs/5.3/components/collapse/#accordion-example * https://www.w3schools.com/bootstrap4/bootstrap_collapse.asp diff --git a/docs/components/alert.md b/docs/components/alert.md index 0a0907a..f8a4027 100644 --- a/docs/components/alert.md +++ b/docs/components/alert.md @@ -54,6 +54,6 @@ add multiple css styles, separate them by a semicolon. ### Links -* https://getbootstrap.com/docs/4.1/components/alerts/ +* https://getbootstrap.com/docs/5.3/components/alerts/ * https://www.w3schools.com/bootstrap4/bootstrap_alerts.asp -* https://getbootstrap.com/docs/4.1/utilities/colors/ +* https://getbootstrap.com/docs/5.3/utilities/colors/ diff --git a/docs/components/badge.md b/docs/components/badge.md index 31eb228..880adb0 100644 --- a/docs/components/badge.md +++ b/docs/components/badge.md @@ -24,7 +24,7 @@ add multiple classes, separate them by a space. Allowed Values are

    -
  • default
  • +
  • default (maps to secondary under Bootstrap 5)
  • primary
  • secondary
  • success
  • @@ -52,7 +52,7 @@ add multiple css styles, separate them by a semicolon. ### Links -* https://getbootstrap.com/docs/4.1/components/badge/ +* https://getbootstrap.com/docs/5.3/components/badge/ * https://www.w3schools.com/bootstrap4/bootstrap_badges.asp -* https://getbootstrap.com/docs/4.1/utilities/colors/ +* https://getbootstrap.com/docs/5.3/utilities/colors/ * diff --git a/docs/components/button.md b/docs/components/button.md index c228f21..d079b83 100644 --- a/docs/components/button.md +++ b/docs/components/button.md @@ -26,7 +26,7 @@ add multiple classes, separate them by a space. Allowed Values are
      -
    • default
    • +
    • default (maps to secondary under Bootstrap 5)
    • primary
    • secondary
    • success
    • @@ -78,7 +78,7 @@ background with button color. ### Links -* https://getbootstrap.com/docs/4.1/components/buttons/ +* https://getbootstrap.com/docs/5.3/components/buttons/ * https://www.w3schools.com/bootstrap4/bootstrap_buttons.asp -* https://getbootstrap.com/docs/4.1/utilities/colors/ +* https://getbootstrap.com/docs/5.3/utilities/colors/ * diff --git a/docs/components/card.md b/docs/components/card.md index a059ffe..a036186 100644 --- a/docs/components/card.md +++ b/docs/components/card.md @@ -109,6 +109,6 @@ add multiple css styles, separate them by a semicolon. ### Links -* https://getbootstrap.com/docs/4.1/components/card/ +* https://getbootstrap.com/docs/5.3/components/card/ * https://www.w3schools.com/bootstrap4/bootstrap_cards.asp -* https://getbootstrap.com/docs/4.1/utilities/colors/ +* https://getbootstrap.com/docs/5.3/utilities/colors/ diff --git a/docs/components/carousel.md b/docs/components/carousel.md index 1a8f7fd..706e082 100644 --- a/docs/components/carousel.md +++ b/docs/components/carousel.md @@ -38,5 +38,5 @@ attributes, otherwise the parser will drop all but one: ``` ### Links -* https://getbootstrap.com/docs/4.1/components/carousel/ +* https://getbootstrap.com/docs/5.3/components/carousel/ * https://www.w3schools.com/bootstrap4/bootstrap_carousel.asp diff --git a/docs/components/collapse.md b/docs/components/collapse.md index 8b6abd7..9702546 100644 --- a/docs/components/collapse.md +++ b/docs/components/collapse.md @@ -29,5 +29,5 @@ be used as the trigger element. In this case, all but the attributes ### Links -* https://getbootstrap.com/docs/4.1/components/collapse/ +* https://getbootstrap.com/docs/5.3/components/collapse/ * https://www.w3schools.com/bootstrap4/bootstrap_collapse.asp diff --git a/docs/components/jumbotron.md b/docs/components/jumbotron.md index 86a34bd..e9788f2 100644 --- a/docs/components/jumbotron.md +++ b/docs/components/jumbotron.md @@ -1,9 +1,19 @@ ## Jumbotron + +> **⚠️ Deprecated.** Bootstrap 5 removed the `.jumbotron` component. The +> `bootstrap_jumbotron` parser function is retained for backward compatibility +> and now emits the equivalent Bootstrap 5 utility-class combination +> (`p-5 mb-4 bg-body-tertiary rounded-3`) per the official migration guide. +> New content should prefer composing utility classes directly. The parser +> function may be removed in a future major release. + A jumbotron indicates a big box for calling extra attention to some special content or information. -A jumbotron is displayed as a grey box with rounded corners. It also enlarges -the font sizes of the text inside it. +A jumbotron is displayed as a light box with rounded corners. The Bootstrap 4 +version also enlarged the font sizes of the text inside it; the Bootstrap 5 +utility-class approximation does not — apply `display-*` utility classes to the +contained headings if you want the BS4-era larger-font look. See also: * [Alert](alert.md) @@ -33,5 +43,5 @@ add multiple css styles, separate them by a semicolon. ### Links -* https://getbootstrap.com/docs/4.1/components/jumbotron/ +* https://getbootstrap.com/docs/5.3/migration/#jumbotron * https://www.w3schools.com/bootstrap4/bootstrap_jumbotron.asp diff --git a/docs/components/modal.md b/docs/components/modal.md index 2bfe7a4..09db2e7 100644 --- a/docs/components/modal.md +++ b/docs/components/modal.md @@ -31,7 +31,7 @@ add multiple classes, separate them by a space. Allowed Values are
        -
      • default
      • +
      • default (maps to secondary under Bootstrap 5)
      • primary
      • secondary
      • success
      • @@ -76,8 +76,8 @@ be used as the trigger element. ### Links -* https://getbootstrap.com/docs/4.1/components/modal/ +* https://getbootstrap.com/docs/5.3/components/modal/ * https://www.w3schools.com/bootstrap4/bootstrap_modal.asp -* https://getbootstrap.com/docs/4.1/utilities/colors/ +* https://getbootstrap.com/docs/5.3/utilities/colors/ Please, see also the [known issues](../known-issues.md) with this component. diff --git a/docs/components/popover.md b/docs/components/popover.md index 5b75e1f..fabf78a 100644 --- a/docs/components/popover.md +++ b/docs/components/popover.md @@ -26,7 +26,7 @@ add multiple classes, separate them by a space. Allowed Values are
          -
        • default
        • +
        • default (maps to secondary under Bootstrap 5)
        • primary
        • secondary
        • success
        • @@ -94,6 +94,6 @@ behaviour with: ### Links -* https://getbootstrap.com/docs/4.1/components/popovers/ +* https://getbootstrap.com/docs/5.3/components/popovers/ * https://www.w3schools.com/bootstrap4/bootstrap_popover.asp -* https://getbootstrap.com/docs/4.1/utilities/colors/ +* https://getbootstrap.com/docs/5.3/utilities/colors/ diff --git a/docs/components/tooltip.md b/docs/components/tooltip.md index 726af56..b5f01d9 100644 --- a/docs/components/tooltip.md +++ b/docs/components/tooltip.md @@ -51,5 +51,5 @@ following to your `Mediawiki:Common.css`: ``` ### Links -* https://getbootstrap.com/docs/4.1/components/tooltips/ +* https://getbootstrap.com/docs/5.3/components/tooltips/ * https://www.w3schools.com/bootstrap4/bootstrap_tooltip.asp diff --git a/docs/known-issues.md b/docs/known-issues.md index 23b8ea5..70dccd1 100644 --- a/docs/known-issues.md +++ b/docs/known-issues.md @@ -2,6 +2,15 @@ Some components cause problems with other components or "external" elements. +> **Note for Bootstrap 5 (BootstrapComponents 6.0):** +> A long-standing class of issues affecting Modal/Popover/Tooltip components +> under MediaWiki 1.43+ (upstream issue [#68][issue-68] — "stop working after +> a cache purge") was resolved in 6.0 by emitting modal markup inline next to +> its trigger instead of relying on the `ParserOutput::setExtensionData` / +> `OutputPageParserOutput` deferred-injection mechanism. If you saw any of the +> earlier "modal does not open" / "popover does nothing" symptoms under MW +> 1.43, those should now be fixed. + ### Modals and popovers When you put popovers on a page with modals (or image modals), the modals break. @@ -41,3 +50,5 @@ simply "push" the modal further down. For example: top: 60px; } ``` + +[issue-68]: https://github.com/oetterer/BootstrapComponents/issues/68 diff --git a/docs/migration-guide.md b/docs/migration-guide.md index 526663a..fa422aa 100644 --- a/docs/migration-guide.md +++ b/docs/migration-guide.md @@ -7,7 +7,7 @@ BootstrapComponents 6.0 upgrades the underlying Bootstrap framework from version #### System Requirements - **MediaWiki:** 1.43 or later (upgraded from 1.39) - **PHP:** 8.1 or later (upgraded from 8.0) -- **Bootstrap Extension:** mediawiki/bootstrap 6.x-dev from [ProfessionalWiki/Bootstrap](https://github.com/ProfessionalWiki/Bootstrap) +- **Bootstrap Extension:** mediawiki/bootstrap ^6.0 from [ProfessionalWiki/Bootstrap](https://github.com/ProfessionalWiki/Bootstrap) #### User Impact **Good News:** Most existing wiki markup using BootstrapComponents should continue to work without any changes! The extension handles most Bootstrap 5 migrations internally. diff --git a/docs/release-notes.md b/docs/release-notes.md index e0f6feb..564b420 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -21,12 +21,18 @@ Released on _TBD_ - `data-placement` → `data-bs-placement` - `data-trigger` → `data-bs-trigger` * JavaScript modules migrated from jQuery to vanilla JavaScript (Bootstrap 5 requirement) -* Badge component: `badge-pill` class changed to `rounded-pill` +* Badge component: `badge-pill` class changed to `rounded-pill`; `badge-` family replaced with `text-bg-` per the BS5 migration guide; `color="default"` now maps to `text-bg-secondary` for backward-compat * Button component: `btn-default` automatically mapped to `btn-secondary` * Alert and Modal close buttons: Migrated from `.close` class with `×` character to `.btn-close` class * Jumbotron component: Recreated using Bootstrap 5 utility classes (`p-5 mb-4 bg-body-tertiary rounded-3`); the parser function is retained for compatibility but is now `@deprecated` * All CSS fixes reviewed and updated for Bootstrap 5 compatibility +**Bug fixes (also benefit users on MediaWiki 1.43+ even without BS5 markup changes):** +* Modal, Popover and Tooltip components now actually trigger under MediaWiki 1.43+. The previous architecture relied on `ParserOutput::setExtensionData` carrying deferred modal markup from parse-time to the `OutputPageParserOutput` hook callback; MW 1.43+'s `ParserOutputAccess` lifecycle no longer preserves this. Modal markup is now emitted inline next to its trigger button, which works regardless of MW version. Closes upstream issue [#68](https://github.com/oetterer/BootstrapComponents/issues/68). +* Tooltip JS module's `DOMContentLoading` typo (should be `DOMContentLoaded`) fixed. +* Modal trigger received a new ResourceLoader JS module that explicitly instantiates `bootstrap.Modal` on each `.modal` element (BS5 no longer auto-initialises modal triggers the way BS4 + jQuery did). +* `OutputPageParserOutput` hook now also loads the BC component JS modules via `OutputPage::addModules()` — previously these were declared with a `scripts` key but only added via `addModuleStyles()`, which never loaded their JS. + **User Impact:** * Most existing wiki markup using BootstrapComponents should continue to work without changes * JavaScript initialization now uses Bootstrap 5 native API diff --git a/src/Components/Badge.php b/src/Components/Badge.php index cbc309e..f7b0b72 100644 --- a/src/Components/Badge.php +++ b/src/Components/Badge.php @@ -78,7 +78,13 @@ private function calculateClassAttribute() { // Bootstrap 5 dropped the `.badge-` family in favour of // `.text-bg-` (which sets background-color AND a contrasting // foreground color). See https://getbootstrap.com/docs/5.3/migration/. - $class[] = 'text-bg-' . $this->getValueFor( 'color', 'primary' ); + // `color="default"` maps to `text-bg-secondary` for backward-compat + // with BS3-era markup (see also Button/Modal/Popover). + $color = $this->getValueFor( 'color', 'primary' ); + if ( $color === 'default' ) { + $color = 'secondary'; + } + $class[] = 'text-bg-' . $color; return $class; } }