feat: Ora mobile responsive (#336)

Co-authored-by: ihor-romaniuk <ihor.romaniuk@raccoongang.com>
This commit is contained in:
vladislavkeblysh
2025-09-24 16:14:42 +03:00
committed by GitHub
parent 4ce7209230
commit f438360fdb
8 changed files with 68 additions and 14 deletions

View File

@@ -7,20 +7,28 @@ const config = createConfig('eslint', {
'import/no-named-as-default-member': 'off', 'import/no-named-as-default-member': 'off',
'import/no-import-module-exports': 'off', 'import/no-import-module-exports': 'off',
'import/no-self-import': 'off', 'import/no-self-import': 'off',
'spaced-comment': ['error', 'always', { 'block': { 'exceptions': ['*'] } }], 'spaced-comment': ['error', 'always', { block: { exceptions: ['*'] } }],
'react-hooks/rules-of-hooks': 'off', 'react-hooks/rules-of-hooks': 'off',
"react/forbid-prop-types": ["error", { "forbid": ["any", "array"] }], // arguable object proptype is use when I do not care about the shape of the object 'react/forbid-prop-types': ['error', { forbid: ['any', 'array'] }], // arguable object proptype is use when I do not care about the shape of the object
'no-import-assign': 'off', 'no-import-assign': 'off',
'no-promise-executor-return': 'off', 'no-promise-executor-return': 'off',
'import/no-cycle': 'off', 'import/no-cycle': 'off',
}, },
overrides: [
{
files: ['**/*.test.{js,jsx,ts,tsx}'],
rules: {
'react/prop-types': 'off',
},
},
],
}); });
config.settings = { config.settings = {
"import/resolver": { 'import/resolver': {
node: { node: {
paths: ["src", "node_modules"], paths: ['src', 'node_modules'],
extensions: [".js", ".jsx"], extensions: ['.js', '.jsx'],
}, },
}, },
}; };

View File

