Skip to content

Commit 4880068

Browse files
committed
refactor: change lang selector logic
1 parent 907145b commit 4880068

File tree

10 files changed

+226
-151
lines changed

10 files changed

+226
-151
lines changed

README.rst

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,19 @@ This library has the following exports:
8686
* ``messages``: Internationalization messages suitable for use with `@edx/frontend-platform/i18n <https://edx.github.io/frontend-platform/module-Internationalization.html>`_
8787
* ``dist/footer.scss``: A SASS file which contains style information for the component. It should be imported into the micro-frontend's own SCSS file.
8888

89-
<Footer /> component props
90-
==========================
89+
Language Selector
90+
-----------------
9191

92-
* onLanguageSelected: Provides the footer with an event handler for when the user selects a
93-
language from its dropdown.
94-
* supportedLanguages: An array of objects representing available languages. See example below for object shape.
92+
The language selector dropdown is optional and can be enabled by setting the MFE configuration variable ``ENABLE_FOOTER_LANG_SELECTOR`` to ``true``.
93+
Secondly, configue the languages that should be displayed in the dropdown by setting the MFE configuration variable ``SITE_SUPPORTED_LANGUAGES`` to an array of locale languages.
94+
Example:
95+
96+
.. code-block:: python
97+
98+
MFE_CONFIG["EDX_FRONTEND_APP_CONFIG"] = {
99+
"ENABLE_FOOTER_LANG_SELECTOR": True,
100+
"SITE_SUPPORTED_LANGUAGES": ['en', 'es', 'fr', 'pt-br'],
101+
}
95102
96103
Plugin
97104
======
@@ -108,13 +115,7 @@ Component Usage Example::
108115

109116
...
110117

111-
<Footer
112-
onLanguageSelected={(languageCode) => {/* set language */}}
113-
supportedLanguages={[
114-
{ label: 'English', value: 'en'},
115-
{ label: 'Español', value: 'es' },
116-
]}
117-
/>
118+
<Footer />
118119

119120
* `An example of minimal component and messages usage. <https://github.com/openedx/frontend-template-application/blob/3355bb3a96232390e9056f35b06ffa8f105ed7ca/src/index.jsx#L23>`_
120121
* `An example of SCSS file usage. <https://github.com/openedx/frontend-template-application/blob/3cd5485bf387b8c479baf6b02bf59e3061dc3465/src/index.scss#L9>`_

env.config.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// This file is used only for the example application.
2+
const config = {
3+
ENABLE_FOOTER_LANG_SELECTOR: true,
4+
SITE_SUPPORTED_LANGUAGES: ['es', 'en'],
5+
};
6+
7+
export default config;

example/index.jsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,7 @@ subscribe(APP_READY, () => {
1515
authenticatedUser: null,
1616
config: getConfig(),
1717
}}>
18-
<Footer
19-
onLanguageSelected={() => {}}
20-
supportedLanguages={[
21-
{ label: 'English', value: 'en' },
22-
{ label: 'Español', value: 'es' },
23-
]}
24-
/>
18+
<Footer />
2519
</AppContext.Provider>
2620
</AppProvider>,
2721
document.getElementById('root'),

