Skip to content

Commit

Permalink
[@wordpress/notices] Add removeAllNotices action to allow all not…
Browse files Browse the repository at this point in the history
…ices 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
  • Loading branch information
opr authored May 30, 2023
1 parent 660b34c commit 82f6d03
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 1 deletion.
47 changes: 47 additions & 0 deletions docs/reference-guides/data/data-core-notices.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<>
<ul>
{ notices.map( ( notice ) => (
<li key={ notice.id }>{ notice.content }</li>
) ) }
</ul>
<Button onClick={ () => removeAllNotices() }>
{ __( 'Clear all notices', 'woo-gutenberg-products-block' ) }
</Button>
<Button onClick={ () => removeAllNotices( 'snackbar' ) }>
{ __(
'Clear all snackbar notices',
'woo-gutenberg-products-block'
) }
</Button>
</>
);
};
```

_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.
Expand Down
1 change: 1 addition & 0 deletions packages/notices/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
57 changes: 57 additions & 0 deletions packages/notices/src/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
* <>
* <ul>
* { notices.map( ( notice ) => (
* <li key={ notice.id }>{ notice.content }</li>
* ) ) }
* </ul>
* <Button
* onClick={ () =>
* removeAllNotices()
* }
* >
* { __( 'Clear all notices', 'woo-gutenberg-products-block' ) }
* </Button>
* <Button
* onClick={ () =>
* removeAllNotices( 'snackbar' )
* }
* >
* { __( 'Clear all snackbar notices', 'woo-gutenberg-products-block' ) }
* </Button>
* </>
* );
* };
* ```
*
* @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.
*
Expand Down
3 changes: 3 additions & 0 deletions packages/notices/src/store/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
29 changes: 29 additions & 0 deletions packages/notices/src/store/test/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
createErrorNotice,
createWarningNotice,
removeNotice,
removeAllNotices,
removeNotices,
} from '../actions';
import { DEFAULT_CONTEXT, DEFAULT_STATUS } from '../constants';
Expand Down Expand Up @@ -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,
} );
} );
} );
} );
87 changes: 86 additions & 1 deletion packages/notices/src/store/test/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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,
},
],
} );
} );
} );

0 comments on commit 82f6d03

Please sign in to comment.