Add Jest tests for TOC before/after edit

* Also increase code coverage threshold

Bug: T316571
Change-Id: I6eb703bd0d23ff9d038c989562d07ec01cb7b1c8
This commit is contained in:
Nicholas Ray 2022-09-13 21:17:27 -06:00
parent 8f097163c2
commit 8295148ae2
3 changed files with 265 additions and 46 deletions

View File

@ -29,10 +29,10 @@ module.exports = {
// An object that configures minimum threshold enforcement for coverage results
coverageThreshold: {
global: {
branches: 14,
functions: 24,
lines: 22,
statements: 22
branches: 25,
functions: 29,
lines: 32,
statements: 33
}
},

View File

@ -1,5 +1,82 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Table of contents re-rendering re-renders toc when wikipage.tableOfContents hook is fired with empty sections 1`] = `
"<nav id=\\"mw-panel-toc\\" class=\\"sidebar-toc\\" role=\\"navigation\\" aria-labelledby=\\"sidebar-toc-label\\" data-event-name=\\"ui.sidebar-toc\\"></nav>
"
`;
exports[`Table of contents re-rendering re-renders toc when wikipage.tableOfContents hook is fired with sections 1`] = `
"<nav id=\\"mw-panel-toc\\" class=\\"sidebar-toc\\" role=\\"navigation\\" aria-labelledby=\\"sidebar-toc-label\\" data-event-name=\\"ui.sidebar-toc\\"><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\\">
<p class=\\"sidebar-toc-title\\">
Contents
<button class=\\"vector-toc-uncollapse-button\\">move to sidebar</button>
<button class=\\"vector-toc-collapse-button\\">hide</button>
</p>
</div>
<ul class=\\"sidebar-toc-contents\\" id=\\"mw-panel-toc-list\\">
<li id=\\"toc-mw-content-text\\" class=\\"sidebar-toc-list-item sidebar-toc-level-1\\">
<a href=\\"#top-page\\" class=\\"sidebar-toc-link\\">
<div class=\\"sidebar-toc-text\\">Beginning</div>
</a>
</li>
<li id=\\"toc-foo\\" class=\\"sidebar-toc-list-item sidebar-toc-level-1 sidebar-toc-list-item-expanded\\">
<a class=\\"sidebar-toc-link\\" href=\\"#foo\\">
<div class=\\"sidebar-toc-text\\">
<span class=\\"sidebar-toc-numb\\">1</span>foo</div>
</a>
<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\\">
<a class=\\"sidebar-toc-link\\" href=\\"#bar\\">
<div class=\\"sidebar-toc-text\\">
<span class=\\"sidebar-toc-numb\\">2</span>bar</div>
</a>
<button aria-controls=\\"toc-bar-sublist\\" class=\\"mw-ui-icon mw-ui-icon-wikimedia-expand 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 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 id=\\"toc-qux-sublist\\" class=\\"sidebar-toc-list\\">
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li id=\\"toc-quux\\" class=\\"sidebar-toc-list-item sidebar-toc-level-1 sidebar-toc-list-item-expanded\\">
<a class=\\"sidebar-toc-link\\" href=\\"#quux\\">
<div class=\\"sidebar-toc-text\\">
<span class=\\"sidebar-toc-numb\\">3</span>quux</div>
</a>
<ul id=\\"toc-quux-sublist\\" class=\\"sidebar-toc-list\\">
</ul>
</li>
<li id=\\"toc-bat\\" class=\\"sidebar-toc-list-item sidebar-toc-level-1 sidebar-toc-list-item-expanded\\">
<a class=\\"sidebar-toc-link\\" href=\\"#bat\\">
<div class=\\"sidebar-toc-text\\">
<span class=\\"sidebar-toc-numb\\">4</span>bat</div>
</a>
<ul id=\\"toc-bat-sublist\\" class=\\"sidebar-toc-list\\">
</ul>
</li>
</ul>
</nav>
</nav>
"
`;
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\\">

View File

@ -13,6 +13,51 @@ const onHeadingClick = jest.fn();
const onToggleClick = jest.fn();
const onToggleCollapse = jest.fn();
const SECTIONS = [
{
toclevel: 1,
number: '1',
line: 'foo',
anchor: 'foo',
'is-top-level-section': true,
'is-parent-section': false,
'array-sections': null
}, {
toclevel: 1,
number: '2',
line: 'bar',
anchor: 'bar',
'is-top-level-section': true,
'is-parent-section': true,
'vector-button-label': 'Toggle bar subsection',
'array-sections': [ {
toclevel: 2,
number: '2.1',
line: 'baz',
anchor: 'baz',
'is-top-level-section': false,
'is-parent-section': true,
'array-sections': [ {
toclevel: 3,
number: '2.1.1',
line: 'qux',
anchor: 'qux',
'is-top-level-section': false,
'is-parent-section': false,
'array-sections': null
} ]
} ]
}, {
toclevel: 1,
number: '3',
line: 'quux',
anchor: 'quux',
'is-top-level-section': true,
'is-parent-section': false,
'array-sections': null
}
];
/**
* @param {Object} templateProps
* @return {string}
@ -23,48 +68,7 @@ function render( templateProps = {} ) {
'msg-vector-toc-beginning': 'Beginning',
'msg-vector-toc-heading': 'Contents',
'vector-is-collapse-sections-enabled': false,
'array-sections': [ {
toclevel: 1,
number: '1',
line: 'foo',
anchor: 'foo',
'is-top-level-section': true,
'is-parent-section': false,
'array-sections': null
}, {
toclevel: 1,
number: '2',
line: 'bar',
anchor: 'bar',
'is-top-level-section': true,
'is-parent-section': true,
'vector-button-label': 'Toggle bar subsection',
'array-sections': [ {
toclevel: 2,
number: '2.1',
line: 'baz',
anchor: 'baz',
'is-top-level-section': false,
'is-parent-section': true,
'array-sections': [ {
toclevel: 3,
number: '2.1.1',
line: 'qux',
anchor: 'qux',
'is-top-level-section': false,
'is-parent-section': false,
'array-sections': null
} ]
} ]
}, {
toclevel: 1,
number: '3',
line: 'quux',
anchor: 'quux',
'is-top-level-section': true,
'is-parent-section': false,
'array-sections': null
} ]
'array-sections': SECTIONS
}, templateProps );
return mustache.render( tableOfContentsTemplate, templateData, {
@ -215,4 +219,142 @@ describe( 'Table of contents', () => {
expect( toggleButton.getAttribute( 'aria-expanded' ) ).toEqual( 'true' );
} );
} );
describe( 're-rendering', () => {
const mockMwHook = () => {
/** @type {Function} */
let callback;
// @ts-ignore
jest.spyOn( mw, 'hook' ).mockImplementation( () => {
return {
add: function ( fn ) {
callback = fn;
return this;
},
fire: ( data ) => {
if ( callback ) {
callback( data );
}
}
};
} );
};
afterEach( () => {
jest.restoreAllMocks();
} );
test( 'listens to wikipage.tableOfContents hook when mounted', () => {
const spy = jest.spyOn( mw, 'hook' );
mount();
expect( spy ).toHaveBeenCalledWith( 'wikipage.tableOfContents' );
} );
test( 're-renders toc when wikipage.tableOfContents hook is fired with empty sections', () => {
mockMwHook();
mount();
mw.hook( 'wikipage.tableOfContents' ).fire( [] );
expect( document.body.innerHTML ).toMatchSnapshot();
} );
test( 're-renders toc when wikipage.tableOfContents hook is fired with sections', async () => {
mockMwHook();
// @ts-ignore
// eslint-disable-next-line compat/compat
jest.spyOn( mw.loader, 'using' ).mockImplementation( () => Promise.resolve() );
// @ts-ignore
mw.template.getCompiler = () => {};
jest.spyOn( mw, 'message' ).mockImplementation( ( msg ) => {
const msgFactory = ( /** @type {string} */ text ) => {
return {
parse: () => '',
plain: () => '',
escaped: () => '',
exists: () => true,
text: () => text
};
};
switch ( msg ) {
case 'vector-toc-heading':
return msgFactory( 'Contents' );
case 'vector-toc-toggle-position-sidebar':
return msgFactory( 'move to sidebar' );
case 'vector-toc-toggle-position-title':
return msgFactory( 'hide' );
case 'vector-toc-beginning':
return msgFactory( 'Beginning' );
default:
return msgFactory( '' );
}
} );
// @ts-ignore
jest.spyOn( mw.template, 'getCompiler' ).mockImplementation( () => {
return {
compile: () => {
return {
render: ( /** @type {Object} */ data ) => {
return {
html: () => {
return render( data );
}
};
}
};
}
};
} );
const toc = mount();
const toggleButton = /** @type {HTMLElement} */ ( barSection.querySelector( `.${toc.TOGGLE_CLASS}` ) );
// Collapse section.
toc.toggleExpandSection( 'toc-bar' );
expect( toggleButton.getAttribute( 'aria-expanded' ) ).toEqual( 'false' );
// wikipage.tableOfContents includes the nested sections in the top level
// of the array.
mw.hook( 'wikipage.tableOfContents' ).fire( [
// foo
SECTIONS[ 0 ],
// bar
SECTIONS[ 1 ],
// baz
// @ts-ignore
SECTIONS[ 1 ][ 'array-sections' ][ 0 ],
// qux
// @ts-ignore
SECTIONS[ 1 ][ 'array-sections' ][ 0 ][ 'array-sections' ][ 0 ],
// quux
SECTIONS[ 2 ],
// Add new section to see how the re-render performs.
{
toclevel: 1,
number: '4',
line: 'bat',
anchor: 'bat',
'is-top-level-section': true,
'is-parent-section': false,
'array-sections': null
}
] );
// Wait until the mw.loader.using promise has resolved so that we can
// check the DOM after it has been updated.
// eslint-disable-next-line compat/compat
await Promise.resolve();
const newToggleButton = /** @type {HTMLElement} */ ( document.querySelector( `#toc-bar .${toc.TOGGLE_CLASS}` ) );
expect( newToggleButton ).not.toBeNull();
// Check that the sections render in their expanded form.
expect( newToggleButton.getAttribute( 'aria-expanded' ) ).toEqual( 'true' );
// Verify newly rendered TOC html matches the expected html.
expect( document.body.innerHTML ).toMatchSnapshot();
} );
} );
} );