From 82f6d03850a011b5ff66eb5f2162f7df34678e53 Mon Sep 17 00:00:00 2001 From: Thomas Roberts <5656702+opr@users.noreply.github.com> Date: Tue, 30 May 2023 20:36:47 +0300 Subject: [PATCH] [`@wordpress/notices`] Add `removeAllNotices` action to allow all notices to be removed from a given context (#44059) * Add removeAllNotices action with example and docs * Add actions tests for removeAllNotices * Add reducer case to handle REMOVE_ALL_NOTICES action type * Add tests for REMOVE_ALL_NOTICES action type * Add code fence around action example * Add noticeType to action args and tests * Change reducer and tests to account for notice type * Add changelog entry * Fix lint error --- .../data/data-core-notices.md | 47 ++++++++++ packages/notices/CHANGELOG.md | 1 + packages/notices/src/store/actions.js | 57 ++++++++++++ packages/notices/src/store/reducer.js | 3 + packages/notices/src/store/test/actions.js | 29 +++++++ packages/notices/src/store/test/reducer.js | 87 ++++++++++++++++++- 6 files changed, 223 insertions(+), 1 deletion(-) diff --git a/docs/reference-guides/data/data-core-notices.md b/docs/reference-guides/data/data-core-notices.md index 18fcf37ad224b9..e11e6f226169f9 100644 --- a/docs/reference-guides/data/data-core-notices.md +++ b/docs/reference-guides/data/data-core-notices.md @@ -261,6 +261,53 @@ _Returns_ - `Object`: Action object. +### removeAllNotices + +Removes all notices from a given context. Defaults to the default context. + +_Usage_ + +```js +import { __ } from '@wordpress/i18n'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { store as noticesStore } from '@wordpress/notices'; +import { Button } from '@wordpress/components'; + +export const ExampleComponent = () => { + const notices = useSelect( ( select ) => + select( noticesStore ).getNotices() + ); + const { removeNotices } = useDispatch( noticesStore ); + return ( + <> + + + + + ); +}; +``` + +_Parameters_ + +- _noticeType_ `string`: The context to remove all notices from. +- _context_ `string`: The context to remove all notices from. + +_Returns_ + +- `Object`: Action object. + ### removeNotice Returns an action object used in signalling that a notice is to be removed. diff --git a/packages/notices/CHANGELOG.md b/packages/notices/CHANGELOG.md index 770b0b532e662c..08632b57fd7244 100644 --- a/packages/notices/CHANGELOG.md +++ b/packages/notices/CHANGELOG.md @@ -5,6 +5,7 @@ ### New Feature - Add a new action `removeNotices` which allows bulk removal of notices by their IDs. ([#39940](https://github.com/WordPress/gutenberg/pull/39940)) +- Add a new action `removeAllNotices` which removes all notices from a given context. ([#44059](https://github.com/WordPress/gutenberg/pull/44059)) ## 4.2.0 (2023-05-24) diff --git a/packages/notices/src/store/actions.js b/packages/notices/src/store/actions.js index 0ddcc769c51467..6ffa7aede2a884 100644 --- a/packages/notices/src/store/actions.js +++ b/packages/notices/src/store/actions.js @@ -314,6 +314,63 @@ export function removeNotice( id, context = DEFAULT_CONTEXT ) { }; } +/** + * Removes all notices from a given context. Defaults to the default context. + * + * @param {string} noticeType The context to remove all notices from. + * @param {string} context The context to remove all notices from. + * + * @example + * ```js + * import { __ } from '@wordpress/i18n'; + * import { useDispatch, useSelect } from '@wordpress/data'; + * import { store as noticesStore } from '@wordpress/notices'; + * import { Button } from '@wordpress/components'; + * + * export const ExampleComponent = () => { + * const notices = useSelect( ( select ) => + * select( noticesStore ).getNotices() + * ); + * const { removeNotices } = useDispatch( noticesStore ); + * return ( + * <> + * + * + * + * + * ); + * }; + * ``` + * + * @return {Object} Action object. + */ +export function removeAllNotices( + noticeType = 'default', + context = DEFAULT_CONTEXT +) { + return { + type: 'REMOVE_ALL_NOTICES', + noticeType, + context, + }; +} + /** * Returns an action object used in signalling that several notices are to be removed. * diff --git a/packages/notices/src/store/reducer.js b/packages/notices/src/store/reducer.js index 6fed169af59869..5f4d88c04af299 100644 --- a/packages/notices/src/store/reducer.js +++ b/packages/notices/src/store/reducer.js @@ -26,6 +26,9 @@ const notices = onSubKey( 'context' )( ( state = [], action ) => { case 'REMOVE_NOTICES': return state.filter( ( { id } ) => ! action.ids.includes( id ) ); + + case 'REMOVE_ALL_NOTICES': + return state.filter( ( { type } ) => type !== action.noticeType ); } return state; diff --git a/packages/notices/src/store/test/actions.js b/packages/notices/src/store/test/actions.js index ed355c045e1b54..37fefc5c3558f4 100644 --- a/packages/notices/src/store/test/actions.js +++ b/packages/notices/src/store/test/actions.js @@ -8,6 +8,7 @@ import { createErrorNotice, createWarningNotice, removeNotice, + removeAllNotices, removeNotices, } from '../actions'; import { DEFAULT_CONTEXT, DEFAULT_STATUS } from '../constants'; @@ -239,4 +240,32 @@ describe( 'actions', () => { } ); } ); } ); + + describe( 'removeAllNotices', () => { + it( 'should return action', () => { + expect( removeAllNotices() ).toEqual( { + type: 'REMOVE_ALL_NOTICES', + noticeType: 'default', + context: DEFAULT_CONTEXT, + } ); + } ); + + it( 'should return action with custom context', () => { + const context = 'foo'; + + expect( removeAllNotices( 'default', context ) ).toEqual( { + type: 'REMOVE_ALL_NOTICES', + noticeType: 'default', + context, + } ); + } ); + + it( 'should return action with type', () => { + expect( removeAllNotices( 'snackbar' ) ).toEqual( { + type: 'REMOVE_ALL_NOTICES', + noticeType: 'snackbar', + context: DEFAULT_CONTEXT, + } ); + } ); + } ); } ); diff --git a/packages/notices/src/store/test/reducer.js b/packages/notices/src/store/test/reducer.js index fe0e33b478bd63..d807b814a51f47 100644 --- a/packages/notices/src/store/test/reducer.js +++ b/packages/notices/src/store/test/reducer.js @@ -7,7 +7,12 @@ import deepFreeze from 'deep-freeze'; * Internal dependencies */ import reducer from '../reducer'; -import { createNotice, removeNotice, removeNotices } from '../actions'; +import { + createNotice, + removeNotice, + removeNotices, + removeAllNotices, +} from '../actions'; import { getNotices } from '../selectors'; import { DEFAULT_CONTEXT } from '../constants'; @@ -208,4 +213,84 @@ describe( 'reducer', () => { ], } ); } ); + + it( 'should remove all notices', () => { + let action = createNotice( 'error', 'save error' ); + const original = deepFreeze( reducer( undefined, action ) ); + + action = createNotice( 'success', 'successfully saved' ); + let state = reducer( original, action ); + state = reducer( state, removeAllNotices() ); + + expect( state ).toEqual( { + [ DEFAULT_CONTEXT ]: [], + } ); + } ); + + it( 'should remove all notices in a given context but leave other contexts intact', () => { + let action = createNotice( 'error', 'save error', { + context: 'foo', + id: 'foo-error', + } ); + const original = deepFreeze( reducer( undefined, action ) ); + + action = createNotice( 'success', 'successfully saved', { + context: 'bar', + } ); + + let state = reducer( original, action ); + state = reducer( state, removeAllNotices( 'default', 'bar' ) ); + + expect( state ).toEqual( { + bar: [], + foo: [ + { + id: 'foo-error', + content: 'save error', + spokenMessage: 'save error', + __unstableHTML: undefined, + status: 'error', + isDismissible: true, + actions: [], + type: 'default', + icon: null, + explicitDismiss: false, + onDismiss: undefined, + }, + ], + } ); + } ); + + it( 'should remove all notices of a given type', () => { + let action = createNotice( 'error', 'save error', { + id: 'global-error', + } ); + const original = deepFreeze( reducer( undefined, action ) ); + + action = createNotice( 'success', 'successfully saved', { + type: 'snackbar', + id: 'snackbar-success', + } ); + + let state = reducer( original, action ); + state = reducer( state, removeAllNotices( 'default' ) ); + + expect( state ).toEqual( { + [ DEFAULT_CONTEXT ]: [ + { + id: 'snackbar-success', + content: 'successfully saved', + spokenMessage: 'successfully saved', + __unstableHTML: undefined, + status: 'success', + isDismissible: true, + actions: [], + type: 'snackbar', + icon: null, + explicitDismiss: false, + onDismiss: undefined, + }, + ], + } ); + } ); } );