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 (
+ <>
+
+ { notices.map( ( notice ) => (
+ - { notice.content }
+ ) ) }
+
+
+
+ >
+ );
+};
+```
+
+_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 (
+ * <>
+ *
+ * { notices.map( ( notice ) => (
+ * - { notice.content }
+ * ) ) }
+ *
+ *
+ *
+ * >
+ * );
+ * };
+ * ```
+ *
+ * @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,
+ },
+ ],
+ } );
+ } );
} );