Compare commits

...

3 Commits

Author SHA1 Message Date
Stanislav
5b676626e3 feat: Restore jump nav and content width (#995) 2024-12-24 10:53:22 +05:00
Stanislav
1fcb7622e7 fix: Add ID attribute to the main content (#1020)
Co-authored-by: Stanislav Lunyachek <lunyachek@MacBook-Pro-M1.local>
2024-04-01 14:12:44 +05:00
Dmytro
2463a1f5fb fix: Fixed the display of the selection of available time zones. (#965)
Co-authored-by: Dima Alipov <dimaalipov@MacBook-Pro-Dima.local>
2024-01-01 23:27:53 +05:00
8 changed files with 698 additions and 9 deletions

View File

@@ -814,12 +814,12 @@ class AccountSettingsPage extends React.Component {
</h1>
<div>
<div className="row">
<div className="col-md-2">
<div className="col-md-3">
<JumpNav
displayDemographicsLink={this.props.formValues.shouldDisplayDemographicsSection}
/>
</div>
<div className="col-md-10">
<div className="col-md-9">
{loading ? this.renderLoading() : null}
{loaded ? this.renderContent() : null}
{loadingError ? this.renderError() : null}

View File

@@ -98,9 +98,28 @@ const EditableSelectField = (props) => {
value: confirmationValue,
});
};
const selectOptions = options.map(option => (
<option value={option.value} key={`${option.value}-${option.label}`}>{option.label}</option>
));
const selectOptions = options.map((option) => {
if (option.group) {
// If the option has a 'group' property, it represents an element with sub-options.
return (
<optgroup label={option.label} key={option.label}>
{option.group.map((subOption) => (
<option
value={subOption.value}
key={`${subOption.value}-${subOption.label}`}
>
{subOption.label}
</option>
))}
</optgroup>
);
}
return (
<option value={option.value} key={`${option.value}-${option.label}`}>
{option.label}
</option>
);
});
return (
<SwitchContent

View File

@@ -20,7 +20,7 @@ const JumpNav = ({
const showPreferences = useSelector(selectShowPreferences());
return (
<div className={classNames('jump-nav px-2.25', { 'jump-nav-sm position-sticky pt-3': stickToTop })}>
<div className={classNames('jump-nav', { 'jump-nav-sm position-sticky pt-3': stickToTop })}>
<Scrollspy
items={[
'basic-information',

View File

@@ -0,0 +1,16 @@
import { profileDataManagerSelector } from './selectors';
const testValue = 'test VALUE';
describe('profileDataManagerSelector', () => {
it('returns the profileDataManager from the state', () => {
const state = {
accountSettings: {
profileDataManager: { testValue },
},
};
const result = profileDataManagerSelector(state);
expect(result).toEqual(state.accountSettings.profileDataManager);
});
});

View File

@@ -0,0 +1,169 @@
import React from 'react';
import { BrowserRouter as Router } from 'react-router-dom';
import { Provider } from 'react-redux';
import renderer from 'react-test-renderer';
import configureStore from 'redux-mock-store';
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
import EditableSelectField from '../EditableSelectField';
const mockDispatch = jest.fn();
jest.mock('react-redux', () => ({
...jest.requireActual('react-redux'),
useDispatch: () => mockDispatch,
}));
jest.mock('@edx/frontend-platform/auth');
jest.mock('../data/selectors', () => jest.fn().mockImplementation(() => ({ certPreferenceSelector: () => ({}) })));
const IntlEditableSelectField = injectIntl(EditableSelectField);
const mockStore = configureStore();
describe('EditableSelectField', () => {
let props = {};
let store = {};
const reduxWrapper = children => (
<Router>
<IntlProvider locale="en">
<Provider store={store}>{children}</Provider>
</IntlProvider>
</Router>
);
beforeEach(() => {
store = mockStore();
props = {
name: 'testField',
label: 'Main Label',
emptyLabel: 'Empty Main Label',
type: 'text',
value: 'Test Field',
userSuppliedValue: '',
options: [
{
label: 'Default Option',
value: 'defaultOption',
},
{
label: 'User Options',
group: [
{
label: 'Suboption 1',
value: 'suboption1',
},
],
},
{
label: 'Other Options',
group: [
{
label: 'Suboption 2',
value: 'suboption2',
},
{
label: 'Suboption 3',
value: 'suboption3',
},
],
},
],
saveState: 'default',
error: '',
confirmationMessageDefinition: {
id: 'confirmationMessageId',
defaultMessage: 'Default Confirmation Message',
description: 'Description of the confirmation message',
},
confirmationValue: 'Confirmation Value',
helpText: 'Helpful Text',
isEditing: false,
isEditable: true,
isGrayedOut: false,
};
});
afterEach(() => jest.clearAllMocks());
it('renders EditableSelectField correctly with editing disabled', () => {
const tree = renderer.create(reduxWrapper(<IntlEditableSelectField {...props} />)).toJSON();
expect(tree).toMatchSnapshot();
});
it('renders EditableSelectField correctly with editing enabled', () => {
props = {
...props,
isEditing: true,
};
const tree = renderer.create(reduxWrapper(<IntlEditableSelectField {...props} />)).toJSON();
expect(tree).toMatchSnapshot();
});
it('renders EditableSelectField with an error', () => {
const errorProps = {
...props,
error: 'This is an error message',
};
const tree = renderer.create(reduxWrapper(<IntlEditableSelectField {...errorProps} />)).toJSON();
expect(tree).toMatchSnapshot();
});
it('renders selectOptions when option has a group', () => {
const propsWithGroup = {
...props,
options: [
{
label: 'User Options',
group: [
{
label: 'Suboption 1',
value: 'suboption1',
},
],
},
],
};
const tree = renderer.create(reduxWrapper(<IntlEditableSelectField {...propsWithGroup} />)).toJSON();
expect(tree).toMatchSnapshot();
});
it('renders selectOptions when option does not have a group', () => {
const propsWithoutGroup = {
...props,
options: [
{
label: 'Default Option',
value: 'defaultOption',
},
],
};
const tree = renderer.create(reduxWrapper(<IntlEditableSelectField {...propsWithoutGroup} />)).toJSON();
expect(tree).toMatchSnapshot();
});
it('renders selectOptions with multiple groups', () => {
const propsWithGroups = {
...props,
options: [
{
label: 'Mixed Options',
group: [
{
label: 'Suboption 1',
value: 'suboption1',
},
{
label: 'Suboption 2',
value: 'suboption2',
},
],
},
],
};
const tree = renderer.create(reduxWrapper(<IntlEditableSelectField {...propsWithGroups} />)).toJSON();
expect(tree).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,485 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EditableSelectField renders EditableSelectField correctly with editing disabled 1`] = `
<div
className="pgn-transition-replace-group position-relative"
style={
Object {
"height": null,
}
}
>
<div
style={
Object {
"padding": ".1px 0",
}
}
>
<div
className="form-group"
>
<div
className="d-flex align-items-start"
>
<h6
aria-level="3"
>
Main Label
</h6>
<button
className="ml-3 btn btn-link"
disabled={false}
onClick={[Function]}
type="button"
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
data-icon="pencil-alt"
data-prefix="fas"
focusable="false"
role="img"
style={Object {}}
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
fill="currentColor"
style={Object {}}
/>
</svg>
Edit
</button>
</div>
<p
className={null}
data-hj-suppress={true}
>
Test Field
</p>
<p
className="small text-muted mt-n2"
>
Default Confirmation Message
</p>
</div>
</div>
</div>
`;
exports[`EditableSelectField renders EditableSelectField correctly with editing enabled 1`] = `
<div
className="pgn-transition-replace-group position-relative"
style={
Object {
"height": null,
}
}
>
<div
style={
Object {
"padding": ".1px 0",
}
}
>
<form
onSubmit={[Function]}
>
<div
className="pgn__form-group"
>
<label
className="pgn__form-label h6 d-block"
htmlFor="field-testField"
size="sm"
>
Main Label
</label>
<div
className="pgn__form-control-decorator-group"
>
<text
aria-describedby="field-testField-2"
className="has-value form-control is-invalid"
data-hj-suppress={true}
id="field-testField"
name="testField"
onBlur={[Function]}
onChange={[Function]}
type="text"
value="Test Field"
>
<option
value="defaultOption"
>
Default Option
</option>
<optgroup
label="User Options"
>
<option
value="suboption1"
>
Suboption 1
</option>
</optgroup>
<optgroup
label="Other Options"
>
<option
value="suboption2"
>
Suboption 2
</option>
<option
value="suboption3"
>
Suboption 3
</option>
</optgroup>
</text>
</div>
<div
className="pgn__form-text pgn__form-text-default"
>
<div>
Helpful Text
</div>
</div>
<div
className="pgn__form-control-description pgn__form-text pgn__form-text-invalid"
id="field-testField-2"
>
<span
className="pgn__icon"
>
<svg
aria-hidden={true}
fill="none"
focusable={false}
height={24}
role="img"
viewBox="0 0 24 24"
width={24}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41Z"
fill="currentColor"
/>
</svg>
</span>
<div>
</div>
</div>
</div>
<p>
<button
aria-disabled={false}
aria-live="assertive"
className="pgn__stateful-btn pgn__stateful-btn-state-default mr-2 btn btn-primary"
disabled={false}
onClick={[Function]}
type="submit"
>
<span
className="d-flex align-items-center justify-content-center"
>
<span>
Save
</span>
</span>
</button>
<button
className="btn btn-outline-primary"
disabled={false}
onClick={[Function]}
type="button"
>
Cancel
</button>
</p>
</form>
</div>
</div>
`;
exports[`EditableSelectField renders EditableSelectField with an error 1`] = `
<div
className="pgn-transition-replace-group position-relative"
style={
Object {
"height": null,
}
}
>
<div
style={
Object {
"padding": ".1px 0",
}
}
>
<div
className="form-group"
>
<div
className="d-flex align-items-start"
>
<h6
aria-level="3"
>
Main Label
</h6>
<button
className="ml-3 btn btn-link"
disabled={false}
onClick={[Function]}
type="button"
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
data-icon="pencil-alt"
data-prefix="fas"
focusable="false"
role="img"
style={Object {}}
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
fill="currentColor"
style={Object {}}
/>
</svg>
Edit
</button>
</div>
<p
className={null}
data-hj-suppress={true}
>
Test Field
</p>
<p
className="small text-muted mt-n2"
>
Default Confirmation Message
</p>
</div>
</div>
</div>
`;
exports[`EditableSelectField renders selectOptions when option does not have a group 1`] = `
<div
className="pgn-transition-replace-group position-relative"
style={
Object {
"height": null,
}
}
>
<div
style={
Object {
"padding": ".1px 0",
}
}
>
<div
className="form-group"
>
<div
className="d-flex align-items-start"
>
<h6
aria-level="3"
>
Main Label
</h6>
<button
className="ml-3 btn btn-link"
disabled={false}
onClick={[Function]}
type="button"
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
data-icon="pencil-alt"
data-prefix="fas"
focusable="false"
role="img"
style={Object {}}
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
fill="currentColor"
style={Object {}}
/>
</svg>
Edit
</button>
</div>
<p
className={null}
data-hj-suppress={true}
>
Test Field
</p>
<p
className="small text-muted mt-n2"
>
Default Confirmation Message
</p>
</div>
</div>
</div>
`;
exports[`EditableSelectField renders selectOptions when option has a group 1`] = `
<div
className="pgn-transition-replace-group position-relative"
style={
Object {
"height": null,
}
}
>
<div
style={
Object {
"padding": ".1px 0",
}
}
>
<div
className="form-group"
>
<div
className="d-flex align-items-start"
>
<h6
aria-level="3"
>
Main Label
</h6>
<button
className="ml-3 btn btn-link"
disabled={false}
onClick={[Function]}
type="button"
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
data-icon="pencil-alt"
data-prefix="fas"
focusable="false"
role="img"
style={Object {}}
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
fill="currentColor"
style={Object {}}
/>
</svg>
Edit
</button>
</div>
<p
className={null}
data-hj-suppress={true}
>
Test Field
</p>
<p
className="small text-muted mt-n2"
>
Default Confirmation Message
</p>
</div>
</div>
</div>
`;
exports[`EditableSelectField renders selectOptions with multiple groups 1`] = `
<div
className="pgn-transition-replace-group position-relative"
style={
Object {
"height": null,
}
}
>
<div
style={
Object {
"padding": ".1px 0",
}
}
>
<div
className="form-group"
>
<div
className="d-flex align-items-start"
>
<h6
aria-level="3"
>
Main Label
</h6>
<button
className="ml-3 btn btn-link"
disabled={false}
onClick={[Function]}
type="button"
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
data-icon="pencil-alt"
data-prefix="fas"
focusable="false"
role="img"
style={Object {}}
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
fill="currentColor"
style={Object {}}
/>
</svg>
Edit
</button>
</div>
<p
className={null}
data-hj-suppress={true}
>
Test Field
</p>
<p
className="small text-muted mt-n2"
>
Default Confirmation Message
</p>
</div>
</div>
</div>
`;

View File

@@ -2,7 +2,7 @@
exports[`JumpNav should not render Optional Information or delete account link 1`] = `
<div
className="jump-nav px-2.25 jump-nav-sm position-sticky pt-3"
className="jump-nav jump-nav-sm position-sticky pt-3"
>
<ul
className="list-unstyled"
@@ -79,7 +79,7 @@ exports[`JumpNav should not render Optional Information or delete account link 1
exports[`JumpNav should render Optional Information and delete account link 1`] = `
<div
className="jump-nav px-2.25 jump-nav-sm position-sticky pt-3"
className="jump-nav jump-nav-sm position-sticky pt-3"
>
<ul
className="list-unstyled"

View File

@@ -31,7 +31,7 @@ subscribe(APP_READY, () => {
<Route element={(
<div className="d-flex flex-column" style={{ minHeight: '100vh' }}>
<Header />
<main className="flex-grow-1">
<main className="flex-grow-1" id="main">
<Outlet />
</main>
<Footer />