Update sticky header to use ARIA attributes for section collapsing and add accessible label to toggle buttons

Bug: T303766
Change-Id: Idda4f286a42152af1d233588a1839ada5491ce95
This commit is contained in:
bwang 2022-03-29 15:51:45 -05:00
parent 94d9445ba3
commit f1c70e99b7
11 changed files with 244 additions and 163 deletions

View File

@ -39,6 +39,7 @@
"vector-intro-page": "Help:Introduction",
"vector-toc-heading": "Contents",
"vector-toc-beginning": "Beginning",
"vector-toc-toggle-button-label": "Toggle $1 subsection",
"vector-anon-user-menu-pages": "Pages for logged out editors",
"vector-anon-user-menu-pages-learn": "learn more",
"vector-anon-user-menu-pages-label": "Learn more about editing",

View File

@ -54,6 +54,7 @@
"vector-intro-page": "Introduction or tutorial page for the wiki. Typically either Project/Help:Introduction ([[d:Q3945]]) or Project/Help:Tutorial ([[d:Q915263]]).",
"vector-toc-heading": "Heading of table of contents\n\n{{Identical|Content}}",
"vector-toc-beginning": "Shown in the table of contents: Text of link to the beginning of the article.",
"vector-toc-toggle-button-label": "Used to label TOC section toggle buttons for screen readers",
"vector-anon-user-menu-pages": "Label describing the anon editor links in the anon user menu",
"vector-anon-user-menu-pages-learn": "Lowercase text of link that goes to Help:Introduction and helps the user learn more about editing",
"vector-anon-user-menu-pages-label": "Accessible version of 'vector-anon-user-menu-pages' link text, prompts user to learn more about editing",

View File

