diff --git a/js/src/edit.js b/js/src/edit.js index a660e35a37f..cf803e0f41c 100644 --- a/js/src/edit.js +++ b/js/src/edit.js @@ -18,6 +18,7 @@ import AnalysisSection from "./components/contentAnalysis/AnalysisSection"; import Data from "./analysis/data.js"; import { isGutenbergDataAvailable } from "./helpers/isGutenbergAvailable"; import SnippetPreviewSection from "./components/SnippetPreviewSection"; +import openSidebarSectionsReducer from "./redux/reducers/openSidebarSections"; import cornerstoneContentReducer from "./redux/reducers/cornerstoneContent"; // This should be the entry point for all the edit screens. Because of backwards compatibility we can't change this at once. @@ -53,6 +54,7 @@ function configureStore() { const rootReducer = combineReducers( { marksButtonStatus: markerStatusReducer, keywords: keywordsReducer, + openSidebarSections: openSidebarSectionsReducer, analysis: analysisReducer, activeKeyword: activeKeywordReducer, isCornerstone: cornerstoneContentReducer, diff --git a/js/src/redux/actions/openSidebarSections.js b/js/src/redux/actions/openSidebarSections.js new file mode 100644 index 00000000000..bd9662587d7 --- /dev/null +++ b/js/src/redux/actions/openSidebarSections.js @@ -0,0 +1,44 @@ +const PREFIX = "WPSEO_"; + +export const OPEN_SIDEBAR_SECTION = `${ PREFIX }OPEN_SIDEBAR_SECTION`; +export const CLOSE_SIDEBAR_SECTION = `${ PREFIX }CLOSE_SIDEBAR_SECTION`; +export const CLOSE_ALL_SIDEBAR_SECTIONS = `${ PREFIX }CLOSE_ALL_SIDEBAR_SECTIONS`; + +/** + * An action creator for the action that opens a section. + * + * @param {string} sectionId The to be opened section. + * + * @returns {Object} A (WPSEO_)OPEN_SECTION action. + */ +export const openSidebarSection = function( sectionId ) { + return { + type: OPEN_SIDEBAR_SECTION, + sectionId, + }; +}; + +/** + * An action creator for the action that closes a section. + * + * @param {string} sectionId The to be closed section. + * + * @returns {Object} A (WPSEO_)CLOSE_SECTION action. + */ +export const closeSidebarSection = function( sectionId ) { + return { + type: CLOSE_SIDEBAR_SECTION, + sectionId, + }; +}; + +/** + * An action creator for the action that closes all sections. + * + * @returns {Object} A (WPSEO_)CLOSE_ALL_SECTIONS action. + */ +export const closeAllSidebarSections = function() { + return { + type: CLOSE_ALL_SIDEBAR_SECTIONS, + }; +}; \ No newline at end of file diff --git a/js/src/redux/reducers/openSidebarSections.js b/js/src/redux/reducers/openSidebarSections.js new file mode 100644 index 00000000000..faac8850aa3 --- /dev/null +++ b/js/src/redux/reducers/openSidebarSections.js @@ -0,0 +1,57 @@ +import { CLOSE_ALL_SIDEBAR_SECTIONS, OPEN_SIDEBAR_SECTION, CLOSE_SIDEBAR_SECTION } from "../actions/openSidebarSections"; + +const INITIAL_STATE = []; + +/** + * Helper function: returns an array with the passed item added. + * + * @param {Array} openSidebarSections The array to which the item should be added. + * @param {string} sectionId The item to be added. + * + * @returns {Array} The array including the item that should be added. + */ +const sidebarSectionOpener = ( openSidebarSections, sectionId ) => { + if ( typeof sectionId !== "string" || openSidebarSections.includes( sectionId ) ) { + return openSidebarSections; + } + + return [ + ...openSidebarSections, + sectionId, + ]; +}; + +/** + * Helper function: returns an array with the passed item removed. + * + * @param {Array} openSidebarSections The array from which the item should be removed. + * @param {string} sectionId The item to be removed. + * + * @returns {Array} The array without the item that should be removed. + */ +const sidebarSectionCloser = ( openSidebarSections, sectionId ) => { + return openSidebarSections.filter( item => item !== sectionId ); +}; + +/** + * A reducer for adding and removing sections from openSidebarSections. + * + * @param {Object} state The current state, which in this case is openSidebarSections. + * @param {Object} action The current action received. + * + * @returns {Object} The new state. + */ +function openSidebarSectionsReducer( state = INITIAL_STATE, action ) { + switch( action.type ) { + case OPEN_SIDEBAR_SECTION: + return sidebarSectionOpener( state, action.sectionId ); + case CLOSE_SIDEBAR_SECTION: + return sidebarSectionCloser( state, action.sectionId ); + case CLOSE_ALL_SIDEBAR_SECTIONS: + return []; + default: + return state; + } +} + +export default openSidebarSectionsReducer; diff --git a/js/tests/redux/actions/openSidebarSections.test.js b/js/tests/redux/actions/openSidebarSections.test.js new file mode 100644 index 00000000000..a54a372176c --- /dev/null +++ b/js/tests/redux/actions/openSidebarSections.test.js @@ -0,0 +1,34 @@ +/* global describe, it, expect */ + +import * as actions from "../../../src/redux/actions/openSidebarSections"; + +describe( "openSidebarSections actions", () => { + it( "should pass along the id when opening a section", () => { + const expected = { + type: actions.OPEN_SIDEBAR_SECTION, + sectionId: "sectionOpened", + }; + const actual = actions.openSidebarSection( "sectionOpened" ); + + expect( actual ).toEqual( expected ); + } ); + + it( "should pass along the keyword when closing a section", () => { + const expected = { + type: actions.CLOSE_SIDEBAR_SECTION, + sectionId: "sectionClosed", + }; + const actual = actions.closeSidebarSection( "sectionClosed" ); + + expect( actual ).toEqual( expected ); + } ); + + it( "should pass nothing but the type in the action object when closing all sections", () => { + const expected = { + type: actions.CLOSE_ALL_SIDEBAR_SECTIONS, + }; + const actual = actions.closeAllSidebarSections(); + + expect( actual ).toEqual( expected ); + } ); +} ); diff --git a/js/tests/redux/reducers/openSidebarSections.test.js b/js/tests/redux/reducers/openSidebarSections.test.js new file mode 100644 index 00000000000..94b81c00dad --- /dev/null +++ b/js/tests/redux/reducers/openSidebarSections.test.js @@ -0,0 +1,66 @@ +/* global describe, it, expect */ + +import { openSidebarSection, closeSidebarSection, closeAllSidebarSections } from "../../../src/redux/actions/openSidebarSections"; +import openSidebarSectionsReducer from "../../../src/redux/reducers/openSidebarSections"; + +describe( "openSidebarSections reducers", () => { + describe( "openSidebarSection", () => { + it( "should add the to be opened section to the openSidebarSections array", () => { + const state = []; + const action = openSidebarSection( "openThisSection" ); + const expected = [ "openThisSection" ]; + const actual = openSidebarSectionsReducer( state, action ); + + expect( actual ).toEqual( expected ); + } ); + + it( "should not add a section that is already open to the openSidebarSections array", () => { + const state = [ "alreadyOpen", "alsoOpen" ]; + const action = openSidebarSection( "alreadyOpen" ); + const expected = [ "alreadyOpen", "alsoOpen" ]; + const actual = openSidebarSectionsReducer( state, action ); + + expect( actual ).toEqual( expected ); + } ); + + it( "should not add the section to the array if the passed argument is not a string", () => { + const state = [ "alreadyOpen", "alsoOpen" ]; + const action = openSidebarSection( 1234 ); + const expected = [ "alreadyOpen", "alsoOpen" ]; + const actual = openSidebarSectionsReducer( state, action ); + + expect( actual ).toEqual( expected ); + } ); + } ); + + describe( "closeSidebarSection", () => { + it( "should remove the to be closed section from the openSidebarSections array", () => { + const state = [ "alreadyOpen", "alsoOpen" ]; + const action = closeSidebarSection( "alsoOpen" ); + const expected = [ "alreadyOpen" ]; + const actual = openSidebarSectionsReducer( state, action ); + + expect( actual ).toEqual( expected ); + } ); + + it( "should not change the array when closing a section that was already closed", () => { + const state = [ "alreadyOpen", "alsoOpen" ]; + const action = closeSidebarSection( "fictionalSection" ); + const expected = [ "alreadyOpen", "alsoOpen" ]; + const actual = openSidebarSectionsReducer( state, action ); + + expect( actual ).toEqual( expected ); + } ); + } ); + + describe( "closeAllSidebarSections", () => { + it( "should empty the openSidebarSections array", () => { + const state = [ "alreadyOpen", "alsoOpen" ]; + const action = closeAllSidebarSections(); + const expected = []; + const actual = openSidebarSectionsReducer( state, action ); + + expect( actual ).toEqual( expected ); + } ); + } ); +} );