Compare commits

..

1 Commits

Author SHA1 Message Date
mubbsharanwar
d69db1f4d5 chore: upgrade frontend-component-header version
upgrade frontend-component-header version to enable show/hide username from header based on flag

VAN-1804
2024-02-06 16:44:33 +05:00
9 changed files with 25 additions and 151 deletions

View File

@@ -5,7 +5,7 @@ $fa-font-path: "~font-awesome/fonts";
@import "~@edx/brand/paragon/variables";
@import "~@openedx/paragon/scss/core/core";
@import "~@edx/brand/paragon/overrides";
@import "~@edx/frontend-component-header/dist/index";
@import "@edx/frontend-component-header/dist/index";
@import "~@edx/frontend-component-footer/dist/footer";
@import "./account-settings/style";

View File

@@ -2,21 +2,18 @@ import React, { useCallback, useMemo } from 'react';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Collapsible, NavItem } from '@openedx/paragon';
import classNames from 'classnames';
import { Collapsible } from '@openedx/paragon';
import messages from './messages';
import ToggleSwitch from './ToggleSwitch';
import {
selectPreferenceAppToggleValue,
selectNonEditablePreferences,
selectPreferencesOfApp,
selectSelectedCourseId,
selectUpdatePreferencesStatus,
} from './data/selectors';
import NotificationPreferenceRow from './NotificationPreferenceRow';
import { updateAppPreferenceToggle, updateChannelPreferenceToggle } from './data/thunks';
import { updateAppPreferenceToggle } from './data/thunks';
import { LOADING_STATUS } from '../constants';
import NOTIFICATION_CHANNELS from './data/constants';
const NotificationPreferenceApp = ({ appId }) => {
const dispatch = useDispatch();
@@ -25,18 +22,6 @@ const NotificationPreferenceApp = ({ appId }) => {
const appPreferences = useSelector(selectPreferencesOfApp(appId));
const appToggle = useSelector(selectPreferenceAppToggleValue(appId));
const updatePreferencesStatus = useSelector(selectUpdatePreferencesStatus());
const nonEditable = useSelector(selectNonEditablePreferences(appId));
const onChannelToggle = useCallback((event) => {
const { id: notificationChannel } = event.target;
const isPreferenceNonEditable = (preference) => nonEditable?.[preference.id]?.includes(notificationChannel);
const hasActivePreferences = appPreferences.some(
(preference) => preference[notificationChannel] && !isPreferenceNonEditable(preference),
);
dispatch(updateChannelPreferenceToggle(courseId, appId, notificationChannel, !hasActivePreferences));
}, [appId, appPreferences, courseId, dispatch, nonEditable]);
const preferences = useMemo(() => (
appPreferences.map(preference => (
@@ -51,12 +36,12 @@ const NotificationPreferenceApp = ({ appId }) => {
dispatch(updateAppPreferenceToggle(courseId, appId, event.target.checked));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [appId]);
if (!courseId) {
return null;
}
return (
<Collapsible.Advanced open={appToggle} data-testid={`${appId}-app`} className="mb-5">
<Collapsible.Advanced open={appToggle} data-testid="notification-app" className="mb-5">
<Collapsible.Trigger>
<div className="d-flex align-items-center">
<span className="mr-auto">
@@ -77,22 +62,7 @@ const NotificationPreferenceApp = ({ appId }) => {
<div className="d-flex flex-row header-label">
<span className="col-8 px-0">{intl.formatMessage(messages.typeLabel)}</span>
<span className="d-flex col-4 px-0">
{NOTIFICATION_CHANNELS.map((channel) => (
<NavItem
id={channel}
key={channel}
className={classNames(
'd-flex',
{ 'ml-auto': channel === 'web' },
{ 'mx-auto': channel === 'email' },
{ 'ml-auto mr-0': channel === 'push' },
)}
role="button"
onClick={onChannelToggle}
>
{intl.formatMessage(messages.notificationChannel, { text: channel })}
</NavItem>
))}
<span className="ml-auto">{intl.formatMessage(messages.webLabel)}</span>
</span>
</div>
<div className="my-3">

View File

@@ -78,7 +78,6 @@ const NotificationPreferenceRow = ({ appId, preferenceName }) => {
value={preference[channel]}
onChange={onToggle}
disabled={nonEditable.includes(channel) || updatePreferencesStatus === LOADING_STATUS}
id={`${preferenceName}-${channel}`}
/>
</div>
))}

View File

@@ -2,9 +2,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, within,
} from '@testing-library/react';
import { fireEvent, render, screen } from '@testing-library/react';
import * as auth from '@edx/frontend-platform/auth';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import NotificationPreferences from './NotificationPreferences';
@@ -31,51 +29,37 @@ const defaultPreferences = {
],
preferences: [
{
id: 'core',
id: 'newPost',
appId: 'discussion',
web: true,
push: true,
email: true,
web: false,
push: false,
mobile: false,
},
{
id: 'newComment',
appId: 'discussion',
web: false,
push: false,
email: false,
mobile: false,
},
{
id: 'newAssignment',
appId: 'coursework',
web: false,
push: false,
email: false,
mobile: false,
},
{
id: 'newGrade',
appId: 'coursework',
web: false,
push: false,
email: false,
mobile: false,
},
],
nonEditable: {
discussion: {
core: [
'web',
],
},
},
nonEditable: {},
};
const updateChannelPreferences = (toggleVal = false) => ({
preferences: [
{ id: 'core', appId: 'discussion', web: true },
{ id: 'newComment', appId: 'discussion', web: toggleVal },
{ id: 'newAssignment', appId: 'coursework', web: toggleVal },
],
});
const setupStore = (override = {}) => {
const storeState = defaultState;
storeState.courses = {
@@ -94,19 +78,17 @@ const setupStore = (override = {}) => {
return store;
};
const notificationPreferences = (store = {}) => (
const renderComponent = (store = {}) => render(
<Router>
<IntlProvider locale="en">
<Provider store={store}>
<NotificationPreferences />
</Provider>
</IntlProvider>
</Router>
</Router>,
);
describe('Notification Preferences', () => {
let store;
beforeEach(() => {
store = setupStore({
...defaultPreferences,
@@ -126,32 +108,30 @@ describe('Notification Preferences', () => {
afterEach(() => jest.clearAllMocks());
it('tests if all notification apps are listed', async () => {
await render(notificationPreferences(store));
expect(screen.queryByTestId('discussion-app')).toBeInTheDocument();
expect(screen.queryByTestId('coursework-app')).toBeInTheDocument();
await 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 render(notificationPreferences(store));
await renderComponent(store);
expect(screen.queryByTestId('loading-spinner')).toBeInTheDocument();
});
it('tests if all notification preferences are listed', async () => {
await render(notificationPreferences(store));
await renderComponent(store);
expect(screen.queryAllByTestId('notification-preference')).toHaveLength(4);
});
it('update group on click', async () => {
const wrapper = await render(notificationPreferences(store));
const wrapper = await 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 render(notificationPreferences(store));
const element = wrapper.container.querySelector('#core-web');
const wrapper = await renderComponent(store);
const element = wrapper.container.querySelector('#newPost-web');
expect(element).not.toBeChecked();
await fireEvent.click(element);
expect(mockDispatch).toHaveBeenCalled();
@@ -159,43 +139,7 @@ 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(notificationPreferences(store));
await renderComponent(store);
expect(screen.queryByTestId('not-found-page')).toBeInTheDocument();
});
it('updates all preferences in the column on web channel click', async () => {
store = setupStore(updateChannelPreferences(true));
const wrapper = render(notificationPreferences(store));
const getChannelSwitch = (id) => screen.queryByTestId(`${id}-web`);
const notificationTypes = ['newComment', 'newAssignment'];
const verifyState = (toggleState) => {
notificationTypes.forEach((notificationType) => {
if (toggleState) {
expect(getChannelSwitch(notificationType)).toBeChecked();
} else {
expect(getChannelSwitch(notificationType)).not.toBeChecked();
}
});
};
verifyState(true);
expect(getChannelSwitch('core')).toBeChecked();
const discussionApp = screen.queryByTestId('discussion-app');
const webChannel = within(discussionApp).queryByText('Web');
await act(async () => {
await fireEvent.click(webChannel);
});
store = setupStore(updateChannelPreferences(false));
wrapper.rerender(notificationPreferences(store));
await waitFor(() => {
verifyState(false);
expect(getChannelSwitch('core')).toBeChecked();
});
});
});

View File

@@ -7,14 +7,12 @@ const ToggleSwitch = ({
value,
disabled,
onChange,
id,
}) => (
<Form.Switch
name={name}
checked={value}
disabled={disabled}
onChange={onChange}
data-testid={id}
/>
);
@@ -23,13 +21,11 @@ ToggleSwitch.propTypes = {
value: PropTypes.bool.isRequired,
disabled: PropTypes.bool,
onChange: PropTypes.func,
id: PropTypes.string,
};
ToggleSwitch.defaultProps = {
onChange: () => null,
disabled: false,
id: '',
};
export default React.memo(ToggleSwitch);

View File

@@ -54,10 +54,6 @@ export const selectPreferenceNonEditableChannels = (appId, name) => state => (
state?.notificationPreferences.preferences.nonEditable[appId]?.[name] || []
);
export const selectNonEditablePreferences = appId => state => (
state?.notificationPreferences.preferences.nonEditable[appId] || []
);
export const selectSelectedCourseId = () => state => (
state.notificationPreferences.preferences.selectedCourse
);

View File

@@ -42,10 +42,3 @@ export const patchPreferenceToggle = async (
const { data } = await getAuthenticatedHttpClient().patch(url, patchData);
return data;
};
export const patchChannelPreferenceToggle = async (courseId, notificationApp, notificationChannel, value) => {
const patchData = snakeCaseObject({ notificationApp, notificationChannel, value });
const url = `${getConfig().LMS_BASE_URL}/api/notifications/channel/configurations/${courseId}`;
const { data } = await getAuthenticatedHttpClient().patch(url, patchData);
return data;
};

View File

@@ -14,7 +14,6 @@ import {
getCourseList,
getCourseNotificationPreferences,
patchAppPreferenceToggle,
patchChannelPreferenceToggle,
patchPreferenceToggle,
} from './service';
@@ -149,15 +148,3 @@ export const updatePreferenceToggle = (
}
}
);
export const updateChannelPreferenceToggle = (courseId, notificationApp, notificationChannel, value) => (
async (dispatch) => {
try {
const data = await patchChannelPreferenceToggle(courseId, notificationApp, notificationChannel, value);
const normalizedData = normalizePreferences(camelCaseObject(data));
dispatch(fetchNotificationPreferenceSuccess(courseId, normalizedData));
} catch (errors) {
dispatch(fetchNotificationPreferenceFailed());
}
}
);

View File

@@ -28,17 +28,6 @@ const messages = defineMessages({
}`,
description: 'Display text for Notification Types',
},
notificationChannel: {
id: 'notification.preference.channel',
defaultMessage: `{
text, select,
web {Web}
email {Email}
push {Push}
other {{text}}
}`,
description: 'Display text for Notification Channel',
},
typeLabel: {
id: 'notification.preference.type.label',
defaultMessage: 'Type',