src/components/Footer.jsx

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,10 @@ class SiteFooter extends React.Component {
3535

3636
render() {
3737
const {
38-
supportedLanguages,
39-
onLanguageSelected,
4038
logo,
4139
intl,
4240
} = this.props;
43-
const showLanguageSelector = supportedLanguages.length > 0 && onLanguageSelected;
44-
const { config } = this.context;
45-
41+
const { config, authenticatedUser } = this.context;
4642
return (
4743
<footer
4844
role="contentinfo"
@@ -61,11 +57,14 @@ class SiteFooter extends React.Component {
6157
/>
6258
</a>
6359
<div className="flex-grow-1" />
64-
{showLanguageSelector && (
65-
<LanguageSelector
66-
options={supportedLanguages}
67-
onSubmit={onLanguageSelected}
68-
/>
60+
{config.ENABLE_FOOTER_LANG_SELECTOR && (
61+
<div className="mb-2">
62+
<LanguageSelector
63+
options={config.SITE_SUPPORTED_LANGUAGES}
64+
username={authenticatedUser?.username}
65+
langCookieName={config.LANGUAGE_PREFERENCE_COOKIE_NAME}
66+
/>
67+
</div>
6968
)}
7069
</div>
7170
</footer>
@@ -78,17 +77,10 @@ SiteFooter.contextType = AppContext;
7877
SiteFooter.propTypes = {
7978
intl: intlShape.isRequired,
8079
logo: PropTypes.string,
81-
onLanguageSelected: PropTypes.func,
82-
supportedLanguages: PropTypes.arrayOf(PropTypes.shape({
83-
label: PropTypes.string.isRequired,
84-
value: PropTypes.string.isRequired,
85-
})),
8680
};
8781

8882
SiteFooter.defaultProps = {
8983
logo: undefined,
90-
onLanguageSelected: undefined,
91-
supportedLanguages: [],
9284
};
9385

9486
export default injectIntl(SiteFooter);

src/components/Footer.test.jsx

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
/* eslint-disable react/prop-types */
22
import React, { useMemo } from 'react';
33
import renderer from 'react-test-renderer';
4-
import { render, fireEvent, screen } from '@testing-library/react';
4+
import { fireEvent, render, waitFor } from '@testing-library/react';
5+
import { initializeMockApp } from '@edx/frontend-platform/testing';
56
import { IntlProvider } from '@edx/frontend-platform/i18n';
7+
import { getCookies } from '@edx/frontend-platform/i18n/lib';
68
import { AppContext } from '@edx/frontend-platform/react';
9+
import '@testing-library/jest-dom';
710

811
import Footer from './Footer';
12+
import { patchPreferences, postSetLang } from './LanguageSelector/data';
13+
14+
jest.mock('./LanguageSelector/data', () => ({
15+
patchPreferences: jest.fn(),
16+
postSetLang: jest.fn(),
17+
}));
918

1019
const FooterWithContext = ({ locale = 'es' }) => {
1120
const contextValue = useMemo(() => ({
@@ -27,33 +36,36 @@ const FooterWithContext = ({ locale = 'es' }) => {
2736
);
2837
};
2938

30-
const FooterWithLanguageSelector = ({ languageSelected = () => {} }) => {
39+
const { LANGUAGE_PREFERENCE_COOKIE_NAME } = process.env;
40+
const FooterWithLanguageSelector = ({ authenticatedUser = null }) => {
3141
const contextValue = useMemo(() => ({
32-
authenticatedUser: null,
42+
authenticatedUser,
3343
config: {
44+
ENABLE_FOOTER_LANG_SELECTOR: true,
45+
LANGUAGE_PREFERENCE_COOKIE_NAME,
3446
LOGO_TRADEMARK_URL: process.env.LOGO_TRADEMARK_URL,
3547
LMS_BASE_URL: process.env.LMS_BASE_URL,
48+
SITE_SUPPORTED_LANGUAGES: ['es', 'en'],
3649
},
37-
}), []);
50+
}), [authenticatedUser]);
3851

3952
return (
4053
<IntlProvider locale="en">
4154
<AppContext.Provider
4255
value={contextValue}
4356
>
44-
<Footer
45-
onLanguageSelected={languageSelected}
46-
supportedLanguages={[
47-
{ label: 'English', value: 'en' },
48-
{ label: 'Español', value: 'es' },
49-
]}
50-
/>
57+
<Footer />
5158
</AppContext.Provider>
5259
</IntlProvider>
5360
);
5461
};
5562

5663
describe('<Footer />', () => {
64+
beforeEach(() => {
65+
jest.clearAllMocks();
66+
initializeMockApp();
67+
});
68+
5769
describe('renders correctly', () => {
5870
it('renders without a language selector', () => {
5971
const tree = renderer
@@ -76,21 +88,32 @@ describe('<Footer />', () => {
7688
});
7789

7890
describe('handles language switching', () => {
79-
it('calls onLanguageSelected prop when a language is changed', () => {
80-
const mockHandleLanguageSelected = jest.fn();
81-
render(<FooterWithLanguageSelector languageSelected={mockHandleLanguageSelected} />);
91+
it('calls publish with LOCALE_CHANGED when the language changed', () => {
92+
const setSpy = jest.spyOn(getCookies(), 'set');
93+
const component = render(<FooterWithLanguageSelector />);
8294

83-
fireEvent.submit(screen.getByTestId('site-footer-submit-btn'), {
84-
target: {
85-
elements: {
86-
'site-footer-language-select': {
87-
value: 'es',
88-
},
89-
},
90-
},
91-
});
95+
expect(component.queryByRole('button')).toBeInTheDocument();
9296

93-
expect(mockHandleLanguageSelected).toHaveBeenCalledWith('es');
97+
const langDropdown = component.queryByRole('button');
98+
fireEvent.click(langDropdown);
99+
fireEvent.click(component.queryByText('Español'));
100+
101+
expect(setSpy).toHaveBeenCalledWith(LANGUAGE_PREFERENCE_COOKIE_NAME, 'es');
102+
});
103+
it('update the lang preference for an autheticathed user', async () => {
104+
const userData = { username: 'test-user' };
105+
const component = render(<FooterWithLanguageSelector authenticatedUser={userData} />);
106+
107+
expect(component.queryByRole('button')).toBeInTheDocument();
108+
109+
const langDropdown = component.queryByRole('button');
110+
fireEvent.click(langDropdown);
111+
fireEvent.click(component.queryByText('Español'));
112+
113+
await waitFor(() => {
114+
expect(patchPreferences).toHaveBeenCalledWith(userData.username, { prefLang: 'es' });
115+
expect(postSetLang).toHaveBeenCalledWith('es');
116+
});
94117
});
95118
});
96119
});

src/components/LanguageSelector.jsx

Lines changed: 0 additions & 58 deletions
This file was deleted.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { getConfig } from '@edx/frontend-platform';
2+
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
3+
import { convertKeyNames, snakeCaseObject } from '@edx/frontend-platform/utils';
4+
5+
export async function patchPreferences(username, params) {
6+
let processedParams = snakeCaseObject(params);
7+
processedParams = convertKeyNames(processedParams, {
8+
pref_lang: 'pref-lang',
9+
});
10+
11+
await getAuthenticatedHttpClient()
12+
.patch(`${getConfig().LMS_BASE_URL}/api/user/v1/preferences/${username}`, processedParams, {
13+
headers: { 'Content-Type': 'application/merge-patch+json' },
14+
});
15+
16+
return params;
17+
}
18+
19+
export async function postSetLang(code) {
20+
const formData = new FormData();
21+
const requestConfig = {
22+
headers: {
23+
Accept: 'application/json',
24+
'X-Requested-With': 'XMLHttpRequest',
25+
},
26+
};
27+
const url = `${getConfig().LMS_BASE_URL}/i18n/setlang/`;
28+
formData.append('language', code);
29+
30+
await getAuthenticatedHttpClient()
31+
.post(url, formData, requestConfig);
32+
}

0 commit comments

Comments
 (0)