diff --git a/ui/shared/notifications/redux/__tests__/reduxNotifications.test.jsx b/ui/shared/notifications/redux/__tests__/reduxNotifications.test.jsx new file mode 100644 index 00000000000..58b7a23ee84 --- /dev/null +++ b/ui/shared/notifications/redux/__tests__/reduxNotifications.test.jsx @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2024 - present Instructure, Inc. + * + * This file is part of Canvas. + * + * Canvas is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, version 3 of the License. + * + * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License along + * with this program. If not, see . + */ + +import {subscribeFlashNotifications, notificationActions, reduceNotifications} from '../actions' +import * as FlashAlert from '@canvas/alerts/react/FlashAlert' + +jest.mock('@canvas/alerts/react/FlashAlert', () => ({ + showFlashAlert: jest.fn(), + destroyContainer: jest.fn(), +})) + +const createMockStore = state => ({ + subs: [], + subscribe(cb) { + this.subs.push(cb) + }, + getState: () => state, + dispatch: jest.fn(), + mockStateChange() { + this.subs.forEach(sub => sub()) + }, +}) + +describe('Redux Notifications', () => { + afterEach(() => { + FlashAlert.destroyContainer.mockClear() + }) + + test('subscribes to a store and calls showFlashAlert for each notification in state', done => { + const mockStore = createMockStore({ + notifications: [ + {id: '1', message: 'hello'}, + {id: '2', message: 'world'}, + ], + }) + + subscribeFlashNotifications(mockStore) + mockStore.mockStateChange() + + setTimeout(() => { + expect(FlashAlert.showFlashAlert).toHaveBeenCalledTimes(2) + expect(FlashAlert.showFlashAlert).toHaveBeenCalledWith({id: '1', message: 'hello'}) + expect(FlashAlert.showFlashAlert).toHaveBeenCalledWith({id: '2', message: 'world'}) + + done() + }, 1) + }) + + test('subscribes to a store and dispatches clearNotifications for each notification in state', done => { + const mockStore = createMockStore({ + notifications: [ + {id: '1', message: 'hello'}, + {id: '2', message: 'world'}, + ], + }) + + subscribeFlashNotifications(mockStore) + mockStore.mockStateChange() + + setTimeout(() => { + expect(mockStore.dispatch).toHaveBeenCalledTimes(2) + expect(mockStore.dispatch).toHaveBeenCalledWith(notificationActions.clearNotification('1')) + expect(mockStore.dispatch).toHaveBeenCalledWith(notificationActions.clearNotification('2')) + + done() + }, 1) + }) +}) + +describe('notificationActions', () => { + test('notifyInfo creates action NOTIFY_INFO with type "info" and payload', () => { + const action = notificationActions.notifyInfo({message: 'test'}) + expect(action).toEqual({type: 'NOTIFY_INFO', payload: {type: 'info', message: 'test'}}) + }) + + test('notifyError creates action NOTIFY_ERROR with type "error" and payload', () => { + const action = notificationActions.notifyError({message: 'test'}) + expect(action).toEqual({type: 'NOTIFY_ERROR', payload: {type: 'error', message: 'test'}}) + }) + + test('clearNotification creates action CLEAR_NOTIFICATION', () => { + const action = notificationActions.clearNotification() + expect(action).toEqual({type: 'CLEAR_NOTIFICATION'}) + }) +}) + +describe('reduceNotifications', () => { + test('catches any action with err and message and treats it as an error notification', () => { + const action = { + type: '_NOT_A_REAL_ACTION_', + payload: {message: 'hello world', err: 'bad things happened'}, + } + const newState = reduceNotifications([], action) + expect(newState).toMatchObject([ + {type: 'error', message: 'hello world', err: 'bad things happened'}, + ]) + }) + + test('adds new info notification on NOTIFY_INFO', () => { + const newState = reduceNotifications( + [], + notificationActions.notifyInfo({message: 'hello world'}) + ) + expect(newState).toMatchObject([{type: 'info', message: 'hello world'}]) + }) + + test('adds new error notification on NOTIFY_ERROR', () => { + const newState = reduceNotifications( + [], + notificationActions.notifyError({message: 'hello world', err: 'bad things happened'}) + ) + expect(newState).toMatchObject([ + {type: 'error', message: 'hello world', err: 'bad things happened'}, + ]) + }) + + test('removes notification on CLEAR_NOTIFICATION', () => { + const newState = reduceNotifications( + [{id: '1', message: 'hello world', type: 'info'}], + notificationActions.clearNotification('1') + ) + expect(newState).toEqual([]) + }) +}) diff --git a/ui/shared/notifications/redux/__tests__/reduxNotificationsSpec.js b/ui/shared/notifications/redux/__tests__/reduxNotificationsSpec.js deleted file mode 100644 index 6d700724f5d..00000000000 --- a/ui/shared/notifications/redux/__tests__/reduxNotificationsSpec.js +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2017 - present Instructure, Inc. - * - * This file is part of Canvas. - * - * Canvas is free software: you can redistribute it and/or modify it under - * the terms of the GNU Affero General Public License as published by the Free - * Software Foundation, version 3 of the License. - * - * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more - * details. - * - * You should have received a copy of the GNU Affero General Public License along - * with this program. If not, see . - */ - -import {subscribeFlashNotifications, notificationActions, reduceNotifications} from '../actions' -import * as FlashAlert from '@canvas/alerts/react/FlashAlert' - -const createMockStore = state => ({ - subs: [], - subscribe(cb) { - this.subs.push(cb) - }, - getState: () => state, - dispatch: () => {}, - mockStateChange() { - this.subs.forEach(sub => sub()) - }, -}) - -QUnit.module('Redux Notifications') - -QUnit.module('subscribeFlashNotifications', { - teardown() { - FlashAlert.destroyContainer() - }, -}) - -test('subscribes to a store and calls showFlashAlert for each notification in state', assert => { - const done = assert.async() - const flashAlertSpy = sinon.spy(FlashAlert, 'showFlashAlert') - const mockStore = createMockStore({ - notifications: [ - {id: '1', message: 'hello'}, - {id: '2', message: 'world'}, - ], - }) - - subscribeFlashNotifications(mockStore) - mockStore.mockStateChange() - - setTimeout(() => { - equal(flashAlertSpy.callCount, 2) - deepEqual(flashAlertSpy.firstCall.args, [{id: '1', message: 'hello'}]) - deepEqual(flashAlertSpy.secondCall.args, [{id: '2', message: 'world'}]) - flashAlertSpy.restore() - done() - }, 1) -}) - -test('subscribes to a store and dispatches clearNotifications for each notification in state', assert => { - const done = assert.async() - const mockStore = createMockStore({ - notifications: [ - {id: '1', message: 'hello'}, - {id: '2', message: 'world'}, - ], - }) - const dispatchSpy = sinon.spy(mockStore, 'dispatch') - - subscribeFlashNotifications(mockStore) - mockStore.mockStateChange() - - setTimeout(() => { - equal(dispatchSpy.callCount, 2) - deepEqual(dispatchSpy.firstCall.args, [notificationActions.clearNotification('1')]) - deepEqual(dispatchSpy.secondCall.args, [notificationActions.clearNotification('2')]) - dispatchSpy.restore() - done() - }, 1) -}) - -QUnit.module('notificationActions') - -test('notifyInfo creates action NOTIFY_INFO with type "info" and payload', () => { - const action = notificationActions.notifyInfo({message: 'test'}) - deepEqual(action, {type: 'NOTIFY_INFO', payload: {type: 'info', message: 'test'}}) -}) - -test('notifyError creates action NOTIFY_ERROR with type "error" and payload', () => { - const action = notificationActions.notifyError({message: 'test'}) - deepEqual(action, {type: 'NOTIFY_ERROR', payload: {type: 'error', message: 'test'}}) -}) - -test('clearNotification creates action CLEAR_NOTIFICATION', () => { - const action = notificationActions.clearNotification() - deepEqual(action, {type: 'CLEAR_NOTIFICATION'}) -}) - -QUnit.module('reduceNotifications') - -test('catches any action with err and message and treats it as an error notification', () => { - const action = { - type: '_NOT_A_REAL_ACTION_', - payload: {message: 'hello world', err: 'bad things happened'}, - } - const newState = reduceNotifications([], action) - equal(newState.length, 1) - equal(newState[0].type, 'error') - equal(newState[0].message, 'hello world') - equal(newState[0].err, 'bad things happened') -}) - -test('adds new info notification on NOTIFY_INFO', () => { - const newState = reduceNotifications([], notificationActions.notifyInfo({message: 'hello world'})) - equal(newState.length, 1) - equal(newState[0].type, 'info') - equal(newState[0].message, 'hello world') -}) - -test('adds new error notification on NOTIFY_ERROR', () => { - const newState = reduceNotifications( - [], - notificationActions.notifyError({message: 'hello world', err: 'bad things happened'}) - ) - equal(newState.length, 1) - equal(newState[0].type, 'error') - equal(newState[0].message, 'hello world') - equal(newState[0].err, 'bad things happened') -}) - -test('removes notification on CLEAR_NOTIFICATION', () => { - const newState = reduceNotifications( - [{id: '1', message: 'hello world', type: 'info'}], - notificationActions.clearNotification('1') - ) - equal(newState.length, 0) -})