Skip to content

Commit

Permalink
Lk/learning language selection (#1299)
Browse files Browse the repository at this point in the history
* feat: add language selection

chore: update tests so we have less error message

test: update test

* test: update tests

* chore: remove duplicate translation

* chore: lint for console

* chore: remove comments

* chore: make sure the affect url frame refresh after the language selection change
  • Loading branch information
leangseu-edx authored Feb 28, 2024
1 parent ff7366d commit 60ae8b3
Show file tree
Hide file tree
Showing 41 changed files with 9,818 additions and 11,039 deletions.
25 changes: 23 additions & 2 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const { createConfig } = require('@edx/frontend-build');

module.exports = createConfig('jest', {
const config = createConfig('jest', {
setupFilesAfterEnv: [
'<rootDir>/src/setupTest.js',
],
Expand All @@ -16,6 +16,27 @@ module.exports = createConfig('jest', {
'react-markdown': '<rootDir>/node_modules/react-markdown/react-markdown.min.js',
},
testTimeout: 30000,
globalSetup: "./global-setup.js",
verbose: true,
testEnvironment: 'jsdom',
globalSetup: "./global-setup.js"
});

// delete config.testURL;

config.reporters = [...(config.reporters || []), ["jest-console-group-reporter", {
// change this setting if need to see less details for each test
// reportType: "summary" | "details",
// enable: true | false,
afterEachTest: {
enable: true,
filePaths: false,
reportType: "details",
},
afterAllTests: {
reportType: "summary",
enable: true,
filePaths: true,
},
}]];

module.exports = config;
20,012 changes: 9,012 additions & 11,000 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
"@edx/frontend-component-footer": "12.2.1",
"@edx/frontend-component-header": "4.6.0",
"@edx/frontend-lib-special-exams": "2.29.0",
"@edx/frontend-lib-learning-assistant": "^1.24.0",
"@edx/frontend-lib-special-exams": "2.29.0",
"@edx/frontend-platform": "5.5.2",
"@edx/openedx-atlas": "^0.6.0",
"@edx/paragon": "20.46.0",
Expand All @@ -48,6 +48,7 @@
"classnames": "2.3.2",
"core-js": "3.22.2",
"history": "5.3.0",
"jest": "^26.6.3",
"joi": "^17.11.0",
"js-cookie": "3.0.5",
"lodash.camelcase": "4.3.0",
Expand Down Expand Up @@ -79,7 +80,7 @@
"copy-webpack-plugin": "^11.0.0",
"es-check": "6.2.1",
"husky": "7.0.4",
"jest": "29.5.0",
"jest-console-group-reporter": "^1.0.1",
"rosie": "2.1.1"
}
}
2 changes: 1 addition & 1 deletion src/course-home/courseware-search/CoursewareSearch.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ const CoursewareSearch = ({ intl, ...sectionProps }) => {
</div>
<div className="courseware-search__outer-content">
<div className="courseware-search__content">
<h1 class="h2">{intl.formatMessage(messages.searchModuleTitle)}</h1>
<h1 className="h2">{intl.formatMessage(messages.searchModuleTitle)}</h1>
<CoursewareSearchForm
searchTerm={searchKeyword}
onSubmit={handleSubmit}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ const CertificateStatus = ({ intl }) => {
certAvailabilityDate = <FormattedDate value={certificateAvailableDate} day="numeric" month="long" year="numeric" />;
body = (
<FormattedMessage
id="courseCelebration.certificateBody.notAvailable.endDate"
id="progress.certificateStatus.notAvailable.endDate"
defaultMessage="This course ends on {endDate}. Final grades and any earned certificates are
scheduled to be available after {certAvailabilityDate}."
description="This shown for leaner when they are eligible for certifcate but it't not available yet, it could because leaners just finished the course quickly!"
Expand Down
5 changes: 0 additions & 5 deletions src/course-home/progress-tab/certificate-status/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,6 @@ const messages = defineMessages({
defaultMessage: 'Your certificate is available!',
description: 'Header text when the certifcate is available',
},
downloadableBody: {
id: 'progress.certificateStatus.downloadableBody',
defaultMessage: 'Showcase your accomplishment on LinkedIn or your resumé today. You can download your certificate now and access it any time from your Dashboard and Profile.',
description: 'Recommending an action for learner when course certificate is available',
},
viewableButton: {
id: 'progress.certificateStatus.viewableButton',
defaultMessage: 'View my certificate',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const CompleteDonutSegment = ({ completePercentage, intl, lockedPercentage }) =>
show={showCompletePopover}
placement="top"
overlay={(
<Popover aria-hidden="true">
<Popover id="complete-content-tooltip-popover" aria-hidden="true">
<Popover.Content>
{intl.formatMessage(messages.completeContentTooltip)}
</Popover.Content>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const IncompleteDonutSegment = ({ incompletePercentage, intl }) => {
show={showIncompletePopover}
placement="top"
overlay={(
<Popover aria-hidden="true">
<Popover id="incomplete-tooltip-popover" aria-hidden="true">
<Popover.Content>
{intl.formatMessage(messages.incompleteContentTooltip)}
</Popover.Content>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const LockedDonutSegment = ({ intl, lockedPercentage }) => {
show={showLockedPopover}
placement="top"
overlay={(
<Popover aria-hidden="true">
<Popover id="locked-tooltip-popover" aria-hidden="true">
<Popover.Content>
{intl.formatMessage(messages.lockedContentTooltip)}
</Popover.Content>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ const ProblemScoreDrawer = ({ intl, problemScores, subsection }) => {
<span id="problem-score-label" className="col-auto p-0">{intl.formatMessage(messages.problemScoreLabel)}</span>
<div className={classNames('col', 'p-0', { 'greyed-out': !subsection.learnerHasAccess })}>
<ul className="list-unstyled row w-100 m-0" aria-labelledby="problem-score-label">
{problemScores.map(problemScore => (
<li className="ml-3">{problemScore.earned}{isLocaleRtl ? '\\' : '/'}{problemScore.possible}</li>
{problemScores.map((problemScore, i) => (
// eslint-disable-next-line react/no-array-index-key
<li key={i} className="ml-3">{problemScore.earned}{isLocaleRtl ? '\\' : '/'}{problemScore.possible}</li>
))}
</ul>
</div>
Expand Down
1 change: 1 addition & 0 deletions src/courseware/CoursewareContainer.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ describe('CoursewareContainer', () => {
<Routes>
{DECODE_ROUTES.COURSEWARE.map((route) => (
<Route
key={route}
path={route}
element={<CoursewareContainer />}
/>
Expand Down
3 changes: 2 additions & 1 deletion src/courseware/course/Course.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ describe('Course', () => {
expect(await screen.findByText('Loading learning sequence...')).toBeInTheDocument();

expect(screen.queryByRole('alert')).not.toBeInTheDocument();
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
// one from translation product tour
expect(screen.getAllByRole('dialog')).toHaveLength(1);
expect(screen.queryByRole('button', { name: 'Learn About Verified Certificates' })).not.toBeInTheDocument();

loadUnit();
Expand Down
5 changes: 4 additions & 1 deletion src/courseware/course/CourseBreadcrumbs.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const CourseBreadcrumb = ({
<Menu>
{content.map((item) => (
<JumpNavMenuItem
key={item.label}
isDefault={item.default}
sequences={item.sequences}
courseId={courseId}
Expand Down Expand Up @@ -169,8 +170,10 @@ const CourseBreadcrumbs = ({
/>
</Link>
</li>
{links.map((content) => (
{links.map((content, i) => (
<CourseBreadcrumb
// eslint-disable-next-line react/no-array-index-key
key={i}
courseId={courseId}
sequenceId={sequenceId}
content={content}
Expand Down
3 changes: 2 additions & 1 deletion src/courseware/course/sequence/Sequence.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ describe('Sequence', () => {
render(<Sequence {...mockData} />, { wrapWithRouter: true });
expect(await screen.findByText('Loading learning sequence...')).toBeInTheDocument();
// `Previous`, `Bookmark` and `Close Tray` buttons
expect(screen.getAllByRole('button')).toHaveLength(3);
// `Change Language`, `Dismiss` and `Try it` buttons from translation selection.
expect(screen.getAllByRole('button')).toHaveLength(6);
// Renders `Next` button plus one button for each unit.
expect(screen.getAllByRole('link')).toHaveLength(1 + unitBlocks.length);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,20 @@ exports[`Unit component output snapshot: not bookmarked, do not show content 1`]
<div
className="unit"
>
<h1
className="mb-0 h3"
<div
className="mb-0"
>
unit-title
</h1>
<h3
className="h3"
>
unit-title
</h3>
<TranslationSelection
courseId="test-course-id"
selectedLanguage="en"
setSelectedLanguage={[MockFunction setSelectedLanguage]}
/>
</div>
<h2
className="sr-only"
>
Expand Down
24 changes: 16 additions & 8 deletions src/courseware/course/sequence/Unit/index.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import PropTypes from 'prop-types';
import React from 'react';
import React, { useMemo } from 'react';

import { AppContext } from '@edx/frontend-platform/react';
import { useIntl } from '@edx/frontend-platform/i18n';
Expand All @@ -13,12 +13,11 @@ import UnitSuspense from './UnitSuspense';
import { modelKeys, views } from './constants';
import { useExamAccess, useShouldDisplayHonorCode } from './hooks';
import { getIFrameUrl } from './urls';
import TranslationSelection from './translation-selection';
import useSelectLanguage from './translation-selection/useSelectLanguage';

const Unit = ({
courseId,
format,
onLoaded,
id,
courseId, format, onLoaded, id,
}) => {
const { formatMessage } = useIntl();
const { authenticatedUser } = React.useContext(AppContext);
Expand All @@ -27,17 +26,26 @@ const Unit = ({
const unit = useModel(modelKeys.units, id);
const isProcessing = unit.bookmarkedUpdateState === 'loading';
const view = authenticatedUser ? views.student : views.public;
const { selectedLanguage, setSelectedLanguage } = useSelectLanguage(courseId);

const iframeUrl = getIFrameUrl({
const iframeUrl = useMemo(() => getIFrameUrl({
id,
view,
format,
examAccess,
});
translateLanguage: selectedLanguage,
}), [id, view, format, examAccess, selectedLanguage]);

return (
<div className="unit">
<h1 className="mb-0 h3">{unit.title}</h1>
<div className="mb-0">
<h3 className="h3">{unit.title}</h3>
<TranslationSelection
courseId={courseId}
selectedLanguage={selectedLanguage}
setSelectedLanguage={setSelectedLanguage}
/>
</div>
<h2 className="sr-only">{formatMessage(messages.headerPlaceholder)}</h2>
<BookmarkButton
unitId={unit.id}
Expand Down
5 changes: 5 additions & 0 deletions src/courseware/course/sequence/Unit/index.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ jest.mock('./ContentIFrame', () => 'ContentIFrame');
jest.mock('./UnitSuspense', () => 'UnitSuspense');
jest.mock('../honor-code', () => 'HonorCode');
jest.mock('../lock-paywall', () => 'LockPaywall');
jest.mock('./translation-selection', () => 'TranslationSelection');
jest.mock('./translation-selection/useSelectLanguage', () => () => ({
selectedLanguage: 'en',
setSelectedLanguage: jest.fn().mockName('setSelectedLanguage'),
}));

jest.mock('../../../../generic/model-store', () => ({
useModel: jest.fn(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React from 'react';
import PropTypes from 'prop-types';

import { useIntl } from '@edx/frontend-platform/i18n';
import {
StandardModal,
ActionRow,
Button,
Icon,
ListBox,
ListBoxOption,
} from '@edx/paragon';
import { Check } from '@edx/paragon/icons';

import useTranslationModal from './useTranslationModal';
import { languages } from './useSelectLanguage';
import messages, { languageMessages } from './messages';

import './TranslationModal.scss';

const TranslationModal = ({
isOpen, close, selectedLanguage, setSelectedLanguage,
}) => {
const { formatMessage } = useIntl();
const {
selectedIndex,
setSelectedIndex,
onSubmit,
} = useTranslationModal({ selectedLanguage, setSelectedLanguage, close });

return (
<StandardModal
title={formatMessage(messages.languageSelectionModalTitle)}
isOpen={isOpen}
onClose={close}
footerNode={(
<ActionRow>
<ActionRow.Spacer />
<Button variant="tertiary" onClick={close}>
{formatMessage(messages.cancelButtonText)}
</Button>
<Button onClick={onSubmit}>{formatMessage(messages.submitButtonText)}</Button>
</ActionRow>
)}
>
<ListBox className="listbox-container">
{languages.map(([key, value], index) => (
<ListBoxOption
className="d-flex justify-content-between"
key={key}
selectedOptionIndex={selectedIndex}
onSelect={() => setSelectedIndex(index)}
>
{formatMessage(languageMessages[value])}
{selectedIndex === index && <Icon src={Check} />}
</ListBoxOption>
))}
</ListBox>
</StandardModal>
);
};

TranslationModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
close: PropTypes.func.isRequired,
selectedLanguage: PropTypes.string.isRequired,
setSelectedLanguage: PropTypes.func.isRequired,
};

export default TranslationModal;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.listbox-container {
max-height: 400px;

:last-child {
margin-bottom: 5px;
}
}
Loading

0 comments on commit 60ae8b3

Please sign in to comment.