From 837ac4e6353d5f6adc8f13dd55ba34c8b6c52675 Mon Sep 17 00:00:00 2001 From: sundasnoreen12 Date: Thu, 1 Feb 2024 16:47:02 +0500 Subject: [PATCH 1/2] test: added test cases to change toggle based on channel --- .../NotificationPreferenceRow.jsx | 1 + .../NotificationPreferences.test.jsx | 84 ++++++++++++++++--- src/notification-preferences/ToggleSwitch.jsx | 4 + 3 files changed, 76 insertions(+), 13 deletions(-) diff --git a/src/notification-preferences/NotificationPreferenceRow.jsx b/src/notification-preferences/NotificationPreferenceRow.jsx index 113af1c57..5202a0a39 100644 --- a/src/notification-preferences/NotificationPreferenceRow.jsx +++ b/src/notification-preferences/NotificationPreferenceRow.jsx @@ -78,6 +78,7 @@ const NotificationPreferenceRow = ({ appId, preferenceName }) => { value={preference[channel]} onChange={onToggle} disabled={nonEditable.includes(channel) || updatePreferencesStatus === LOADING_STATUS} + dataTestId={`${preferenceName}-${channel}`} /> ))} diff --git a/src/notification-preferences/NotificationPreferences.test.jsx b/src/notification-preferences/NotificationPreferences.test.jsx index f57e50beb..a064a3727 100644 --- a/src/notification-preferences/NotificationPreferences.test.jsx +++ b/src/notification-preferences/NotificationPreferences.test.jsx @@ -2,7 +2,9 @@ import { Provider } from 'react-redux'; import { BrowserRouter as Router } from 'react-router-dom'; import configureStore from 'redux-mock-store'; -import { fireEvent, render, screen } from '@testing-library/react'; +import { + fireEvent, render, screen, waitFor, act, +} from '@testing-library/react'; import * as auth from '@edx/frontend-platform/auth'; import { IntlProvider } from '@edx/frontend-platform/i18n'; import NotificationPreferences from './NotificationPreferences'; @@ -33,33 +35,50 @@ const defaultPreferences = { appId: 'discussion', web: false, push: false, - mobile: false, + email: false, }, { id: 'newComment', appId: 'discussion', web: false, push: false, - mobile: false, + email: false, }, { id: 'newAssignment', appId: 'coursework', web: false, push: false, - mobile: false, + email: false, }, { id: 'newGrade', appId: 'coursework', web: false, push: false, - mobile: false, + email: false, }, ], nonEditable: {}, }; +const updateChannelPreferences = (toggleVal = false) => ({ + preferences: [ + { + id: 'newPost', appId: 'discussion', web: toggleVal, push: toggleVal, email: toggleVal, + }, + { + id: 'newComment', appId: 'discussion', web: toggleVal, push: toggleVal, email: toggleVal, + }, + { + id: 'newAssignment', appId: 'coursework', web: toggleVal, push: toggleVal, email: toggleVal, + }, + { + id: 'newGrade', appId: 'coursework', web: toggleVal, push: toggleVal, email: toggleVal, + }, + ], +}); + const setupStore = (override = {}) => { const storeState = defaultState; storeState.courses = { @@ -78,17 +97,19 @@ const setupStore = (override = {}) => { return store; }; -const renderComponent = (store = {}) => render( +const renderComponent = (store = {}) => ( - , + ); + describe('Notification Preferences', () => { let store; + beforeEach(() => { store = setupStore({ ...defaultPreferences, @@ -108,29 +129,30 @@ describe('Notification Preferences', () => { afterEach(() => jest.clearAllMocks()); it('tests if all notification apps are listed', async () => { - await renderComponent(store); + await render(renderComponent(store)); expect(screen.queryAllByTestId('notification-app')).toHaveLength(2); }); + it('show spinner if api call is in progress', async () => { store = setupStore({ status: LOADING_STATUS }); - await renderComponent(store); + await render(renderComponent(store)); expect(screen.queryByTestId('loading-spinner')).toBeInTheDocument(); }); it('tests if all notification preferences are listed', async () => { - await renderComponent(store); + await render(renderComponent(store)); expect(screen.queryAllByTestId('notification-preference')).toHaveLength(4); }); it('update group on click', async () => { - const wrapper = await renderComponent(store); + const wrapper = await render(renderComponent(store)); const element = wrapper.container.querySelector('#discussion-app-toggle'); await fireEvent.click(element); expect(mockDispatch).toHaveBeenCalled(); }); it('update preference on click', async () => { - const wrapper = await renderComponent(store); + const wrapper = await render(renderComponent(store)); const element = wrapper.container.querySelector('#newPost-web'); expect(element).not.toBeChecked(); await fireEvent.click(element); @@ -139,7 +161,43 @@ describe('Notification Preferences', () => { it('show not found page if invalid course id is entered in url', async () => { store = setupStore({ status: FAILURE_STATUS, selectedCourse: 'invalid-course-id' }); - await renderComponent(store); + await render(renderComponent(store)); expect(screen.queryByTestId('not-found-page')).toBeInTheDocument(); }); + + it.each([false, true])( + 'updates all preferences in the column on web channel click when toggle state is - %s', + async (toggleState) => { + store = setupStore(updateChannelPreferences(toggleState)); + const wrapper = render(renderComponent(store)); + + const getCheckbox = (id) => screen.queryByTestId(`${id}-web`); + const checkboxes = ['newPost', 'newComment', 'newAssignment', 'newGrade']; + + const verifyState = (expectedState) => { + checkboxes.forEach((checkbox) => { + if (expectedState) { + expect(getCheckbox(checkbox)).toBeChecked(); + } else { + expect(getCheckbox(checkbox)).not.toBeChecked(); + } + }); + }; + + verifyState(toggleState); + + const element = screen.queryAllByText('Web')[0]; + + await act(async () => { + await fireEvent.click(element); + }); + + store = setupStore(updateChannelPreferences(!toggleState)); + wrapper.rerender(renderComponent(store)); + + await waitFor(() => { + verifyState(!toggleState); + }); + }, + ); }); diff --git a/src/notification-preferences/ToggleSwitch.jsx b/src/notification-preferences/ToggleSwitch.jsx index 83e1183ea..eecb9628f 100644 --- a/src/notification-preferences/ToggleSwitch.jsx +++ b/src/notification-preferences/ToggleSwitch.jsx @@ -7,12 +7,14 @@ const ToggleSwitch = ({ value, disabled, onChange, + dataTestId, }) => ( ); @@ -21,11 +23,13 @@ ToggleSwitch.propTypes = { value: PropTypes.bool.isRequired, disabled: PropTypes.bool, onChange: PropTypes.func, + dataTestId: PropTypes.string, }; ToggleSwitch.defaultProps = { onChange: () => null, disabled: false, + dataTestId: '', }; export default React.memo(ToggleSwitch); From 67feee5e0bcb5816eeb20ee1b3eb87ddcf5b6bd7 Mon Sep 17 00:00:00 2001 From: sundasnoreen12 Date: Thu, 1 Feb 2024 19:43:27 +0500 Subject: [PATCH 2/2] refactor: fix reveiw issues --- .../NotificationPreferenceApp.jsx | 2 +- .../NotificationPreferenceRow.jsx | 2 +- .../NotificationPreferences.test.jsx | 108 +++++++++--------- src/notification-preferences/ToggleSwitch.jsx | 8 +- 4 files changed, 59 insertions(+), 61 deletions(-) diff --git a/src/notification-preferences/NotificationPreferenceApp.jsx b/src/notification-preferences/NotificationPreferenceApp.jsx index 5ebad140f..ed57e66d9 100644 --- a/src/notification-preferences/NotificationPreferenceApp.jsx +++ b/src/notification-preferences/NotificationPreferenceApp.jsx @@ -41,7 +41,7 @@ const NotificationPreferenceApp = ({ appId }) => { return null; } return ( - +
diff --git a/src/notification-preferences/NotificationPreferenceRow.jsx b/src/notification-preferences/NotificationPreferenceRow.jsx index 5202a0a39..8541a9e0c 100644 --- a/src/notification-preferences/NotificationPreferenceRow.jsx +++ b/src/notification-preferences/NotificationPreferenceRow.jsx @@ -78,7 +78,7 @@ const NotificationPreferenceRow = ({ appId, preferenceName }) => { value={preference[channel]} onChange={onToggle} disabled={nonEditable.includes(channel) || updatePreferencesStatus === LOADING_STATUS} - dataTestId={`${preferenceName}-${channel}`} + id={`${preferenceName}-${channel}`} />
))} diff --git a/src/notification-preferences/NotificationPreferences.test.jsx b/src/notification-preferences/NotificationPreferences.test.jsx index a064a3727..6dba6af03 100644 --- a/src/notification-preferences/NotificationPreferences.test.jsx +++ b/src/notification-preferences/NotificationPreferences.test.jsx @@ -3,7 +3,7 @@ import { Provider } from 'react-redux'; import { BrowserRouter as Router } from 'react-router-dom'; import configureStore from 'redux-mock-store'; import { - fireEvent, render, screen, waitFor, act, + fireEvent, render, screen, waitFor, act, within, } from '@testing-library/react'; import * as auth from '@edx/frontend-platform/auth'; import { IntlProvider } from '@edx/frontend-platform/i18n'; @@ -31,11 +31,11 @@ const defaultPreferences = { ], preferences: [ { - id: 'newPost', + id: 'core', appId: 'discussion', - web: false, - push: false, - email: false, + web: true, + push: true, + email: true, }, { id: 'newComment', @@ -59,23 +59,20 @@ const defaultPreferences = { email: false, }, ], - nonEditable: {}, + nonEditable: { + discussion: { + core: [ + 'web', + ], + }, + }, }; const updateChannelPreferences = (toggleVal = false) => ({ preferences: [ - { - id: 'newPost', appId: 'discussion', web: toggleVal, push: toggleVal, email: toggleVal, - }, - { - id: 'newComment', appId: 'discussion', web: toggleVal, push: toggleVal, email: toggleVal, - }, - { - id: 'newAssignment', appId: 'coursework', web: toggleVal, push: toggleVal, email: toggleVal, - }, - { - id: 'newGrade', appId: 'coursework', web: toggleVal, push: toggleVal, email: toggleVal, - }, + { id: 'core', appId: 'discussion', web: true }, + { id: 'newComment', appId: 'discussion', web: toggleVal }, + { id: 'newAssignment', appId: 'coursework', web: toggleVal }, ], }); @@ -97,7 +94,7 @@ const setupStore = (override = {}) => { return store; }; -const renderComponent = (store = {}) => ( +const notificationPreferences = (store = {}) => ( @@ -129,31 +126,32 @@ describe('Notification Preferences', () => { afterEach(() => jest.clearAllMocks()); it('tests if all notification apps are listed', async () => { - await render(renderComponent(store)); - expect(screen.queryAllByTestId('notification-app')).toHaveLength(2); + await render(notificationPreferences(store)); + expect(screen.queryByTestId('discussion-app')).toBeInTheDocument(); + expect(screen.queryByTestId('coursework-app')).toBeInTheDocument(); }); it('show spinner if api call is in progress', async () => { store = setupStore({ status: LOADING_STATUS }); - await render(renderComponent(store)); + await render(notificationPreferences(store)); expect(screen.queryByTestId('loading-spinner')).toBeInTheDocument(); }); it('tests if all notification preferences are listed', async () => { - await render(renderComponent(store)); + await render(notificationPreferences(store)); expect(screen.queryAllByTestId('notification-preference')).toHaveLength(4); }); it('update group on click', async () => { - const wrapper = await render(renderComponent(store)); + const wrapper = await render(notificationPreferences(store)); const element = wrapper.container.querySelector('#discussion-app-toggle'); await fireEvent.click(element); expect(mockDispatch).toHaveBeenCalled(); }); it('update preference on click', async () => { - const wrapper = await render(renderComponent(store)); - const element = wrapper.container.querySelector('#newPost-web'); + const wrapper = await render(notificationPreferences(store)); + const element = wrapper.container.querySelector('#core-web'); expect(element).not.toBeChecked(); await fireEvent.click(element); expect(mockDispatch).toHaveBeenCalled(); @@ -161,43 +159,43 @@ describe('Notification Preferences', () => { it('show not found page if invalid course id is entered in url', async () => { store = setupStore({ status: FAILURE_STATUS, selectedCourse: 'invalid-course-id' }); - await render(renderComponent(store)); + await render(notificationPreferences(store)); expect(screen.queryByTestId('not-found-page')).toBeInTheDocument(); }); - it.each([false, true])( - 'updates all preferences in the column on web channel click when toggle state is - %s', - async (toggleState) => { - store = setupStore(updateChannelPreferences(toggleState)); - const wrapper = render(renderComponent(store)); + it('updates all preferences in the column on web channel click', async () => { + store = setupStore(updateChannelPreferences(true)); + const wrapper = render(notificationPreferences(store)); - const getCheckbox = (id) => screen.queryByTestId(`${id}-web`); - const checkboxes = ['newPost', 'newComment', 'newAssignment', 'newGrade']; + const getChannelSwitch = (id) => screen.queryByTestId(`${id}-web`); + const notificationTypes = ['newComment', 'newAssignment']; - const verifyState = (expectedState) => { - checkboxes.forEach((checkbox) => { - if (expectedState) { - expect(getCheckbox(checkbox)).toBeChecked(); - } else { - expect(getCheckbox(checkbox)).not.toBeChecked(); - } - }); - }; + const verifyState = (toggleState) => { + notificationTypes.forEach((notificationType) => { + if (toggleState) { + expect(getChannelSwitch(notificationType)).toBeChecked(); + } else { + expect(getChannelSwitch(notificationType)).not.toBeChecked(); + } + }); + }; - verifyState(toggleState); + verifyState(true); + expect(getChannelSwitch('core')).toBeChecked(); - const element = screen.queryAllByText('Web')[0]; + const discussionApp = screen.queryByTestId('discussion-app'); + const webChannel = within(discussionApp).queryByText('Web'); - await act(async () => { - await fireEvent.click(element); - }); + await act(async () => { + await fireEvent.click(webChannel); + }); - store = setupStore(updateChannelPreferences(!toggleState)); - wrapper.rerender(renderComponent(store)); + store = setupStore(updateChannelPreferences(false)); + wrapper.rerender(notificationPreferences(store)); - await waitFor(() => { - verifyState(!toggleState); - }); - }, - ); + await waitFor(() => { + verifyState(false); + expect(getChannelSwitch('core')).toBeChecked(); + }); + }); }); diff --git a/src/notification-preferences/ToggleSwitch.jsx b/src/notification-preferences/ToggleSwitch.jsx index eecb9628f..91c2c995d 100644 --- a/src/notification-preferences/ToggleSwitch.jsx +++ b/src/notification-preferences/ToggleSwitch.jsx @@ -7,14 +7,14 @@ const ToggleSwitch = ({ value, disabled, onChange, - dataTestId, + id, }) => ( ); @@ -23,13 +23,13 @@ ToggleSwitch.propTypes = { value: PropTypes.bool.isRequired, disabled: PropTypes.bool, onChange: PropTypes.func, - dataTestId: PropTypes.string, + id: PropTypes.string, }; ToggleSwitch.defaultProps = { onChange: () => null, disabled: false, - dataTestId: '', + id: '', }; export default React.memo(ToggleSwitch);