SearchPreview: Code improvement - Create a DOM modules to centralise all DOM manipulations
- Move all DOM actions into its own module - Update unit tests Bug: T339353 Change-Id: I2c23f4c7efb567b02dd75990572831fc04619c1a
这个提交包含在:
父节点
e69a3c10d8
当前提交
f1d554d5af
|
@ -58,8 +58,7 @@ module.exports = exports = {
|
|||
'loading'
|
||||
] ),
|
||||
mapState( useDomStore, [
|
||||
'firstFocusableElement',
|
||||
'lastFocusableElement'
|
||||
'searchResults'
|
||||
] )
|
||||
),
|
||||
methods: $.extend( {},
|
||||
|
@ -71,7 +70,9 @@ module.exports = exports = {
|
|||
mapActions( useDomStore, [
|
||||
'focusDialog',
|
||||
'handleTabTrap',
|
||||
'updateTabbableElements'
|
||||
'updateTabbableElements',
|
||||
'focusCurrentResult',
|
||||
'generateAndInsertAriaButton'
|
||||
] ),
|
||||
mapActions( useEventStore, [ 'initEventLoggingSession' ] ),
|
||||
{
|
||||
|
@ -83,34 +84,15 @@ module.exports = exports = {
|
|||
},
|
||||
restoreQuickViewOnNavigation() {
|
||||
if ( this.queryQuickViewTitle ) {
|
||||
const currentElement = this.currentElement( this.queryQuickViewTitle );
|
||||
this.toggleVisibily( {
|
||||
title: this.queryQuickViewTitle,
|
||||
element: currentElement
|
||||
} );
|
||||
this.toggleVisibily( this.queryQuickViewTitle );
|
||||
}
|
||||
},
|
||||
getSearchResults() {
|
||||
// eslint-disable-next-line no-jquery/no-global-selector
|
||||
return $( '#mw-content-text .mw-search-result-ns-0' )
|
||||
.not( '#mw-content-text .mw-search-interwiki-results .mw-search-result-ns-0' );
|
||||
},
|
||||
currentElement: function ( title ) {
|
||||
return this.getSearchResults().find( `[data-prefixedtext="${title}"]` ).closest( 'li' )[ 0 ];
|
||||
},
|
||||
leaving() {
|
||||
// Emit QuickView closing event only if QuickView is present in url
|
||||
if ( this.queryQuickViewTitle ) {
|
||||
this.onPageClose();
|
||||
}
|
||||
},
|
||||
generateAndInsertAriaButton( container ) {
|
||||
const ariaButton = document.createElement( 'BUTTON' );
|
||||
ariaButton.type = 'button';
|
||||
ariaButton.classList.add( 'quickView-aria-button' );
|
||||
ariaButton.ariaLabel = this.$i18n( 'searchvue-aria-button' ).text();
|
||||
container.insertBefore( ariaButton, null );
|
||||
},
|
||||
closeAndFocus( event ) {
|
||||
if ( !this.title ) {
|
||||
return;
|
||||
|
@ -118,7 +100,7 @@ module.exports = exports = {
|
|||
|
||||
// event.detail will be equal to 0 if triggered by keyboard
|
||||
if ( event.detail === 0 ) {
|
||||
this.currentElement( this.title ).querySelector( '.quickView-aria-button' ).focus();
|
||||
this.focusCurrentResult( this.title );
|
||||
}
|
||||
this.closeQuickView();
|
||||
},
|
||||
|
@ -128,9 +110,7 @@ module.exports = exports = {
|
|||
if ( searchResultLink.hasAttribute( 'data-prefixedtext' ) ) {
|
||||
event.stopPropagation();
|
||||
const resultTitle = searchResultLink.getAttribute( 'data-prefixedtext' );
|
||||
const currentElement = this.currentElement( resultTitle );
|
||||
const payload = { title: resultTitle, element: currentElement };
|
||||
this.toggleVisibily( payload );
|
||||
this.toggleVisibily( resultTitle );
|
||||
}
|
||||
},
|
||||
resultHasInfoToDisplay( prefixedText ) {
|
||||
|
@ -171,11 +151,10 @@ module.exports = exports = {
|
|||
},
|
||||
mounted: function () {
|
||||
|
||||
const searchResults = this.getSearchResults();
|
||||
for ( const searchResultLi of searchResults ) {
|
||||
for ( const searchResultLi of this.searchResults ) {
|
||||
const searchResult = searchResultLi.querySelector( '.mw-search-result-heading' );
|
||||
|
||||
// Search Result will ne undefined if an user would type in the searchbox quickly
|
||||
// Search Result will be undefined if an user would type in the searchbox quickly
|
||||
// before the results are fully mapped. Quite probably triggered by a BOT.
|
||||
if ( !searchResult ) {
|
||||
return;
|
||||
|
@ -188,13 +167,13 @@ module.exports = exports = {
|
|||
searchResultLi.dataset.prefixedtext = prefixedText;
|
||||
|
||||
const searchResultContainer = searchResult.parentElement;
|
||||
this.generateAndInsertAriaButton( searchResultContainer );
|
||||
this.generateAndInsertAriaButton( searchResultContainer, this.$i18n( 'searchvue-aria-button' ).text() );
|
||||
}
|
||||
}
|
||||
|
||||
const searchResultWithQuickView = searchResults.filter( function ( resultIndex ) {
|
||||
return searchResults[ resultIndex ].classList.contains( 'searchresult-with-quickview' );
|
||||
} );
|
||||
const searchResultWithQuickView = this.searchResults.filter( function ( resultIndex ) {
|
||||
return this.searchResults[ resultIndex ].classList.contains( 'searchresult-with-quickview' );
|
||||
}.bind( this ) );
|
||||
// Mouse click
|
||||
searchResultWithQuickView.find( '.searchresult, .mw-search-result-data, .quickView-aria-button' )
|
||||
.click( function ( event ) {
|
||||
|
|
|
@ -32,7 +32,8 @@ const QuickView = require( './sections/QuickView.vue' ),
|
|||
onDocumentScroll = require( '../composables/onDocumentScroll.js' ),
|
||||
onResizeObserver = require( '../composables/onResizeObserver.js' ),
|
||||
onDocumentResize = require( '../composables/onDocumentResize.js' ),
|
||||
useRootStore = require( '../stores/Root.js' );
|
||||
useRootStore = require( '../stores/Root.js' ),
|
||||
useDomStore = require( '../stores/Dom.js' );
|
||||
|
||||
// @vue/component
|
||||
module.exports = exports = {
|
||||
|
@ -43,8 +44,9 @@ module.exports = exports = {
|
|||
'content-skeleton': ContentSkeleton
|
||||
},
|
||||
setup() {
|
||||
const domStore = useDomStore();
|
||||
const { scrollY } = onDocumentScroll();
|
||||
const { elementWidth } = onResizeObserver( document.querySelector( '#bodyContent' ) );
|
||||
const { elementWidth } = onResizeObserver( domStore.pageContainer );
|
||||
const { width } = onDocumentResize();
|
||||
|
||||
return {
|
||||
|
@ -55,8 +57,6 @@ module.exports = exports = {
|
|||
},
|
||||
data: function () {
|
||||
return {
|
||||
pageContainer: document.querySelector( '#bodyContent' ),
|
||||
searchContainer: document.querySelector( '.searchresults' ),
|
||||
pageScrolled: false
|
||||
};
|
||||
},
|
||||
|
@ -140,6 +140,10 @@ module.exports = exports = {
|
|||
mapState( useRootStore, [
|
||||
'breakpoints',
|
||||
'visible'
|
||||
] ),
|
||||
mapState( useDomStore, [
|
||||
'pageContainer',
|
||||
'searchContainer'
|
||||
] )
|
||||
),
|
||||
methods: $.extend(
|
||||
|
|
|
@ -72,17 +72,7 @@ module.exports = exports = {
|
|||
mapState( useRequestStatusStore, [
|
||||
'loading'
|
||||
] )
|
||||
),
|
||||
methods: $.extend( {
|
||||
getSearchResults() {
|
||||
// eslint-disable-next-line no-jquery/no-global-selector
|
||||
return $( '#mw-content-text .mw-search-result-ns-0' )
|
||||
.not( '#mw-content-text .mw-search-interwiki-results .mw-search-result-ns-0' );
|
||||
},
|
||||
currentElement: function ( title ) {
|
||||
return this.getSearchResults().find( `[data-prefixedtext="${title}"]` ).closest( 'li' )[ 0 ];
|
||||
}
|
||||
} )
|
||||
)
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
@ -5,18 +5,32 @@ const Pinia = require( 'pinia' );
|
|||
const useDomStore = Pinia.defineStore( 'dom', {
|
||||
state: () => ( {
|
||||
container: '.mw-search-quick-view',
|
||||
focusableElements: null
|
||||
focusableElements: null,
|
||||
pageContainer: document.querySelector( '#bodyContent' ),
|
||||
searchContainer: document.querySelector( '.searchresults' ),
|
||||
// eslint-disable-next-line no-jquery/no-global-selector
|
||||
searchResults: $( '#mw-content-text .mw-search-result-ns-0' )
|
||||
.not( '#mw-content-text .mw-search-interwiki-results .mw-search-result-ns-0' )
|
||||
} ),
|
||||
getters: {
|
||||
firstFocusableElement() {
|
||||
if ( this.focusableElements && this.focusableElements.length > 0 ) {
|
||||
return this.focusableElements[ 0 ];
|
||||
firstFocusableElement( state ) {
|
||||
if ( state.focusableElements && state.focusableElements.length > 0 ) {
|
||||
return state.focusableElements[ 0 ];
|
||||
}
|
||||
},
|
||||
lastFocusableElement() {
|
||||
if ( this.focusableElements && this.focusableElements.length > 0 ) {
|
||||
return this.focusableElements[ this.focusableElements.length - 1 ];
|
||||
lastFocusableElement( state ) {
|
||||
if ( state.focusableElements && state.focusableElements.length > 0 ) {
|
||||
return state.focusableElements[ state.focusableElements.length - 1 ];
|
||||
}
|
||||
},
|
||||
currentSelectedResults( state ) {
|
||||
return ( title ) => {
|
||||
if ( title ) {
|
||||
// phpcs:disable Squiz.WhiteSpace.OperatorSpacing.NoSpaceBefore,Squiz.WhiteSpace.OperatorSpacing.NoSpaceAfter
|
||||
return state.searchResults.find( `[data-prefixedtext="${title}"]` ).closest( 'li' )[ 0 ];
|
||||
// phpcs:enable Squiz.WhiteSpace.OperatorSpacing.NoSpaceBefore,Squiz.WhiteSpace.OperatorSpacing.NoSpaceAfter
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
|
@ -76,6 +90,59 @@ const useDomStore = Pinia.defineStore( 'dom', {
|
|||
}
|
||||
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Handle the addition and removal of classes to the body and other element to define
|
||||
* when the Search Preview is open. This classes are used to apply specific CSS properties.
|
||||
*
|
||||
* @param {string} title
|
||||
*/
|
||||
handleClassesToggle( title ) {
|
||||
if ( title ) {
|
||||
document.getElementsByTagName( 'body' )[ 0 ].classList.add( 'search-preview-open' );
|
||||
this.currentSelectedResults( title ).classList.add( 'searchresult-with-quickview--open' );
|
||||
} else {
|
||||
document.getElementsByTagName( 'body' )[ 0 ].classList.remove( 'search-preview-open' );
|
||||
const openElement = document.getElementsByClassName( 'searchresult-with-quickview--open' )[ 0 ];
|
||||
if ( openElement ) {
|
||||
openElement.classList.remove( 'searchresult-with-quickview--open' );
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Moves the focus to the main search result element. This action is usually triggered after
|
||||
* the search view container is closed using keyboard navigation.
|
||||
*
|
||||
* @param {string} title
|
||||
*/
|
||||
focusCurrentResult( title ) {
|
||||
this.currentSelectedResults( title ).querySelector( '.quickView-aria-button' ).focus();
|
||||
},
|
||||
/**
|
||||
* handled the modification of the main text snippets shown in the search result page.
|
||||
*
|
||||
* @param {string} title
|
||||
* @param {string} snippet
|
||||
* @param {boolean} isMobile
|
||||
*/
|
||||
updateMainSearchResultSnippets: ( title, snippet, isMobile ) => {
|
||||
if ( !title || !snippet || !isMobile ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selector = '[data-prefixedtext="' + title + '"] .searchresult';
|
||||
|
||||
// Edge case in which the search result has no text within it (empty page)
|
||||
if ( document.querySelector( selector ) ) {
|
||||
document.querySelector( selector ).innerHTML = snippet;
|
||||
}
|
||||
},
|
||||
generateAndInsertAriaButton( container, ariaLabel ) {
|
||||
const ariaButton = document.createElement( 'BUTTON' );
|
||||
ariaButton.type = 'button';
|
||||
ariaButton.classList.add( 'quickView-aria-button' );
|
||||
ariaButton.ariaLabel = ariaLabel;
|
||||
container.insertBefore( ariaButton, null );
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
const Pinia = require( 'pinia' );
|
||||
const useRequestStatusStore = require( './RequestStatus.js' );
|
||||
const useMediaStore = require( './Media.js' );
|
||||
const useDomStore = require( './Dom.js' );
|
||||
|
||||
const restApi = new mw.Rest();
|
||||
|
||||
|
@ -239,26 +240,6 @@ const generateExpandedSnippet = ( page, currentResult, isMobile ) => {
|
|||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* handled the modification of the main text snippets shown in the search result page.
|
||||
*
|
||||
* @param {string} title
|
||||
* @param {string} snippet
|
||||
* @param {boolean} isMobile
|
||||
*/
|
||||
const updateMainSearchResultSnippets = ( title, snippet, isMobile ) => {
|
||||
if ( !title || !snippet || !isMobile ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selector = '[data-prefixedtext="' + title + '"] .searchresult';
|
||||
|
||||
// Edge case in which the search result has no text within it (empty page)
|
||||
if ( document.querySelector( selector ) ) {
|
||||
document.querySelector( selector ).innerHTML = snippet;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Restore the main text snippets to its original value.
|
||||
*
|
||||
|
@ -269,9 +250,10 @@ const restoreMainSearchResultSnippets = ( currentResult, isMobile ) => {
|
|||
if ( !currentResult ) {
|
||||
return;
|
||||
}
|
||||
const domStore = useDomStore();
|
||||
// The original snippet does not have ellipsis at the end of it
|
||||
const snippet = currentResult.text + mw.msg( 'ellipsis' );
|
||||
updateMainSearchResultSnippets( currentResult.prefixedText, snippet, isMobile );
|
||||
domStore.updateMainSearchResultSnippets( currentResult.prefixedText, snippet, isMobile );
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -356,6 +338,7 @@ const useQueryStore = Pinia.defineStore( 'query', {
|
|||
|
||||
const requestStatusStore = useRequestStatusStore();
|
||||
const mediaStore = useMediaStore();
|
||||
const domStore = useDomStore();
|
||||
requestStatusStore.setRequestStatus( {
|
||||
type: 'query',
|
||||
status: requestStatusStore.requestStatuses.inProgress
|
||||
|
@ -384,7 +367,7 @@ const useQueryStore = Pinia.defineStore( 'query', {
|
|||
mediaStore.setMediaInfo( result, isMobile );
|
||||
const sections = getArticleSections( result );
|
||||
const snippetObject = generateExpandedSnippet( result, currentSelectedResult, isMobile );
|
||||
updateMainSearchResultSnippets( title, snippetObject.expandedSnippet, isMobile );
|
||||
domStore.updateMainSearchResultSnippets( title, snippetObject.expandedSnippet, isMobile );
|
||||
const description = getDescription( result, snippetObject.isBeginningOfText, isMobile );
|
||||
|
||||
this.$patch( {
|
||||
|
|
|
@ -4,6 +4,7 @@ const Pinia = require( 'pinia' );
|
|||
const useEventStore = require( './Event.js' );
|
||||
const useQueryStore = require( './Query.js' );
|
||||
const useMediaStore = require( './Media.js' );
|
||||
const useDomStore = require( './Dom.js' );
|
||||
const useRequestStatusStore = require( './RequestStatus.js' );
|
||||
|
||||
/**
|
||||
|
@ -42,26 +43,6 @@ const removeQuickViewFromHistoryState = () => {
|
|||
window.history.pushState( mwUri.query, null, queryString );
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle the addition and removal of classes to the body and other element to define
|
||||
* when the Search Preview is open. This classes are used to apply specific CSS properties.
|
||||
*
|
||||
* @param {boolean} open
|
||||
* @param {Element} currentElement
|
||||
*/
|
||||
const handleClassesToggle = ( open, currentElement ) => {
|
||||
if ( open ) {
|
||||
document.getElementsByTagName( 'body' )[ 0 ].classList.add( 'search-preview-open' );
|
||||
currentElement.classList.add( 'searchresult-with-quickview--open' );
|
||||
} else {
|
||||
document.getElementsByTagName( 'body' )[ 0 ].classList.remove( 'search-preview-open' );
|
||||
const openElement = document.getElementsByClassName( 'searchresult-with-quickview--open' )[ 0 ];
|
||||
if ( openElement ) {
|
||||
openElement.classList.remove( 'searchresult-with-quickview--open' );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const useRootStore = Pinia.defineStore( 'root', {
|
||||
state: () => ( {
|
||||
title: null,
|
||||
|
@ -144,11 +125,9 @@ const useRootStore = Pinia.defineStore( 'root', {
|
|||
/**
|
||||
* Handles the visibility of the Search Preview definiting title.
|
||||
*
|
||||
* @param {Object} payload
|
||||
* @param {string} payload.title
|
||||
* @param {Element} payload.element
|
||||
* @param {string} title
|
||||
*/
|
||||
toggleVisibily( { title, element } ) {
|
||||
toggleVisibily( title ) {
|
||||
let destination = '.searchresults';
|
||||
if ( this.isMobile ) {
|
||||
// phpcs:disable Squiz.WhiteSpace.OperatorSpacing.NoSpaceBefore,Squiz.WhiteSpace.OperatorSpacing.NoSpaceAfter
|
||||
|
@ -158,17 +137,15 @@ const useRootStore = Pinia.defineStore( 'root', {
|
|||
}
|
||||
this.destination = destination;
|
||||
|
||||
this.handleTitleChange( { newTitle: title, element: element } );
|
||||
this.handleTitleChange( title );
|
||||
},
|
||||
/**
|
||||
* Handle the change in title by retrieving the information from server
|
||||
* and managing the visibility of the panel
|
||||
*
|
||||
* @param {Object} payload
|
||||
* @param {?string} payload.newTitle
|
||||
* @param {?Element} payload.element
|
||||
* @param {?string} newTitle
|
||||
*/
|
||||
handleTitleChange( { newTitle: newTitle, element: element } ) {
|
||||
handleTitleChange( newTitle ) {
|
||||
if ( !newTitle ) {
|
||||
return;
|
||||
}
|
||||
|
@ -176,6 +153,7 @@ const useRootStore = Pinia.defineStore( 'root', {
|
|||
const currentTitle = this.title;
|
||||
const eventStore = useEventStore();
|
||||
const queryStore = useQueryStore();
|
||||
const domStore = useDomStore();
|
||||
|
||||
// This invokes on each title change
|
||||
this.closeQuickView();
|
||||
|
@ -192,7 +170,7 @@ const useRootStore = Pinia.defineStore( 'root', {
|
|||
queryStore.retrieveInfoFromQuery( newTitle, selectedTitleIndex, this.results, this.isMobile );
|
||||
this.title = newTitle;
|
||||
pushTitleToHistoryState( newTitle );
|
||||
handleClassesToggle( true, element );
|
||||
domStore.handleClassesToggle( newTitle );
|
||||
|
||||
this.selectedIndex = selectedTitleIndex;
|
||||
|
||||
|
@ -206,6 +184,7 @@ const useRootStore = Pinia.defineStore( 'root', {
|
|||
|
||||
const mediaStore = useMediaStore();
|
||||
const queryStore = useQueryStore();
|
||||
const domStore = useDomStore();
|
||||
const requestStatusStore = useRequestStatusStore();
|
||||
|
||||
if ( this.title !== null ) {
|
||||
|
@ -225,7 +204,7 @@ const useRootStore = Pinia.defineStore( 'root', {
|
|||
mediaStore.abort();
|
||||
queryStore.abort();
|
||||
mediaStore.$reset();
|
||||
handleClassesToggle( false );
|
||||
domStore.handleClassesToggle();
|
||||
},
|
||||
/**
|
||||
* Emit close event when page is closing/refreshing while QuickView is open
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
const mockElement = require( './element.js' );
|
||||
|
||||
document.querySelector = jest.fn().mockImplementation( () => mockElement );
|
||||
document.getElementsByTagName = jest.fn().mockImplementation( () => [ mockElement ] );
|
||||
document.getElementsByClassName = jest.fn().mockImplementation( () => [ mockElement ] );
|
|
@ -1,6 +1,9 @@
|
|||
module.exports = {
|
||||
innerHTML: 'dummyElement',
|
||||
querySelectorAll: jest.fn(),
|
||||
classList: {
|
||||
add: jest.fn(),
|
||||
remove: jest.fn()
|
||||
}
|
||||
},
|
||||
focus: jest.fn()
|
||||
};
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
document.querySelector = jest.fn().mockImplementation( () => {
|
||||
return {
|
||||
innerHTML: '',
|
||||
querySelectorAll: jest.fn()
|
||||
};
|
||||
} );
|
|
@ -1,16 +1,37 @@
|
|||
const useDomStore = require( '../../../resources/stores/Dom.js' ),
|
||||
Pinia = require( 'pinia' );
|
||||
|
||||
require( '../mocks/querySelector.js' );
|
||||
Pinia = require( 'pinia' ),
|
||||
mockElement = require( '../mocks/element.js' );
|
||||
|
||||
require( '../mocks/domSelector.js' );
|
||||
beforeEach( () => {
|
||||
Pinia.setActivePinia( Pinia.createPinia() );
|
||||
} );
|
||||
|
||||
const createMockElement = ( name ) => {
|
||||
const fakeElement = document.createElement( 'div' );
|
||||
fakeElement.textContent = name;
|
||||
fakeElement.focus = jest.fn();
|
||||
|
||||
return fakeElement;
|
||||
};
|
||||
|
||||
describe( 'Dom store', () => {
|
||||
let domStore;
|
||||
beforeEach( () => {
|
||||
domStore = useDomStore();
|
||||
domStore.searchResults = {
|
||||
find: jest.fn().mockReturnValueOnce( {
|
||||
closest: jest.fn().mockReturnValueOnce( [
|
||||
Object.assign(
|
||||
{
|
||||
// We cannot add this directly in the MockElement otherwise it will trigger a circular dependencies
|
||||
querySelector: document.querySelector
|
||||
},
|
||||
mockElement
|
||||
)
|
||||
] )
|
||||
} )
|
||||
};
|
||||
} );
|
||||
describe( 'Getters', () => {
|
||||
describe( 'firstFocusableElement', () => {
|
||||
|
@ -38,6 +59,14 @@ describe( 'Dom store', () => {
|
|||
expect( domStore.lastFocusableElement ).toEqual( 'third' );
|
||||
} );
|
||||
} );
|
||||
describe( 'currentSelectedResults', () => {
|
||||
it( 'Return null if no title is passed', () => {
|
||||
expect( domStore.currentSelectedResults() ).toBeFalsy();
|
||||
} );
|
||||
it( 'Return the result element if a title is passed', () => {
|
||||
expect( domStore.currentSelectedResults( 'dummy' ).innerHTML ).toEqual( 'dummyElement' );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
describe( 'Actions', () => {
|
||||
describe( 'updateTabbableElements', () => {
|
||||
|
@ -75,13 +104,6 @@ describe( 'Dom store', () => {
|
|||
describe( 'handleTabTrap', () => {
|
||||
let mockEvent;
|
||||
beforeEach( () => {
|
||||
const createMockElement = ( name ) => {
|
||||
const fakeElement = document.createElement( 'div' );
|
||||
fakeElement.textContent = name;
|
||||
fakeElement.focus = jest.fn();
|
||||
|
||||
return fakeElement;
|
||||
};
|
||||
domStore.focusableElements = [
|
||||
createMockElement( 'firstElement' ),
|
||||
createMockElement( 'secondElement' )
|
||||
|
@ -125,5 +147,102 @@ describe( 'Dom store', () => {
|
|||
expect( mockEvent.preventDefault ).toHaveBeenCalled();
|
||||
} );
|
||||
} );
|
||||
describe( 'handleClassesToggle', () => {
|
||||
describe( 'When no title is passed', () => {
|
||||
it( 'remove class "search-preview-open" from body', () => {
|
||||
|
||||
domStore.handleClassesToggle();
|
||||
expect( document.getElementsByTagName ).toHaveBeenCalled();
|
||||
expect( mockElement.classList.remove ).toHaveBeenCalledWith( 'search-preview-open' );
|
||||
} );
|
||||
it( 'remove class "searchresult-with-quickview--open" from search result', () => {
|
||||
// We mock the currentSelectedResult getter
|
||||
domStore.currentSelectedResults = mockElement;
|
||||
|
||||
domStore.handleClassesToggle();
|
||||
expect( document.getElementsByClassName ).toHaveBeenCalled();
|
||||
expect( mockElement.classList.remove ).toHaveBeenCalledWith( 'searchresult-with-quickview--open' );
|
||||
|
||||
} );
|
||||
} );
|
||||
describe( 'When title is passed', () => {
|
||||
it( 'add class "search-preview-open" from body', () => {
|
||||
|
||||
domStore.handleClassesToggle( 'dummy' );
|
||||
expect( document.getElementsByTagName ).toHaveBeenCalled();
|
||||
expect( mockElement.classList.add ).toHaveBeenCalledWith( 'search-preview-open' );
|
||||
} );
|
||||
it( 'add class "searchresult-with-quickview--open" from search result', () => {
|
||||
|
||||
domStore.handleClassesToggle( 'dummy' );
|
||||
expect( mockElement.classList.add ).toHaveBeenCalledWith( 'searchresult-with-quickview--open' );
|
||||
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
describe( 'focusCurrentResult', () => {
|
||||
it( 'Calls focus on the current element', () => {
|
||||
domStore.focusCurrentResult( 'dummy' );
|
||||
expect( mockElement.focus ).toHaveBeenCalled();
|
||||
} );
|
||||
} );
|
||||
describe( 'updateMainSearchResultSnippets', () => {
|
||||
beforeEach( () => {
|
||||
|
||||
} );
|
||||
describe( 'does not update dom when the following argument is missing', () => {
|
||||
it( 'title', () => {
|
||||
domStore.updateMainSearchResultSnippets( false, 'dummySnippet', true );
|
||||
expect( document.querySelector ).not.toHaveBeenCalledWith( expect.stringContaining( 'data-prefixedtext' ) );
|
||||
} );
|
||||
it( 'snippet', () => {
|
||||
domStore.updateMainSearchResultSnippets( 'dummyTitle', undefined, true );
|
||||
expect( document.querySelector ).not.toHaveBeenCalledWith( expect.stringContaining( 'data-prefixedtext' ) );
|
||||
} );
|
||||
it( 'isMobile', () => {
|
||||
domStore.updateMainSearchResultSnippets( 'dummyTitle', 'dummySnippet' );
|
||||
expect( document.querySelector ).not.toHaveBeenCalledWith( expect.stringContaining( 'data-prefixedtext' ) );
|
||||
} );
|
||||
} );
|
||||
describe( 'when all paramethers are passed correctly', () => {
|
||||
it( 'update the element with the provided snippet', () => {
|
||||
const dummyElement = createMockElement( 'dummyElement' );
|
||||
document.querySelector.mockReturnValue( dummyElement );
|
||||
|
||||
domStore.updateMainSearchResultSnippets( 'dummyTitle', 'dummySnippet', true );
|
||||
|
||||
expect( document.querySelector ).toHaveBeenCalledWith( expect.stringContaining( 'data-prefixedtext' ) );
|
||||
expect( dummyElement.innerHTML ).toEqual( 'dummySnippet' );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
||||
describe( 'generateAndInsertAriaButton', () => {
|
||||
let container;
|
||||
let insertBefore;
|
||||
beforeEach( () => {
|
||||
insertBefore = jest.fn();
|
||||
container = {
|
||||
insertBefore: insertBefore
|
||||
};
|
||||
} );
|
||||
it( 'Attach a button to the provided container', () => {
|
||||
domStore.generateAndInsertAriaButton( container );
|
||||
|
||||
expect( insertBefore ).toHaveBeenCalled();
|
||||
// we check the first argument that should be the button itself
|
||||
expect( insertBefore.mock.calls[ 0 ][ 0 ] ).toBeTruthy();
|
||||
expect( insertBefore.mock.calls[ 0 ][ 0 ].type ).toBe( 'button' );
|
||||
} );
|
||||
it( 'Set the button aria label', () => {
|
||||
const dummyAriaLabel = 'dummyAriaLabel';
|
||||
domStore.generateAndInsertAriaButton( container, dummyAriaLabel );
|
||||
|
||||
expect( insertBefore ).toHaveBeenCalled();
|
||||
// we check the first argument that should be the button itself
|
||||
expect( insertBefore.mock.calls[ 0 ][ 0 ] ).toBeTruthy();
|
||||
expect( insertBefore.mock.calls[ 0 ][ 0 ].ariaLabel ).toBe( dummyAriaLabel );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -3,7 +3,7 @@ const useStore = require( '../../../resources/stores/Query.js' ),
|
|||
when = require( 'jest-when' ).when,
|
||||
useRequestStatusStore = require( '../../../resources/stores/RequestStatus.js' );
|
||||
|
||||
require( '../mocks/querySelector.js' );
|
||||
require( '../mocks/domSelector.js' );
|
||||
|
||||
/**
|
||||
* Quick little helper function to escape contents for use in regular expressions;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
const useStore = require( '../../../resources/stores/Root.js' ),
|
||||
Pinia = require( 'pinia' ),
|
||||
fakeElement = require( '../mocks/element.js' ),
|
||||
useMediaStore = require( '../../../resources/stores/Media.js' ),
|
||||
useQueryStore = require( '../../../resources/stores/Query.js' ),
|
||||
useEventStore = require( '../../../resources/stores/Event.js' );
|
||||
useEventStore = require( '../../../resources/stores/Event.js' ),
|
||||
useDomStore = require( '../../../resources/stores/Dom.js' );
|
||||
require( '../mocks/history.js' );
|
||||
|
||||
beforeEach( () => {
|
||||
|
@ -15,6 +15,7 @@ describe( 'Root store', () => {
|
|||
let media;
|
||||
let query;
|
||||
let event;
|
||||
let dom;
|
||||
beforeEach( () => {
|
||||
store = useStore();
|
||||
} );
|
||||
|
@ -130,14 +131,14 @@ describe( 'Root store', () => {
|
|||
store.handleTitleChange = jest.fn();
|
||||
} );
|
||||
it( 'Set destination to false, if title is not passed', () => {
|
||||
store.toggleVisibily( {} );
|
||||
store.toggleVisibily();
|
||||
|
||||
expect( store.destination ).toBeFalsy();
|
||||
} );
|
||||
it( 'Set destination to title provided', () => {
|
||||
const title = 'dummy';
|
||||
|
||||
store.toggleVisibily( { title: title } );
|
||||
store.toggleVisibily( title );
|
||||
expect( store.destination ).toContain( title );
|
||||
} );
|
||||
} );
|
||||
|
@ -164,15 +165,17 @@ describe( 'Root store', () => {
|
|||
query = useQueryStore();
|
||||
media = useMediaStore();
|
||||
event = useEventStore();
|
||||
dom = useDomStore();
|
||||
store.results = [
|
||||
{ prefixedText: 'dummy1', thumbnail: { width: 1, height: 2 } },
|
||||
{ prefixedText: 'dummy2' }
|
||||
];
|
||||
store.closeQuickView = jest.fn();
|
||||
dom.handleClassesToggle = jest.fn();
|
||||
} );
|
||||
describe( 'when called with empty title', () => {
|
||||
it( 'Nothing is committed', () => {
|
||||
store.handleTitleChange( {} );
|
||||
store.handleTitleChange();
|
||||
|
||||
expect( store.closeQuickView ).not.toHaveBeenCalled();
|
||||
|
||||
|
@ -183,7 +186,7 @@ describe( 'Root store', () => {
|
|||
const title = 'dummy';
|
||||
store.title = title;
|
||||
store.closeQuickView = jest.fn();
|
||||
store.handleTitleChange( { newTitle: title } );
|
||||
store.handleTitleChange( title );
|
||||
|
||||
expect( store.closeQuickView ).toHaveBeenCalled();
|
||||
|
||||
|
@ -194,7 +197,7 @@ describe( 'Root store', () => {
|
|||
it( 'Setup article thumbnail height and width from the info available in the result', () => {
|
||||
|
||||
const title = 'dummy1';
|
||||
store.handleTitleChange( { newTitle: title, element: fakeElement } );
|
||||
store.handleTitleChange( title );
|
||||
|
||||
expect( query.thumbnail ).toEqual( store.results[ 0 ].thumbnail );
|
||||
|
||||
|
@ -203,21 +206,21 @@ describe( 'Root store', () => {
|
|||
query.retrieveInfoFromQuery = jest.fn();
|
||||
|
||||
const title = 'dummy1';
|
||||
store.handleTitleChange( { newTitle: title, element: fakeElement } );
|
||||
store.handleTitleChange( title );
|
||||
|
||||
expect( query.retrieveInfoFromQuery ).toHaveBeenCalled();
|
||||
|
||||
} );
|
||||
it( 'and current title had no value, update the title', () => {
|
||||
const title = 'dummy1';
|
||||
store.handleTitleChange( { newTitle: title, element: fakeElement } );
|
||||
store.handleTitleChange( title );
|
||||
|
||||
expect( store.title ).toEqual( title );
|
||||
|
||||
} );
|
||||
it( 'and value differs from existing title, update the title', () => {
|
||||
const title = 'dummy1';
|
||||
store.handleTitleChange( { newTitle: title, element: fakeElement } );
|
||||
store.handleTitleChange( title );
|
||||
|
||||
expect( store.title ).toEqual( title );
|
||||
|
||||
|
@ -225,7 +228,7 @@ describe( 'Root store', () => {
|
|||
|
||||
it( 'Adds QuickView to history state', () => {
|
||||
const title = 'dummy1';
|
||||
store.handleTitleChange( { newTitle: title, element: fakeElement } );
|
||||
store.handleTitleChange( title );
|
||||
|
||||
expect( window.history.pushState ).toHaveBeenCalled();
|
||||
expect( window.history.pushState ).toHaveBeenCalledWith(
|
||||
|
@ -243,7 +246,7 @@ describe( 'Root store', () => {
|
|||
const title = 'dummy2';
|
||||
const eventName = 'open-searchpreview';
|
||||
|
||||
store.handleTitleChange( { newTitle: title, element: fakeElement } );
|
||||
store.handleTitleChange( title );
|
||||
expect( event.logQuickViewEvent.mock.calls[ 0 ][ 0 ].action ).toBe( eventName );
|
||||
} );
|
||||
} );
|
||||
|
|
正在加载...
在新工单中引用