mirror of
https://gerrit.wikimedia.org/r/mediawiki/skins/Vector.git
synced 2024-11-15 03:34:25 +00:00
4d1c0b8940
At resolutions below 1000px, we want pinned elements such as the Page Tools menu and Main Menu to collapse. This behaviour is temporary and when the browser is resized, the pinned elements should revert to their previous pinned state. We also want to remove the ability to pin these menus at low resolutions, so the "hide/move" button is hidden. A new matchMedia event handler is added to PinnableElement.js to handle this behaviour. CSS is also added to hide the pinned menus at low resolution. This is to account for the situation where the page is loaded at narrow widths, with pinned elements, and the JS hasn't loaded yet. features.js is refactors so that class toggling can happen independently of saving the state to user preferences (since we want to toggle the classes but not save the state at lower resolutions). Bug: T326364 Change-Id: I3113ab83deb15843e04ed63ec767a85c522517b5
177 lines
7.6 KiB
JavaScript
177 lines
7.6 KiB
JavaScript
jest.mock( '../../resources/skins.vector.es6/features.js' );
|
|
|
|
const features = require( '../../resources/skins.vector.es6/features.js' );
|
|
const mustache = require( 'mustache' );
|
|
const fs = require( 'fs' );
|
|
const pinnableHeaderTemplate = fs.readFileSync( 'includes/templates/PinnableHeader.mustache', 'utf8' );
|
|
const pinnableElement = require( '../../resources/skins.vector.es6/pinnableElement.js' );
|
|
|
|
/**
|
|
* Mock for matchMedia, which is not included in JSDOM.
|
|
* https://jestjs.io/docs/26.x/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
|
|
*/
|
|
Object.defineProperty( window, 'matchMedia', {
|
|
writable: true,
|
|
value: jest.fn().mockImplementation( query => ( {
|
|
matches: false,
|
|
media: query,
|
|
onchange: null,
|
|
addListener: jest.fn(), // deprecated
|
|
removeListener: jest.fn(), // deprecated
|
|
addEventListener: jest.fn(),
|
|
removeEventListener: jest.fn(),
|
|
dispatchEvent: jest.fn()
|
|
} ) )
|
|
} );
|
|
|
|
const simpleData = {
|
|
'is-pinned': false,
|
|
'data-name': 'simple',
|
|
'data-pinnable-element-id': 'pinnable-element',
|
|
label: 'simple pinnable element',
|
|
'label-tag-name': 'div',
|
|
'pin-label': 'pin',
|
|
'unpin-label': 'unpin'
|
|
};
|
|
|
|
const movableData = {
|
|
'is-pinned': false,
|
|
'data-name': 'movable',
|
|
'data-pinnable-element-id': 'pinnable-element',
|
|
'data-pinned-container-id': 'pinned-container',
|
|
'data-unpinned-container-id': 'unpinned-container',
|
|
label: 'moveable pinnable element',
|
|
'label-tag-name': 'div',
|
|
'pin-label': 'pin',
|
|
'unpin-label': 'unpin'
|
|
};
|
|
|
|
// @ts-ignore
|
|
const initializeHTML = ( headerData ) => {
|
|
const pinnableHeaderHTML = mustache.render( pinnableHeaderTemplate, headerData );
|
|
const pinnableElementHTML = `<div id="pinnable-element"> ${ pinnableHeaderHTML } </div>`;
|
|
document.body.innerHTML = `
|
|
<div id="pinned-container">
|
|
${ headerData[ 'is-pinned' ] ? pinnableElementHTML : '' }
|
|
</div>
|
|
<div id="unpinned-container">
|
|
${ !headerData[ 'is-pinned' ] ? pinnableElementHTML : '' }
|
|
</div>
|
|
`;
|
|
|
|
if ( headerData[ 'data-feature-name' ] ) {
|
|
// Return early if the persistent option is enabled as features.js will
|
|
// manage the body classes instead of pinnableElement.
|
|
return;
|
|
}
|
|
|
|
if ( headerData[ 'is-pinned' ] ) {
|
|
document.body.classList.add( `${headerData[ 'data-name' ]}-pinned` );
|
|
document.body.classList.remove( `${headerData[ 'data-name' ]}-unpinned` );
|
|
} else {
|
|
document.body.classList.remove( `${headerData[ 'data-name' ]}-pinned` );
|
|
document.body.classList.add( `${headerData[ 'data-name' ]}-unpinned` );
|
|
}
|
|
};
|
|
|
|
describe( 'Pinnable header', () => {
|
|
test( 'renders', () => {
|
|
initializeHTML( simpleData );
|
|
expect( document.body.innerHTML ).toMatchSnapshot();
|
|
} );
|
|
|
|
test( 'updates pinnable classes when toggle is pressed', () => {
|
|
initializeHTML( simpleData );
|
|
pinnableElement.initPinnableElement();
|
|
const pinButton = /** @type {HTMLElement} */ ( document.querySelector( '.vector-pinnable-header-pin-button' ) );
|
|
const unpinButton = /** @type {HTMLElement} */ ( document.querySelector( '.vector-pinnable-header-unpin-button' ) );
|
|
const header = /** @type {HTMLElement} */ ( document.querySelector( `.${simpleData[ 'data-name' ]}-pinnable-header` ) );
|
|
|
|
expect( header.classList.contains( pinnableElement.PINNED_HEADER_CLASS ) ).toBe( false );
|
|
expect( document.body.classList.contains( `${simpleData[ 'data-name' ]}-pinned` ) ).toBe( false );
|
|
expect( header.classList.contains( pinnableElement.UNPINNED_HEADER_CLASS ) ).toBe( true );
|
|
expect( document.body.classList.contains( `${simpleData[ 'data-name' ]}-unpinned` ) ).toBe( true );
|
|
pinButton.click();
|
|
expect( header.classList.contains( pinnableElement.PINNED_HEADER_CLASS ) ).toBe( true );
|
|
expect( document.body.classList.contains( `${simpleData[ 'data-name' ]}-pinned` ) ).toBe( true );
|
|
expect( header.classList.contains( pinnableElement.UNPINNED_HEADER_CLASS ) ).toBe( false );
|
|
expect( document.body.classList.contains( `${simpleData[ 'data-name' ]}-unpinned` ) ).toBe( false );
|
|
unpinButton.click();
|
|
expect( header.classList.contains( pinnableElement.PINNED_HEADER_CLASS ) ).toBe( false );
|
|
expect( document.body.classList.contains( `${simpleData[ 'data-name' ]}-pinned` ) ).toBe( false );
|
|
expect( header.classList.contains( pinnableElement.UNPINNED_HEADER_CLASS ) ).toBe( true );
|
|
expect( document.body.classList.contains( `${simpleData[ 'data-name' ]}-unpinned` ) ).toBe( true );
|
|
} );
|
|
|
|
test( 'doesnt move pinnable element when data attributes arent defined', () => {
|
|
initializeHTML( simpleData );
|
|
pinnableElement.initPinnableElement();
|
|
const pinButton = /** @type {HTMLElement} */ ( document.querySelector( '.vector-pinnable-header-pin-button' ) );
|
|
const unpinButton = /** @type {HTMLElement} */ ( document.querySelector( '.vector-pinnable-header-unpin-button' ) );
|
|
const pinnableElem = /** @type {HTMLElement} */ ( document.getElementById( simpleData[ 'data-pinnable-element-id' ] ) );
|
|
|
|
/* eslint-disable no-restricted-properties */
|
|
expect( pinnableElem.parentElement && pinnableElem.parentElement.id ).toBe( 'unpinned-container' );
|
|
pinButton.click();
|
|
expect( pinnableElem.parentElement && pinnableElem.parentElement.id ).toBe( 'unpinned-container' );
|
|
unpinButton.click();
|
|
expect( pinnableElem.parentElement && pinnableElem.parentElement.id ).toBe( 'unpinned-container' );
|
|
/* eslint-enable no-restricted-properties */
|
|
} );
|
|
|
|
test( 'moves pinnable element when data attributes are defined', () => {
|
|
initializeHTML( movableData );
|
|
pinnableElement.initPinnableElement();
|
|
const pinButton = /** @type {HTMLElement} */ ( document.querySelector( '.vector-pinnable-header-pin-button' ) );
|
|
const unpinButton = /** @type {HTMLElement} */ ( document.querySelector( '.vector-pinnable-header-unpin-button' ) );
|
|
const pinnableElem = /** @type {HTMLElement} */ ( document.getElementById( movableData[ 'data-pinnable-element-id' ] ) );
|
|
|
|
/* eslint-disable no-restricted-properties */
|
|
expect( pinnableElem.parentElement && pinnableElem.parentElement.id ).toBe( 'unpinned-container' );
|
|
pinButton.click();
|
|
expect( pinnableElem.parentElement && pinnableElem.parentElement.id ).toBe( 'pinned-container' );
|
|
unpinButton.click();
|
|
expect( pinnableElem.parentElement && pinnableElem.parentElement.id ).toBe( 'unpinned-container' );
|
|
/* eslint-enable no-restricted-properties */
|
|
} );
|
|
|
|
test( 'calls features.js when data-feature-name is set', () => {
|
|
initializeHTML( {
|
|
...simpleData,
|
|
'data-name': 'vector-page-tools',
|
|
'data-feature-name': 'page-tools-pinned'
|
|
} );
|
|
pinnableElement.initPinnableElement();
|
|
const pinButton = /** @type {HTMLElement} */ ( document.querySelector( '.vector-pinnable-header-pin-button' ) );
|
|
const unpinButton = /** @type {HTMLElement} */ ( document.querySelector( '.vector-pinnable-header-unpin-button' ) );
|
|
|
|
pinButton.click();
|
|
|
|
expect( features.toggle ).toHaveBeenCalledTimes( 1 );
|
|
expect( features.toggle ).toHaveBeenCalledWith( 'page-tools-pinned' );
|
|
|
|
// @ts-ignore
|
|
features.toggle.mockClear();
|
|
unpinButton.click();
|
|
|
|
expect( features.toggle ).toHaveBeenCalledTimes( 1 );
|
|
expect( features.toggle ).toHaveBeenCalledWith( 'page-tools-pinned' );
|
|
} );
|
|
|
|
test( 'isPinned() returns whether the element is pinned or not', () => {
|
|
initializeHTML( simpleData );
|
|
pinnableElement.initPinnableElement();
|
|
const pinButton = /** @type {HTMLElement} */ ( document.querySelector( '.vector-pinnable-header-pin-button' ) );
|
|
const unpinButton = /** @type {HTMLElement} */ ( document.querySelector( '.vector-pinnable-header-unpin-button' ) );
|
|
const header = /** @type {HTMLElement} */ ( document.querySelector( `.${simpleData[ 'data-name' ]}-pinnable-header` ) );
|
|
|
|
pinButton.click();
|
|
|
|
expect( pinnableElement.isPinned( header ) ).toBe( true );
|
|
|
|
unpinButton.click();
|
|
|
|
expect( pinnableElement.isPinned( header ) ).toBe( false );
|
|
} );
|
|
} );
|