@ -528,8 +528,7 @@ abstract class SkinVector extends SkinMustache {
true,
'searchform',
true
),
'data-toc' => $this->getTocData( $parentData['data-toc'] ?? [] )
)
] );
if ( $skin->getUser()->isRegistered() ) {
@ -581,30 +580,6 @@ abstract class SkinVector extends SkinMustache {
return $commonSkinData;
}
/**
* Annotates table of contents data with Vector-specific information.
*
* @param array $tocData
* @return array
*/
private function getTocData( array $tocData ): array {
// If the table of contents has no items, we won't output it.
// empty array is interpreted by Mustache as falsey.
if ( empty( $tocData ) || empty( $tocData[ 'array-sections' ] ) ) {
return [];
}
return array_merge( $tocData, [
'is-vector-toc-beginning-enabled' => $this->getConfig()->get(
'VectorTableOfContentsBeginning'
),
'vector-is-collapse-sections-enabled' =>
$tocData[ 'number-section-count'] >= $this->getConfig()->get(
'VectorTableOfContentsCollapseAtCount'
)
] );
}
/**
* Annotates search box with Vector-specific information
*

View File

@ -77,6 +77,38 @@ class SkinVector22 extends SkinVector {
return $this->isTOCEnabled();
}
/**
* Annotates table of contents data with Vector-specific information.
*
* @param array $tocData
* @return array
*/
private function getTocData( array $tocData ): array {
// If the table of contents has no items, we won't output it.
// empty array is interpreted by Mustache as falsey.
if ( empty( $tocData ) || empty( $tocData[ 'array-sections' ] ) ) {
return [];
}
// Populate button labels for collapsible TOC sections
foreach ( $tocData[ 'array-sections' ] as &$section ) {
if ( $section['is-top-level-section'] && $section['is-parent-section'] ) {
$section['vector-button-label'] =
$this->msg( 'vector-toc-toggle-button-label', $section['line'] )->text();
}
}
return array_merge( $tocData, [
'is-vector-toc-beginning-enabled' => $this->getConfig()->get(
'VectorTableOfContentsBeginning'
),
'vector-is-collapse-sections-enabled' =>
$tocData[ 'number-section-count'] >= $this->getConfig()->get(
'VectorTableOfContentsCollapseAtCount'
)
] );
}
/**
* Temporary function while we deprecate SkinVector class.
*
@ -92,6 +124,9 @@ class SkinVector22 extends SkinVector {
public function getTemplateData(): array {
$featureManager = VectorServices::getFeatureManager();
$parentData = parent::getTemplateData();
$parentData['data-toc'] = $this->getTocData( $parentData['data-toc'] ?? [] );
if ( !$this->isTableOfContentsVisibleInSidebar() ) {
unset( $parentData['data-toc'] );
}

View File

@ -1,5 +1,5 @@
<nav id="mw-panel-toc" class="sidebar-toc" role="navigation" aria-labelledby="sidebar-toc-header" data-event-name="ui.sidebar-toc">
<div class="sidebar-toc-header">
<nav id="mw-panel-toc" class="sidebar-toc" role="navigation" aria-labelledby="sidebar-toc-label" data-event-name="ui.sidebar-toc">
<div id="sidebar-toc-label" class="sidebar-toc-header">
<h2 class="sidebar-toc-title" aria-hidden="true">{{ msg-vector-toc-heading }}</h2>
</div>
<ul class="sidebar-toc-contents">

View File

@ -5,9 +5,11 @@
<span class="sidebar-toc-numb">{{number}}</span>{{{line}}}</div>
</a>
{{#is-top-level-section}}{{#is-parent-section}}
<button class="mw-ui-icon mw-ui-icon-wikimedia-downTriangle mw-ui-icon-small sidebar-toc-toggle"></button>
<button aria-controls="toc-{{anchor}}-sublist" class="mw-ui-icon mw-ui-icon-wikimedia-downTriangle mw-ui-icon-small sidebar-toc-toggle">
{{{vector-button-label}}}
</button>
{{/is-parent-section}}{{/is-top-level-section}}
<ul class="sidebar-toc-list">
<ul id="toc-{{anchor}}-sublist" class="sidebar-toc-list">
{{#array-sections}}
{{>TableOfContents__line}}
{{/array-sections}}

2
package-lock.json generated
View File

@ -13,7 +13,7 @@
"@types/node-fetch": "2.5.7",
"@vue/test-utils": "1.1.0",
"@wikimedia/mw-node-qunit": "6.3.0",
"@wikimedia/types-wikimedia": "^0.3.3",
"@wikimedia/types-wikimedia": "0.3.3",
"@wikimedia/wvui": "0.3.5",
"babel-loader": "8.0.6",
"commander": "9.1.0",

View File

@ -178,8 +178,10 @@ module.exports = function tableOfContents( props ) {
}
const parentSection = /** @type {HTMLElement} */ ( tocSection.closest( `.${PARENT_SECTION_CLASS}` ) );
const toggle = tocSection.querySelector( `.${TOGGLE_CLASS}` );
if ( parentSection && expandedSections.indexOf( parentSection ) < 0 ) {
if ( parentSection && toggle && expandedSections.indexOf( parentSection ) < 0 ) {
toggle.setAttribute( 'aria-expanded', 'true' );
parentSection.classList.add( EXPANDED_SECTION_CLASS );
expandedSections.push( parentSection );
}
@ -229,7 +231,10 @@ module.exports = function tableOfContents( props ) {
function collapseSections( selectedIds ) {
const sectionIdsToCollapse = selectedIds || getExpandedSectionIds();
expandedSections = expandedSections.filter( function ( section ) {
if ( sectionIdsToCollapse.indexOf( section.id ) > -1 ) {
const isSelected = sectionIdsToCollapse.indexOf( section.id ) > -1;
const toggle = isSelected ? section.getElementsByClassName( TOGGLE_CLASS ) : undefined;
if ( isSelected && toggle && toggle.length > 0 ) {
toggle[ 0 ].setAttribute( 'aria-expanded', 'false' );
section.classList.remove( EXPANDED_SECTION_CLASS );
return false;
}
@ -252,6 +257,23 @@ module.exports = function tableOfContents( props ) {
}
}
/**
* Set aria-expanded attribute for all toggle buttons.
*/
function initializeExpandedStatus() {
const parentSections = props.container.querySelectorAll( `.${PARENT_SECTION_CLASS}` );
parentSections.forEach( ( section ) => {
const expanded = section.classList.contains( EXPANDED_SECTION_CLASS );
const toggle = section.querySelector( `.${TOGGLE_CLASS}` );
if ( toggle ) {
toggle.setAttribute( 'aria-expanded', expanded.toString() );
}
} );
}
/**
* Bind event listeners for clicking on section headings and toggle buttons.
*/
function bindClickListener() {
props.container.addEventListener( 'click', function ( e ) {
if (
@ -284,6 +306,9 @@ module.exports = function tableOfContents( props ) {
props.container.querySelectorAll( `.${EXPANDED_SECTION_CLASS}` )
);
// Initialize toggle buttons aria-expanded attribute.
initializeExpandedStatus();
// Bind event listeners.
bindClickListener();
}

View File

@ -1,8 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Table of contents when \`vector-is-collapse-sections-enabled\` is false renders 1`] = `
"<nav id=\\"mw-panel-toc\\" class=\\"sidebar-toc\\" role=\\"navigation\\" aria-labelledby=\\"sidebar-toc-header\\" data-event-name=\\"ui.sidebar-toc\\">
<div class=\\"sidebar-toc-header\\">
exports[`Table of contents renders when \`vector-is-collapse-sections-enabled\` is false 1`] = `
"<nav id=\\"mw-panel-toc\\" class=\\"sidebar-toc\\" role=\\"navigation\\" aria-labelledby=\\"sidebar-toc-label\\" data-event-name=\\"ui.sidebar-toc\\">
<div id=\\"sidebar-toc-label\\" class=\\"sidebar-toc-header\\">
<h2 class=\\"sidebar-toc-title\\" aria-hidden=\\"true\\">Contents</h2>
</div>
<ul class=\\"sidebar-toc-contents\\">
@ -16,7 +16,7 @@ exports[`Table of contents when \`vector-is-collapse-sections-enabled\` is false
<div class=\\"sidebar-toc-text\\">
<span class=\\"sidebar-toc-numb\\">1</span>foo</div>
</a>
<ul class=\\"sidebar-toc-list\\">
<ul id=\\"toc-foo-sublist\\" class=\\"sidebar-toc-list\\">
</ul>
</li>
<li id=\\"toc-bar\\" class=\\"sidebar-toc-list-item sidebar-toc-level-1 sidebar-toc-list-item-expanded\\">
@ -24,20 +24,22 @@ exports[`Table of contents when \`vector-is-collapse-sections-enabled\` is false
<div class=\\"sidebar-toc-text\\">
<span class=\\"sidebar-toc-numb\\">2</span>bar</div>
</a>
<button class=\\"mw-ui-icon mw-ui-icon-wikimedia-downTriangle mw-ui-icon-small sidebar-toc-toggle\\"></button>
<ul class=\\"sidebar-toc-list\\">
<button aria-controls=\\"toc-bar-sublist\\" class=\\"mw-ui-icon mw-ui-icon-wikimedia-downTriangle mw-ui-icon-small sidebar-toc-toggle\\" aria-expanded=\\"true\\">
Toggle bar subsection
</button>
<ul id=\\"toc-bar-sublist\\" class=\\"sidebar-toc-list\\">
<li id=\\"toc-baz\\" class=\\"sidebar-toc-list-item sidebar-toc-level-2\\">
<a class=\\"sidebar-toc-link\\" href=\\"#baz\\">
<div class=\\"sidebar-toc-text\\">
<span class=\\"sidebar-toc-numb\\">2.1</span>baz</div>
</a>
<ul class=\\"sidebar-toc-list\\">
<ul id=\\"toc-baz-sublist\\" class=\\"sidebar-toc-list\\">
<li id=\\"toc-qux\\" class=\\"sidebar-toc-list-item sidebar-toc-level-3\\">
<a class=\\"sidebar-toc-link\\" href=\\"#qux\\">
<div class=\\"sidebar-toc-text\\">
<span class=\\"sidebar-toc-numb\\">2.1.1</span>qux</div>
</a>
<ul class=\\"sidebar-toc-list\\">
<ul id=\\"toc-qux-sublist\\" class=\\"sidebar-toc-list\\">
</ul>
</li>
</ul>
@ -49,7 +51,7 @@ exports[`Table of contents when \`vector-is-collapse-sections-enabled\` is false
<div class=\\"sidebar-toc-text\\">
<span class=\\"sidebar-toc-numb\\">3</span>quux</div>
</a>
<ul class=\\"sidebar-toc-list\\">
<ul id=\\"toc-quux-sublist\\" class=\\"sidebar-toc-list\\">
</ul>
</li>
</ul>
@ -57,9 +59,9 @@ exports[`Table of contents when \`vector-is-collapse-sections-enabled\` is false
"
`;
exports[`Table of contents when \`vector-is-collapse-sections-enabled\` is true renders 1`] = `
"<nav id=\\"mw-panel-toc\\" class=\\"sidebar-toc\\" role=\\"navigation\\" aria-labelledby=\\"sidebar-toc-header\\" data-event-name=\\"ui.sidebar-toc\\">
<div class=\\"sidebar-toc-header\\">
exports[`Table of contents renders when \`vector-is-collapse-sections-enabled\` is true 1`] = `
"<nav id=\\"mw-panel-toc\\" class=\\"sidebar-toc\\" role=\\"navigation\\" aria-labelledby=\\"sidebar-toc-label\\" data-event-name=\\"ui.sidebar-toc\\">
<div id=\\"sidebar-toc-label\\" class=\\"sidebar-toc-header\\">
<h2 class=\\"sidebar-toc-title\\" aria-hidden=\\"true\\">Contents</h2>
</div>
<ul class=\\"sidebar-toc-contents\\">
@ -73,7 +75,7 @@ exports[`Table of contents when \`vector-is-collapse-sections-enabled\` is true
<div class=\\"sidebar-toc-text\\">
<span class=\\"sidebar-toc-numb\\">1</span>foo</div>
</a>
<ul class=\\"sidebar-toc-list\\">
<ul id=\\"toc-foo-sublist\\" class=\\"sidebar-toc-list\\">
</ul>
</li>
<li id=\\"toc-bar\\" class=\\"sidebar-toc-list-item sidebar-toc-level-1\\">
@ -81,20 +83,22 @@ exports[`Table of contents when \`vector-is-collapse-sections-enabled\` is true
<div class=\\"sidebar-toc-text\\">
<span class=\\"sidebar-toc-numb\\">2</span>bar</div>
</a>
<button class=\\"mw-ui-icon mw-ui-icon-wikimedia-downTriangle mw-ui-icon-small sidebar-toc-toggle\\"></button>
<ul class=\\"sidebar-toc-list\\">
<button aria-controls=\\"toc-bar-sublist\\" class=\\"mw-ui-icon mw-ui-icon-wikimedia-downTriangle mw-ui-icon-small sidebar-toc-toggle\\" aria-expanded=\\"false\\">
Toggle bar subsection
</button>
<ul id=\\"toc-bar-sublist\\" class=\\"sidebar-toc-list\\">
<li id=\\"toc-baz\\" class=\\"sidebar-toc-list-item sidebar-toc-level-2\\">
<a class=\\"sidebar-toc-link\\" href=\\"#baz\\">
<div class=\\"sidebar-toc-text\\">
<span class=\\"sidebar-toc-numb\\">2.1</span>baz</div>
</a>
<ul class=\\"sidebar-toc-list\\">
<ul id=\\"toc-baz-sublist\\" class=\\"sidebar-toc-list\\">
<li id=\\"toc-qux\\" class=\\"sidebar-toc-list-item sidebar-toc-level-3\\">
<a class=\\"sidebar-toc-link\\" href=\\"#qux\\">
<div class=\\"sidebar-toc-text\\">
<span class=\\"sidebar-toc-numb\\">2.1.1</span>qux</div>
</a>
<ul class=\\"sidebar-toc-list\\">
<ul id=\\"toc-qux-sublist\\" class=\\"sidebar-toc-list\\">
</ul>
</li>
</ul>
@ -106,7 +110,7 @@ exports[`Table of contents when \`vector-is-collapse-sections-enabled\` is true
<div class=\\"sidebar-toc-text\\">
<span class=\\"sidebar-toc-numb\\">3</span>quux</div>
</a>
<ul class=\\"sidebar-toc-list\\">
<ul id=\\"toc-quux-sublist\\" class=\\"sidebar-toc-list\\">
</ul>
</li>
</ul>

View File

@ -37,6 +37,7 @@ function render( templateProps = {} ) {
anchor: 'bar',
'is-top-level-section': true,
'is-parent-section': true,
'vector-button-label': 'Toggle bar subsection',
'array-sections': [ {
toclevel: 2,
number: '2.1',
@ -91,6 +92,27 @@ function mount( templateProps = {} ) {
}
describe( 'Table of contents', () => {
describe( 'renders', () => {
test( 'when `vector-is-collapse-sections-enabled` is false', () => {
const toc = mount();
expect( document.body.innerHTML ).toMatchSnapshot();
expect( barSection.classList.contains( toc.EXPANDED_SECTION_CLASS ) ).toEqual( true );
} );
test( 'when `vector-is-collapse-sections-enabled` is true', () => {
const toc = mount( { 'vector-is-collapse-sections-enabled': true } );
expect( document.body.innerHTML ).toMatchSnapshot();
expect( barSection.classList.contains( toc.EXPANDED_SECTION_CLASS ) ).toEqual( false );
} );
test( 'toggles for top level parent sections', () => {
const toc = mount();
expect( fooSection.getElementsByClassName( toc.TOGGLE_CLASS ).length ).toEqual( 0 );
expect( barSection.getElementsByClassName( toc.TOGGLE_CLASS ).length ).toEqual( 1 );
expect( bazSection.getElementsByClassName( toc.TOGGLE_CLASS ).length ).toEqual( 0 );
expect( quxSection.getElementsByClassName( toc.TOGGLE_CLASS ).length ).toEqual( 0 );
expect( quuxSection.getElementsByClassName( toc.TOGGLE_CLASS ).length ).toEqual( 0 );
} );
} );
describe( 'binds event listeners', () => {
test( 'for onHeadingClick', () => {
const toc = mount();
@ -110,17 +132,8 @@ describe( 'Table of contents', () => {
} );
} );
test( 'renders toggles for top level parent sections', () => {
const toc = mount();
expect( fooSection.getElementsByClassName( toc.TOGGLE_CLASS ).length ).toEqual( 0 );
expect( barSection.getElementsByClassName( toc.TOGGLE_CLASS ).length ).toEqual( 1 );
expect( bazSection.getElementsByClassName( toc.TOGGLE_CLASS ).length ).toEqual( 0 );
expect( quxSection.getElementsByClassName( toc.TOGGLE_CLASS ).length ).toEqual( 0 );
expect( quuxSection.getElementsByClassName( toc.TOGGLE_CLASS ).length ).toEqual( 0 );
} );
describe( 'when changing sections', () => {
test( 'applies correct class', () => {
describe( 'applies correct classes', () => {
test( 'when changing active sections', () => {
const toc = mount( { 'vector-is-collapse-sections-enabled': true } );
toc.changeActiveSection( 'toc-foo' );
expect( fooSection.classList.contains( toc.ACTIVE_SECTION_CLASS ) ).toEqual( true );
@ -150,72 +163,47 @@ describe( 'Table of contents', () => {
expect( quxSection.classList.contains( toc.ACTIVE_SECTION_CLASS ) ).toEqual( true );
expect( quuxSection.classList.contains( toc.ACTIVE_SECTION_CLASS ) ).toEqual( false );
} );
} );
describe( 'when `vector-is-collapse-sections-enabled` is false', () => {
test( 'renders', () => {
mount();
expect( document.body.innerHTML ).toMatchSnapshot();
} );
test( 'expands sections', () => {
test( 'when expanding sections', () => {
const toc = mount();
toc.expandSection( 'toc-foo' );
expect( fooSection.classList.contains( toc.EXPANDED_SECTION_CLASS ) ).toEqual( true );
expect( barSection.classList.contains( toc.EXPANDED_SECTION_CLASS ) ).toEqual( true );
expect( bazSection.classList.contains( toc.EXPANDED_SECTION_CLASS ) ).toEqual( false );
expect( quxSection.classList.contains( toc.EXPANDED_SECTION_CLASS ) ).toEqual( false );
expect( quuxSection.classList.contains( toc.EXPANDED_SECTION_CLASS ) ).toEqual( true );
toc.expandSection( 'toc-bar' );
expect( fooSection.classList.contains( toc.EXPANDED_SECTION_CLASS ) ).toEqual( true );
expect( barSection.classList.contains( toc.EXPANDED_SECTION_CLASS ) ).toEqual( true );
expect( bazSection.classList.contains( toc.EXPANDED_SECTION_CLASS ) ).toEqual( false );
expect( quxSection.classList.contains( toc.EXPANDED_SECTION_CLASS ) ).toEqual( false );
expect( quuxSection.classList.contains( toc.EXPANDED_SECTION_CLASS ) ).toEqual( true );
} );
test( 'toggles expanded sections', () => {
test( 'when toggling sections', () => {
const toc = mount();
toc.toggleExpandSection( 'toc-foo' );
expect( fooSection.classList.contains( toc.EXPANDED_SECTION_CLASS ) ).toEqual( false );
toc.toggleExpandSection( 'toc-foo' );
expect( fooSection.classList.contains( toc.EXPANDED_SECTION_CLASS ) ).toEqual( true );
} );
} );
describe( 'when `vector-is-collapse-sections-enabled` is true', () => {
test( 'renders', () => {
mount( { 'vector-is-collapse-sections-enabled': true } );
expect( document.body.innerHTML ).toMatchSnapshot();
} );
test( 'expands sections', () => {
const toc = mount( { 'vector-is-collapse-sections-enabled': true } );
toc.expandSection( 'toc-foo' );
expect( fooSection.classList.contains( toc.EXPANDED_SECTION_CLASS ) ).toEqual( true );
toc.toggleExpandSection( 'toc-bar' );
expect( barSection.classList.contains( toc.EXPANDED_SECTION_CLASS ) ).toEqual( false );
expect( bazSection.classList.contains( toc.EXPANDED_SECTION_CLASS ) ).toEqual( false );
expect( quxSection.classList.contains( toc.EXPANDED_SECTION_CLASS ) ).toEqual( false );
expect( quuxSection.classList.contains( toc.EXPANDED_SECTION_CLASS ) ).toEqual( false );
toc.toggleExpandSection( 'toc-bar' );
expect( barSection.classList.contains( toc.EXPANDED_SECTION_CLASS ) ).toEqual( true );
} );
} );
describe( 'applies the correct aria attributes', () => {
test( 'when initialized', () => {
const toc = mount();
const toggleButton = /** @type {HTMLElement} */ ( barSection.querySelector( `.${toc.TOGGLE_CLASS}` ) );
expect( toggleButton.getAttribute( 'aria-expanded' ) ).toEqual( 'true' );
} );
test( 'when expanding sections', () => {
const toc = mount();
const toggleButton = /** @type {HTMLElement} */ ( barSection.querySelector( `.${toc.TOGGLE_CLASS}` ) );
toc.expandSection( 'toc-bar' );
expect( fooSection.classList.contains( toc.EXPANDED_SECTION_CLASS ) ).toEqual( true );
expect( barSection.classList.contains( toc.EXPANDED_SECTION_CLASS ) ).toEqual( true );
expect( bazSection.classList.contains( toc.EXPANDED_SECTION_CLASS ) ).toEqual( false );
expect( quxSection.classList.contains( toc.EXPANDED_SECTION_CLASS ) ).toEqual( false );
expect( quuxSection.classList.contains( toc.EXPANDED_SECTION_CLASS ) ).toEqual( false );
expect( toggleButton.getAttribute( 'aria-expanded' ) ).toEqual( 'true' );
} );
test( 'toggles expanded sections', () => {
const toc = mount( { 'vector-is-collapse-sections-enabled': true } );
toc.toggleExpandSection( 'toc-foo' );
expect( fooSection.classList.contains( toc.EXPANDED_SECTION_CLASS ) ).toEqual( true );
test( 'when toggling sections', () => {
const toc = mount();
const toggleButton = /** @type {HTMLElement} */ ( barSection.querySelector( `.${toc.TOGGLE_CLASS}` ) );
toc.toggleExpandSection( 'toc-foo' );
expect( fooSection.classList.contains( toc.EXPANDED_SECTION_CLASS ) ).toEqual( false );
toc.toggleExpandSection( 'toc-bar' );
expect( toggleButton.getAttribute( 'aria-expanded' ) ).toEqual( 'false' );
toc.toggleExpandSection( 'toc-bar' );
expect( toggleButton.getAttribute( 'aria-expanded' ) ).toEqual( 'true' );
} );
} );
} );

View File

@ -49,7 +49,42 @@ class SkinVectorTest extends MediaWikiIntegrationTestCase {
}
public function provideGetTocData() {
$config = [
'VectorTableOfContentsBeginning' => true,
'VectorTableOfContentsCollapseAtCount' => 1
];
$tocData = [
'number-section-count' => 2,
'array-sections' => [
[
'toclevel' => 1,
'level' => '2',
'line' => 'A',
'number' => '1',
'index' => '1',
'fromtitle' => 'Test',
'byteoffset' => 231,
'anchor' => 'A',
'array-sections' => [],
'is-top-level-section' => true,
'is-parent-section' => false,
],
[
'toclevel' => 1,
'level' => '4',
'line' => 'B',
'number' => '2',
'index' => '2',
'fromtitle' => 'Test',
'byteoffset' => 245,
'anchor' => 'B',
'array-sections' => [],
'is-top-level-section' => true,
'is-parent-section' => false,
]
]
];
$nestedTocData = [
'number-section-count' => 2,
'array-sections' => [
[
@ -62,89 +97,104 @@ class SkinVectorTest extends MediaWikiIntegrationTestCase {
'byteoffset' => 231,
'anchor' => 'A',
'array-sections' => [
[
'toclevel' => 2,
'level' => '4',
'line' => 'A1',
'number' => '1.1',
'index' => '2',
'fromtitle' => 'Test',
'byteoffset' => 245,
'anchor' => 'A1'
]
]
'toclevel' => 2,
'level' => '4',
'line' => 'A1',
'number' => '1.1',
'index' => '2',
'fromtitle' => 'Test',
'byteoffset' => 245,
'anchor' => 'A1',
'array-sections' => [],
'is-top-level-section' => false,
'is-parent-section' => false,
],
'is-top-level-section' => true,
'is-parent-section' => true,
],
]
];
$expectedConfigData = [
'is-vector-toc-beginning-enabled' => $config[ 'VectorTableOfContentsBeginning' ],
'vector-is-collapse-sections-enabled' =>
$tocData[ 'number-section-count' ] >= $config[ 'VectorTableOfContentsCollapseAtCount' ]
];
$expectedNestedTocData = array_merge( $nestedTocData, $expectedConfigData );
$context = RequestContext::getMain();
$buttonLabel = $context->msg( 'vector-toc-toggle-button-label',
$expectedNestedTocData[ 'array-sections' ][ 0 ][ 'line' ]
)->text();
$expectedNestedTocData[ 'array-sections' ][ 0 ][ 'vector-button-label' ] = $buttonLabel;
return [
// When zero sections
[
// $tocData
[],
// wgVectorTableOfContentsCollapseAtCount
1,
// expected 'vector-is-collapse-sections-enabled' value
false
$config,
// TOC data is empty when given an empty array
[]
],
// When number of multiple sections is lower than configured value
[
// $tocData
$tocData,
// wgVectorTableOfContentsCollapseAtCount
3,
// expected 'vector-is-collapse-sections-enabled' value
false
array_merge( $config, [ 'VectorTableOfContentsCollapseAtCount' => 3 ] ),
// 'vector-is-collapse-sections-enabled' value is false
array_merge( $tocData, $expectedConfigData, [
'vector-is-collapse-sections-enabled' => false
] )
],
// When number of multiple sections is equal to the configured value
[
// $tocData
$tocData,
// wgVectorTableOfContentsCollapseAtCount
2,
// expected 'vector-is-collapse-sections-enabled' value
true
array_merge( $config, [ 'VectorTableOfContentsCollapseAtCount' => 2 ] ),
// 'vector-is-collapse-sections-enabled' value is true
array_merge( $tocData, $expectedConfigData )
],
// When number of multiple sections is higher than configured value
[
// $tocData
$tocData,
// wgVectorTableOfContentsCollapseAtCount
1,
// expected 'vector-is-collapse-sections-enabled' value
true
array_merge( $config, [ 'VectorTableOfContentsCollapseAtCount' => 1 ] ),
// 'vector-is-collapse-sections-enabled' value is true
array_merge( $tocData, $expectedConfigData )
],
// When "Beginning" TOC section is configured to be turned off
[
$tocData,
array_merge( $config, [ 'VectorTableOfContentsBeginning' => false ] ),
// 'is-vector-toc-beginning-enabled' value is false
array_merge( $tocData, $expectedConfigData, [
'is-vector-toc-beginning-enabled' => false
] )
],
// When TOC has sections with top level parent sections
[
$nestedTocData,
$config,
// 'vector-button-label' is provided for top level parent sections
$expectedNestedTocData
],
];
}
/**
* @covers \Vector\SkinVector::getTocData
* @covers \Vector\SkinVector22::getTocData
* @dataProvider provideGetTOCData
*/
public function testGetTocData(
array $tocData,
int $configValue,
bool $expected
array $config,
array $expected
) {
$this->setMwGlobals( [
'wgVectorTableOfContentsCollapseAtCount' => $configValue
'wgVectorTableOfContentsCollapseAtCount' => $config['VectorTableOfContentsCollapseAtCount'],
'wgVectorTableOfContentsBeginning' => $config['VectorTableOfContentsBeginning'],
] );
$skinVector = new SkinVectorLegacy( [ 'name' => 'vector-2022' ] );
$skinVector = new SkinVector22( [ 'name' => 'vector-2022' ] );
$openSkinVector = TestingAccessWrapper::newFromObject( $skinVector );
$data = $openSkinVector->getTocData( $tocData );
if ( empty( $tocData ) ) {
$this->assertEquals( [], $data, 'toc data is empty when given an empty array' );
return;
}
$this->assertArrayHasKey( 'vector-is-collapse-sections-enabled', $data );
$this->assertEquals(
$expected,
$data['vector-is-collapse-sections-enabled'],
'vector-is-collapse-sections-enabled has correct value'
);
$this->assertArrayHasKey( 'array-sections', $data );
$this->assertEquals( $expected, $data );
}
/**