@@ -1,5 +1,6 @@
import { screen } from '@testing-library/react'; import { screen } from '@testing-library/react';
import { selectors } from 'data/redux'; import { selectors } from 'data/redux';
import { renderWithIntl } from './testUtils'; import { renderWithIntl } from './testUtils';
import { App, mapStateToProps } from './App'; import { App, mapStateToProps } from './App';
@@ -22,6 +23,14 @@ jest.mock('containers/DemoWarning', () => function DemoWarning() {
return <div role="alert" data-testid="demo-warning">Demo Warning</div>; return <div role="alert" data-testid="demo-warning">Demo Warning</div>;
}); });
jest.mock('@edx/frontend-component-header', () => ({
LearningHeader: ({ courseTitle, courseNumber, courseOrg }) => (
<div data-testid="header">
Header - {courseTitle} {courseNumber} {courseOrg}
</div>
),
}));
jest.mock('data/redux', () => ({ jest.mock('data/redux', () => ({
selectors: { selectors: {
app: { app: {
@@ -53,7 +62,7 @@ describe('App component', () => {
renderWithIntl(<App {...defaultProps} />); renderWithIntl(<App {...defaultProps} />);
const org = screen.getByText((text) => text.includes('test-org')); const org = screen.getByText((text) => text.includes('test-org'));
expect(org).toBeInTheDocument(); expect(org).toBeInTheDocument();
const title = screen.getByText('Test Course'); const title = screen.getByText((content) => content.includes('Test Course'));
expect(title).toBeInTheDocument(); expect(title).toBeInTheDocument();
}); });

View File

@@ -1,5 +1,6 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { ConfirmModal } from './ConfirmModal'; import { ConfirmModal } from './ConfirmModal';
describe('ConfirmModal', () => { describe('ConfirmModal', () => {
@@ -18,24 +19,40 @@ describe('ConfirmModal', () => {
}); });
it('should not render content when modal is closed', () => { it('should not render content when modal is closed', () => {
render(<ConfirmModal {...props} />); render(
<IntlProvider locale="en">
<ConfirmModal {...props} />
</IntlProvider>,
);
expect(screen.queryByText(props.content)).toBeNull(); expect(screen.queryByText(props.content)).toBeNull();
}); });
it('should display content when modal is open', () => { it('should display content when modal is open', () => {
render(<ConfirmModal {...props} isOpen />); render(
<IntlProvider locale="en">
<ConfirmModal {...props} isOpen />
</IntlProvider>,
);
expect(screen.getByText(props.content)).toBeInTheDocument(); expect(screen.getByText(props.content)).toBeInTheDocument();
}); });
it('should call onCancel when cancel button is clicked', async () => { it('should call onCancel when cancel button is clicked', async () => {
render(<ConfirmModal {...props} isOpen />); render(
<IntlProvider locale="en">
<ConfirmModal {...props} isOpen />
</IntlProvider>,
);
const user = userEvent.setup(); const user = userEvent.setup();
await user.click(screen.getByText(props.cancelText)); await user.click(screen.getByText(props.cancelText));
expect(props.onCancel).toHaveBeenCalledTimes(1); expect(props.onCancel).toHaveBeenCalledTimes(1);
}); });
it('should call onConfirm when confirm button is clicked', async () => { it('should call onConfirm when confirm button is clicked', async () => {
render(<ConfirmModal {...props} isOpen />); render(
<IntlProvider locale="en">
<ConfirmModal {...props} isOpen />
</IntlProvider>,
);
const user = userEvent.setup(); const user = userEvent.setup();
await user.click(screen.getByText(props.confirmText)); await user.click(screen.getByText(props.confirmText));
expect(props.onConfirm).toHaveBeenCalledTimes(1); expect(props.onConfirm).toHaveBeenCalledTimes(1);

View File

@@ -27,7 +27,7 @@ export const InfoPopover = (
return ( return (
<OverlayTrigger <OverlayTrigger
trigger="focus" trigger="focus"
placement="right-end" placement="left-end"
flip flip
overlay={( overlay={(
<Popover id="info-popover" className="overlay-help-popover"> <Popover id="info-popover" className="overlay-help-popover">

View File

@@ -23,4 +23,14 @@ span.pgn__icon.breadcrumb-arrow {
margin-bottom: 0; margin-bottom: 0;
} }
} }
@media (--pgn-size-breakpoint-max-width-xs) {
.badge {
white-space: normal;
}
.pgn__table-actions > div:first-of-type {
z-index: var(--pgn-elevation-modal-zindex) !important;
}
}
} }

View File

@@ -33,3 +33,10 @@
padding-bottom:var(--pgn-spacing-spacer-3); padding-bottom:var(--pgn-spacing-spacer-3);
} }
} }
@media (--pgn-size-breakpoint-max-width-xs) {
.overlay-help-popover {
max-width: calc(100% - 60px) !important;
}
}

View File

@@ -30,7 +30,7 @@ export const ReviewActions = ({
{ gradingStatus && ( { gradingStatus && (
<StatusBadge className="review-actions-status mr-3" status={gradingStatus} /> <StatusBadge className="review-actions-status mr-3" status={gradingStatus} />
)} )}
<span className="small"> <span className="small text-nowrap">
{pointsPossible && ( {pointsPossible && (
<FormattedMessage <FormattedMessage
{...messages.pointsDisplay} {...messages.pointsDisplay}

View File

@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { FullscreenModal } from '@openedx/paragon'; import { FullscreenModal, Truncate, useMediaQuery } from '@openedx/paragon';
import { useIntl } from '@edx/frontend-platform/i18n'; import { useIntl } from '@edx/frontend-platform/i18n';
import LoadingMessage from 'components/LoadingMessage'; import LoadingMessage from 'components/LoadingMessage';
@@ -28,9 +28,12 @@ export const ReviewModal = () => {
isOpen, isOpen,
closeConfirmModalProps, closeConfirmModalProps,
} = hooks.rendererHooks({ dispatch, intl }); } = hooks.rendererHooks({ dispatch, intl });
const isMobile = useMediaQuery({ query: '(max-width: 768px)' });
return ( return (
<FullscreenModal <FullscreenModal
title={title} title={isMobile ? <Truncate lines={3}>{title}</Truncate> : title}
isOpen={isOpen} isOpen={isOpen}
beforeBodyNode={( beforeBodyNode={(
<> <>