Bw/design fixes (#49)
This commit is contained in:
@@ -31,7 +31,11 @@ export const CourseCardContent = ({ cardId, orientation }) => {
|
||||
<Card.Header
|
||||
title={(
|
||||
<h3>
|
||||
<a href={homeUrl} data-testid="CourseCardTitle">
|
||||
<a
|
||||
href={homeUrl}
|
||||
className="course-card-title"
|
||||
data-testid="CourseCardTitle"
|
||||
>
|
||||
{courseName}
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useDispatch } from 'react-redux';
|
||||
import { Button } from '@edx/paragon';
|
||||
|
||||
import useCardDetailsData from './hooks';
|
||||
import './index.scss';
|
||||
|
||||
export const CourseCardDetails = ({ cardId }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
@import "~@edx/brand/paragon/variables";
|
||||
@import "~@edx/paragon/scss/core/core";
|
||||
@import "~@edx/brand/paragon/overrides";
|
||||
|
||||
a.course-card-title {
|
||||
color: $black;
|
||||
}
|
||||
@@ -22,6 +22,7 @@ exports[`CourseCardContent snapshot orientation horizontal 1`] = `
|
||||
title={
|
||||
<h3>
|
||||
<a
|
||||
className="course-card-title"
|
||||
data-testid="CourseCardTitle"
|
||||
href="test-home-url"
|
||||
>
|
||||
@@ -75,6 +76,7 @@ exports[`CourseCardContent snapshot orientation vertical 1`] = `
|
||||
title={
|
||||
<h3>
|
||||
<a
|
||||
className="course-card-title"
|
||||
data-testid="CourseCardTitle"
|
||||
href="test-home-url"
|
||||
>
|
||||
|
||||
@@ -8,7 +8,7 @@ exports[`CourseList snapshots with filters 1`] = `
|
||||
id="course-list-heading-container"
|
||||
>
|
||||
<h2
|
||||
className="my-3"
|
||||
className="mb-4.5"
|
||||
>
|
||||
My Courses
|
||||
</h2>
|
||||
@@ -42,7 +42,7 @@ exports[`CourseList snapshots with multiple courses and pages 1`] = `
|
||||
id="course-list-heading-container"
|
||||
>
|
||||
<h2
|
||||
className="my-3"
|
||||
className="mb-4.5"
|
||||
>
|
||||
My Courses
|
||||
</h2>
|
||||
@@ -87,7 +87,7 @@ exports[`CourseList snapshots with no filters 1`] = `
|
||||
id="course-list-heading-container"
|
||||
>
|
||||
<h2
|
||||
className="my-3"
|
||||
className="mb-4.5"
|
||||
>
|
||||
My Courses
|
||||
</h2>
|
||||
|
||||
@@ -27,7 +27,7 @@ export const CourseList = () => {
|
||||
return (
|
||||
<div className="course-list-container">
|
||||
<div id="course-list-heading-container">
|
||||
<h2 className="my-3">{formatMessage(messages.myCourses)}</h2>
|
||||
<h2 className="mb-4.5">{formatMessage(messages.myCourses)}</h2>
|
||||
<div id="course-filter-controls-container" className="text-right">
|
||||
<CourseFilterControls {...filterOptions} />
|
||||
</div>
|
||||
|
||||
37
src/containers/Dashboard/LoadedView.jsx
Normal file
37
src/containers/Dashboard/LoadedView.jsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import { Container, Col, Row } from '@edx/paragon';
|
||||
|
||||
import CourseList from 'containers/CourseList';
|
||||
import WidgetSidebar from 'containers/WidgetSidebar';
|
||||
import hooks from './hooks';
|
||||
|
||||
export const columnConfig = {
|
||||
courseList: {
|
||||
xs: { span: 12, offset: 0 },
|
||||
sm: { span: 8, offset: 2 },
|
||||
md: { span: 12, offset: 0 },
|
||||
lg: { span: 10, offset: 1 },
|
||||
xl: { span: 8, offset: 0 },
|
||||
},
|
||||
sidebar: { md: 12, xl: 4 },
|
||||
};
|
||||
|
||||
export const LoadedView = () => {
|
||||
const isCollapsed = hooks.useIsDashboardCollapsed();
|
||||
|
||||
return (
|
||||
<Container fluid size="xl">
|
||||
<Row>
|
||||
<Col {...columnConfig.courseList} className="p-0 px-4">
|
||||
<CourseList />
|
||||
</Col>
|
||||
<Col {...columnConfig.sidebar} className="p-0 pr-4 pl-1">
|
||||
{!isCollapsed && (<h2 className="mb-4.5 display-block"> </h2>)}
|
||||
<WidgetSidebar />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoadedView;
|
||||
58
src/containers/Dashboard/LoadedView.test.jsx
Normal file
58
src/containers/Dashboard/LoadedView.test.jsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { shallow } from 'enzyme';
|
||||
import { Col, Row } from '@edx/paragon';
|
||||
|
||||
import CourseList from 'containers/CourseList';
|
||||
import WidgetSidebar from 'containers/WidgetSidebar';
|
||||
|
||||
import hooks from './hooks';
|
||||
import LoadedView, { columnConfig } from './LoadedView';
|
||||
|
||||
jest.mock('./hooks', () => ({
|
||||
useIsDashboardCollapsed: jest.fn(() => true),
|
||||
}));
|
||||
|
||||
describe('LoadedView', () => {
|
||||
const testColumns = () => {
|
||||
it('loads courseList and sidebar column layout', () => {
|
||||
const columns = shallow(<LoadedView />).find(Row).find(Col);
|
||||
Object.keys(columnConfig.courseList).forEach(size => {
|
||||
expect(columns.at(0).props()[size]).toEqual(columnConfig.courseList[size]);
|
||||
});
|
||||
Object.keys(columnConfig.sidebar).forEach(size => {
|
||||
expect(columns.at(1).props()[size]).toEqual(columnConfig.sidebar[size]);
|
||||
});
|
||||
});
|
||||
it('displays CourseList in first column', () => {
|
||||
const columns = shallow(<LoadedView />).find(Row).find(Col);
|
||||
expect(columns.at(0).find(CourseList).length).toEqual(1);
|
||||
});
|
||||
it('displays WidgetSidebar in second column', () => {
|
||||
const columns = shallow(<LoadedView />).find(Row).find(Col);
|
||||
expect(columns.at(1).find(WidgetSidebar).length).toEqual(1);
|
||||
});
|
||||
};
|
||||
const testSnapshot = () => {
|
||||
test('snapshot', () => {
|
||||
expect(shallow(<LoadedView />)).toMatchSnapshot();
|
||||
});
|
||||
};
|
||||
describe('collapsed', () => {
|
||||
testColumns();
|
||||
testSnapshot();
|
||||
it('does not show spacer component above widget sidebar', () => {
|
||||
const columns = shallow(<LoadedView />).find(Col);
|
||||
expect(columns.at(1).find('h2').length).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('not collapsed', () => {
|
||||
beforeEach(() => { hooks.useIsDashboardCollapsed.mockReturnValueOnce(false); });
|
||||
testColumns();
|
||||
testSnapshot();
|
||||
it('shows a blank (nbsp) h2 spacer component above widget sidebar', () => {
|
||||
const columns = shallow(<LoadedView />).find(Col);
|
||||
// nonbreaking space equivalent
|
||||
expect(columns.at(1).find('h2').text()).toEqual('\xA0');
|
||||
});
|
||||
});
|
||||
});
|
||||
20
src/containers/Dashboard/LoadingView.jsx
Normal file
20
src/containers/Dashboard/LoadingView.jsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import { Spinner } from '@edx/paragon';
|
||||
|
||||
import hooks from './hooks';
|
||||
|
||||
export const LoadingView = () => {
|
||||
const { spinnerScreenReaderText } = hooks.useDashboardMessages();
|
||||
|
||||
return (
|
||||
<div className="course-list-loading">
|
||||
<Spinner
|
||||
animation="border"
|
||||
className="mie-3"
|
||||
screenReaderText={spinnerScreenReaderText}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoadingView;
|
||||
23
src/containers/Dashboard/LoadingView.test.jsx
Normal file
23
src/containers/Dashboard/LoadingView.test.jsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { shallow } from 'enzyme';
|
||||
import { Spinner } from '@edx/paragon';
|
||||
|
||||
import hooks from './hooks';
|
||||
import LoadingView from './LoadingView';
|
||||
|
||||
jest.mock('./hooks', () => ({
|
||||
useDashboardMessages: jest.fn(),
|
||||
}));
|
||||
|
||||
const spinnerScreenReaderText = 'test-sr-text';
|
||||
describe('LoadingView', () => {
|
||||
beforeEach(() => {
|
||||
hooks.useDashboardMessages.mockReturnValueOnce({ spinnerScreenReaderText });
|
||||
});
|
||||
test('snapshot', () => {
|
||||
expect(shallow(<LoadingView />)).toMatchSnapshot();
|
||||
});
|
||||
it('renders spinner component with associated screen reader text', () => {
|
||||
const wrapper = shallow(<LoadingView />);
|
||||
expect(wrapper.find(Spinner).props().screenReaderText).toEqual(spinnerScreenReaderText);
|
||||
});
|
||||
});
|
||||
110
src/containers/Dashboard/__snapshots__/LoadedView.test.jsx.snap
Normal file
110
src/containers/Dashboard/__snapshots__/LoadedView.test.jsx.snap
Normal file
@@ -0,0 +1,110 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`LoadedView collapsed snapshot 1`] = `
|
||||
<Container
|
||||
fluid={true}
|
||||
size="xl"
|
||||
>
|
||||
<Row>
|
||||
<Col
|
||||
className="p-0 px-4"
|
||||
lg={
|
||||
Object {
|
||||
"offset": 1,
|
||||
"span": 10,
|
||||
}
|
||||
}
|
||||
md={
|
||||
Object {
|
||||
"offset": 0,
|
||||
"span": 12,
|
||||
}
|
||||
}
|
||||
sm={
|
||||
Object {
|
||||
"offset": 2,
|
||||
"span": 8,
|
||||
}
|
||||
}
|
||||
xl={
|
||||
Object {
|
||||
"offset": 0,
|
||||
"span": 8,
|
||||
}
|
||||
}
|
||||
xs={
|
||||
Object {
|
||||
"offset": 0,
|
||||
"span": 12,
|
||||
}
|
||||
}
|
||||
>
|
||||
<CourseList />
|
||||
</Col>
|
||||
<Col
|
||||
className="p-0 pr-4 pl-1"
|
||||
md={12}
|
||||
xl={4}
|
||||
>
|
||||
<WidgetSidebar />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
`;
|
||||
|
||||
exports[`LoadedView not collapsed snapshot 1`] = `
|
||||
<Container
|
||||
fluid={true}
|
||||
size="xl"
|
||||
>
|
||||
<Row>
|
||||
<Col
|
||||
className="p-0 px-4"
|
||||
lg={
|
||||
Object {
|
||||
"offset": 1,
|
||||
"span": 10,
|
||||
}
|
||||
}
|
||||
md={
|
||||
Object {
|
||||
"offset": 0,
|
||||
"span": 12,
|
||||
}
|
||||
}
|
||||
sm={
|
||||
Object {
|
||||
"offset": 2,
|
||||
"span": 8,
|
||||
}
|
||||
}
|
||||
xl={
|
||||
Object {
|
||||
"offset": 0,
|
||||
"span": 8,
|
||||
}
|
||||
}
|
||||
xs={
|
||||
Object {
|
||||
"offset": 0,
|
||||
"span": 12,
|
||||
}
|
||||
}
|
||||
>
|
||||
<CourseList />
|
||||
</Col>
|
||||
<Col
|
||||
className="p-0 pr-4 pl-1"
|
||||
md={12}
|
||||
xl={4}
|
||||
>
|
||||
<h2
|
||||
className="mb-4.5 display-block"
|
||||
>
|
||||
|
||||
</h2>
|
||||
<WidgetSidebar />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
`;
|
||||
@@ -0,0 +1,13 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`LoadingView snapshot 1`] = `
|
||||
<div
|
||||
className="course-list-loading"
|
||||
>
|
||||
<Spinner
|
||||
animation="border"
|
||||
className="mie-3"
|
||||
screenReaderText="test-sr-text"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
@@ -1,128 +1,57 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Dashboard snapshots courses loaded 1`] = `
|
||||
exports[`Dashboard snapshots courses loaded, show select session modal, no available dashboards snapshot 1`] = `
|
||||
<div
|
||||
className="d-flex flex-column p-2"
|
||||
className="d-flex flex-column p-2 pt-3"
|
||||
id="dashboard-container"
|
||||
>
|
||||
<h1
|
||||
className="sr-only"
|
||||
>
|
||||
Learner Home
|
||||
</h1>
|
||||
<Container
|
||||
fluid={true}
|
||||
size="xl"
|
||||
>
|
||||
<Row>
|
||||
<Col
|
||||
className="p-0 px-4"
|
||||
lg={
|
||||
Object {
|
||||
"offset": 1,
|
||||
"span": 10,
|
||||
}
|
||||
}
|
||||
md={
|
||||
Object {
|
||||
"offset": 0,
|
||||
"span": 12,
|
||||
}
|
||||
}
|
||||
sm={
|
||||
Object {
|
||||
"offset": 2,
|
||||
"span": 8,
|
||||
}
|
||||
}
|
||||
xl={
|
||||
Object {
|
||||
"offset": 0,
|
||||
"span": 8,
|
||||
}
|
||||
}
|
||||
xs={
|
||||
Object {
|
||||
"offset": 0,
|
||||
"span": 12,
|
||||
}
|
||||
}
|
||||
>
|
||||
<CourseList />
|
||||
</Col>
|
||||
<Col
|
||||
className="p-0 pr-4 pl-1"
|
||||
md={12}
|
||||
xl={4}
|
||||
>
|
||||
<WidgetSidebar />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Dashboard snapshots courses still loading 1`] = `
|
||||
<div
|
||||
className="d-flex flex-column p-2"
|
||||
id="dashboard-container"
|
||||
>
|
||||
<h1
|
||||
className="sr-only"
|
||||
>
|
||||
Learner Home
|
||||
test-page-title
|
||||
</h1>
|
||||
<SelectSessionModal />
|
||||
<div
|
||||
className="course-list-loading"
|
||||
id="dashboard-content"
|
||||
>
|
||||
<Spinner
|
||||
animation="border"
|
||||
className="mie-3"
|
||||
screenReaderText="Loading..."
|
||||
/>
|
||||
<LoadedView />
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Dashboard snapshots there are available dashboards 1`] = `
|
||||
exports[`Dashboard snapshots courses still loading snapshot 1`] = `
|
||||
<div
|
||||
className="d-flex flex-column p-2"
|
||||
className="d-flex flex-column p-2 pt-3"
|
||||
id="dashboard-container"
|
||||
>
|
||||
<h1
|
||||
className="sr-only"
|
||||
>
|
||||
Learner Home
|
||||
test-page-title
|
||||
</h1>
|
||||
<div
|
||||
id="dashboard-content"
|
||||
>
|
||||
<LoadingView />
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Dashboard snapshots there are no courses, there ARE available dashboards snapshot 1`] = `
|
||||
<div
|
||||
className="d-flex flex-column p-2 pt-3"
|
||||
id="dashboard-container"
|
||||
>
|
||||
<h1
|
||||
className="sr-only"
|
||||
>
|
||||
test-page-title
|
||||
</h1>
|
||||
<EnterpriseDashboardModal />
|
||||
<EmptyCourse />
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Dashboard snapshots there are no courses 1`] = `
|
||||
<div
|
||||
className="d-flex flex-column p-2"
|
||||
id="dashboard-container"
|
||||
>
|
||||
<h1
|
||||
className="sr-only"
|
||||
<div
|
||||
id="dashboard-content"
|
||||
>
|
||||
Learner Home
|
||||
</h1>
|
||||
<EmptyCourse />
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Dashboard snapshots there is a select session modal 1`] = `
|
||||
<div
|
||||
className="d-flex flex-column p-2"
|
||||
id="dashboard-container"
|
||||
>
|
||||
<h1
|
||||
className="sr-only"
|
||||
>
|
||||
Learner Home
|
||||
</h1>
|
||||
<EmptyCourse />
|
||||
<EmptyCourse />
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
31
src/containers/Dashboard/hooks.js
Normal file
31
src/containers/Dashboard/hooks.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import { useWindowSize, breakpoints } from '@edx/paragon';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { thunkActions } from 'data/redux';
|
||||
|
||||
import appMessages from 'messages';
|
||||
|
||||
export const useIsDashboardCollapsed = () => {
|
||||
const { width } = useWindowSize();
|
||||
return width < breakpoints.large.maxWidth;
|
||||
};
|
||||
|
||||
export const useInitializeDashboard = () => {
|
||||
const dispatch = useDispatch();
|
||||
React.useEffect(() => { dispatch(thunkActions.app.initialize()); }, [dispatch]);
|
||||
};
|
||||
|
||||
export const useDashboardMessages = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
return {
|
||||
spinnerScreenReaderText: formatMessage(appMessages.loadingSR),
|
||||
pageTitle: formatMessage(appMessages.pageTitle),
|
||||
};
|
||||
};
|
||||
|
||||
export default {
|
||||
useIsDashboardCollapsed,
|
||||
useInitializeDashboard,
|
||||
useDashboardMessages,
|
||||
};
|
||||
66
src/containers/Dashboard/hooks.test.js
Normal file
66
src/containers/Dashboard/hooks.test.js
Normal file
@@ -0,0 +1,66 @@
|
||||
import React from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useWindowSize, breakpoints } from '@edx/paragon';
|
||||
|
||||
import { thunkActions } from 'data/redux';
|
||||
|
||||
import appMessages from 'messages';
|
||||
import * as hooks from './hooks';
|
||||
|
||||
jest.mock('@edx/paragon', () => ({
|
||||
useWindowSize: jest.fn(),
|
||||
breakpoints: {},
|
||||
}));
|
||||
|
||||
jest.mock('data/redux', () => ({
|
||||
thunkActions: {
|
||||
app: {
|
||||
initialize: jest.fn(() => 'thunkActions.app.initialize'),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
describe('CourseCard hooks', () => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('useIsDashboardCollapsed', () => {
|
||||
it('returns true iff windowSize width is below the xl breakpoint', () => {
|
||||
useWindowSize.mockReturnValueOnce({ width: 20 });
|
||||
breakpoints.large = { maxWidth: 30 };
|
||||
expect(hooks.useIsDashboardCollapsed()).toEqual(true);
|
||||
useWindowSize.mockReturnValueOnce({ width: 40 });
|
||||
expect(hooks.useIsDashboardCollapsed()).toEqual(false);
|
||||
useWindowSize.mockReturnValueOnce({ width: 40 });
|
||||
expect(hooks.useIsDashboardCollapsed()).toEqual(false);
|
||||
});
|
||||
});
|
||||
describe('useInitializeDashboard', () => {
|
||||
it('dispatches initialize thunk action on component load', () => {
|
||||
const dispatch = useDispatch();
|
||||
hooks.useInitializeDashboard();
|
||||
const [cb, prereqs] = React.useEffect.mock.calls[0];
|
||||
expect(prereqs).toEqual([dispatch]);
|
||||
expect(dispatch).not.toHaveBeenCalled();
|
||||
cb();
|
||||
expect(dispatch).toHaveBeenCalledWith(thunkActions.app.initialize());
|
||||
});
|
||||
});
|
||||
describe('useDashboardMessages', () => {
|
||||
it('returns spinner screen reader text', () => {
|
||||
expect(hooks.useDashboardMessages().spinnerScreenReaderText).toEqual(
|
||||
formatMessage(appMessages.loadingSR),
|
||||
);
|
||||
});
|
||||
it('returns page title', () => {
|
||||
expect(hooks.useDashboardMessages().pageTitle).toEqual(
|
||||
formatMessage(appMessages.pageTitle),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,76 +1,38 @@
|
||||
import React from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import {
|
||||
Container,
|
||||
Col,
|
||||
Row,
|
||||
Spinner,
|
||||
} from '@edx/paragon';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import {
|
||||
thunkActions,
|
||||
hooks as appHooks,
|
||||
} from 'data/redux';
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import { RequestKeys } from 'data/constants/requests';
|
||||
|
||||
import CourseList from 'containers/CourseList';
|
||||
import WidgetSidebar from 'containers/WidgetSidebar';
|
||||
import EmptyCourse from 'containers/EmptyCourse';
|
||||
import SelectSessionModal from 'containers/SelectSessionModal';
|
||||
import EnterpriseDashboardModal from 'containers/EnterpriseDashboardModal';
|
||||
import SelectSessionModal from 'containers/SelectSessionModal';
|
||||
|
||||
import appMessages from 'messages';
|
||||
import LoadingView from './LoadingView';
|
||||
import LoadedView from './LoadedView';
|
||||
import hooks from './hooks';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
export const Dashboard = () => {
|
||||
const dispatch = useDispatch();
|
||||
React.useEffect(
|
||||
() => { dispatch(thunkActions.app.initialize()); },
|
||||
[dispatch],
|
||||
);
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
hooks.useInitializeDashboard();
|
||||
const { pageTitle } = hooks.useDashboardMessages();
|
||||
const hasCourses = appHooks.useHasCourses();
|
||||
const hasAvailableDashboards = appHooks.useHasAvailableDashboards();
|
||||
const showSelectSessionModal = appHooks.useShowSelectSessionModal();
|
||||
const initIsPending = appHooks.useIsPendingRequest(RequestKeys.initialize);
|
||||
|
||||
const showSelectSessionModal = appHooks.useShowSelectSessionModal();
|
||||
return (
|
||||
<div id="dashboard-container" className="d-flex flex-column p-2">
|
||||
<h1 className="sr-only">{formatMessage(appMessages.pageTitle)}</h1>
|
||||
{hasAvailableDashboards && <EnterpriseDashboardModal />}
|
||||
{initIsPending && (
|
||||
<div className="course-list-loading">
|
||||
<Spinner
|
||||
animation="border"
|
||||
className="mie-3"
|
||||
screenReaderText={formatMessage(appMessages.loadingSR)}
|
||||
/>
|
||||
</div>
|
||||
<div id="dashboard-container" className="d-flex flex-column p-2 pt-3">
|
||||
<h1 className="sr-only">{pageTitle}</h1>
|
||||
{!initIsPending && (
|
||||
<>
|
||||
{hasAvailableDashboards && <EnterpriseDashboardModal />}
|
||||
{(hasCourses && showSelectSessionModal) && <SelectSessionModal />}
|
||||
</>
|
||||
)}
|
||||
{(!initIsPending && hasCourses) && (
|
||||
<Container fluid size="xl">
|
||||
<Row>
|
||||
<Col
|
||||
xs={{ span: 12, offset: 0 }}
|
||||
sm={{ span: 8, offset: 2 }}
|
||||
md={{ span: 12, offset: 0 }}
|
||||
lg={{ span: 10, offset: 1 }}
|
||||
xl={{ span: 8, offset: 0 }}
|
||||
className="p-0 px-4"
|
||||
>
|
||||
{showSelectSessionModal && (<SelectSessionModal />)}
|
||||
<CourseList />
|
||||
</Col>
|
||||
<Col md={12} xl={4} className="p-0 pr-4 pl-1">
|
||||
<WidgetSidebar />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
)}
|
||||
{(!initIsPending && !hasCourses) && (<EmptyCourse />)}
|
||||
<div id="dashboard-content">
|
||||
{initIsPending && (<LoadingView />)}
|
||||
{(!initIsPending && hasCourses) && (<LoadedView />)}
|
||||
{(!initIsPending && !hasCourses) && (<EmptyCourse />)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { hooks } from 'data/redux';
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
|
||||
import CourseList from 'containers/CourseList';
|
||||
import WidgetSidebar from 'containers/WidgetSidebar';
|
||||
import EmptyCourse from 'containers/EmptyCourse';
|
||||
import SelectSessionModal from 'containers/SelectSessionModal';
|
||||
import EnterpriseDashboardModal from 'containers/EnterpriseDashboardModal';
|
||||
import SelectSessionModal from 'containers/SelectSessionModal';
|
||||
|
||||
import LoadedView from './LoadedView';
|
||||
import LoadingView from './LoadingView';
|
||||
import hooks from './hooks';
|
||||
import Dashboard from '.';
|
||||
|
||||
jest.mock('data/redux', () => ({
|
||||
@@ -24,116 +25,114 @@ jest.mock('data/redux', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('containers/CourseList', () => 'CourseList');
|
||||
jest.mock('containers/WidgetSidebar', () => 'WidgetSidebar');
|
||||
jest.mock('containers/EmptyCourse', () => 'EmptyCourse');
|
||||
jest.mock('containers/SelectSessionModal', () => 'SelectSessionModal');
|
||||
jest.mock('containers/EnterpriseDashboardModal', () => 'EnterpriseDashboardModal');
|
||||
jest.mock('./LoadingView', () => 'LoadingView');
|
||||
jest.mock('./LoadedView', () => 'LoadedView');
|
||||
|
||||
jest.mock('./hooks', () => ({
|
||||
useInitializeDashboard: jest.fn(),
|
||||
useDashboardMessages: jest.fn(),
|
||||
}));
|
||||
|
||||
const pageTitle = 'test-page-title';
|
||||
|
||||
describe('Dashboard', () => {
|
||||
beforeEach(() => {
|
||||
hooks.useDashboardMessages.mockReturnValue({ pageTitle });
|
||||
});
|
||||
const createWrapper = ({
|
||||
hasCourses,
|
||||
hasAvailableDashboards,
|
||||
showSelectSessionModal,
|
||||
initIsPending,
|
||||
showSelectSessionModal,
|
||||
}) => {
|
||||
hooks.useHasCourses.mockReturnValueOnce(hasCourses);
|
||||
hooks.useHasAvailableDashboards.mockReturnValueOnce(hasAvailableDashboards);
|
||||
hooks.useShowSelectSessionModal.mockReturnValueOnce(showSelectSessionModal);
|
||||
hooks.useIsPendingRequest.mockReturnValueOnce(initIsPending);
|
||||
appHooks.useHasCourses.mockReturnValueOnce(hasCourses);
|
||||
appHooks.useHasAvailableDashboards.mockReturnValueOnce(hasAvailableDashboards);
|
||||
appHooks.useIsPendingRequest.mockReturnValueOnce(initIsPending);
|
||||
appHooks.useShowSelectSessionModal.mockReturnValueOnce(showSelectSessionModal);
|
||||
return shallow(<Dashboard />);
|
||||
};
|
||||
|
||||
let wrapper;
|
||||
describe('snapshots', () => {
|
||||
test('courses still loading', () => {
|
||||
const wrapper = createWrapper({
|
||||
hasCourses: false,
|
||||
hasAvailableDashboards: false,
|
||||
showSelectSessionModal: false,
|
||||
initIsPending: true,
|
||||
const testTitle = () => {
|
||||
test('page title is displayed in sr-only h1 tag', () => {
|
||||
const heading = wrapper.find('h1');
|
||||
expect(heading.props().className).toEqual('sr-only');
|
||||
expect(heading.text()).toEqual(pageTitle);
|
||||
});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
test('courses loaded', () => {
|
||||
const wrapper = createWrapper({
|
||||
hasCourses: true,
|
||||
hasAvailableDashboards: false,
|
||||
showSelectSessionModal: false,
|
||||
initIsPending: false,
|
||||
};
|
||||
const testSnapshot = () => {
|
||||
test('snapshot', () => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
};
|
||||
const testContent = (el) => {
|
||||
expect(wrapper.find('#dashboard-content').children()).toMatchObject(shallow(el));
|
||||
};
|
||||
|
||||
const renderString = (show) => (show ? 'renders' : 'does not render');
|
||||
const testView = ({
|
||||
props,
|
||||
content: [contentName, contentEl],
|
||||
showEnterpriseModal,
|
||||
showSelectSessionModal,
|
||||
}) => {
|
||||
beforeEach(() => { wrapper = createWrapper(props); });
|
||||
testTitle();
|
||||
testSnapshot();
|
||||
it(`renders ${contentName}`, () => {
|
||||
testContent(contentEl);
|
||||
});
|
||||
it(`${renderString(showEnterpriseModal)} dashbaord modal`, () => {
|
||||
expect(wrapper.find(EnterpriseDashboardModal).length)
|
||||
.toEqual(showEnterpriseModal ? 1 : 0);
|
||||
});
|
||||
it(`${renderString(showSelectSessionModal)} select session modal`, () => {
|
||||
expect(wrapper.find(SelectSessionModal).length).toEqual(showSelectSessionModal ? 1 : 0);
|
||||
});
|
||||
};
|
||||
describe('courses still loading', () => {
|
||||
testView({
|
||||
props: {
|
||||
hasCourses: false,
|
||||
hasAvailableDashboards: false,
|
||||
initIsPending: true,
|
||||
showSelectSessionModal: false,
|
||||
},
|
||||
content: ['LoadingView', <LoadingView />],
|
||||
showEnterpriseModal: false,
|
||||
showSelectSessionModal: false,
|
||||
});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('there are no courses', () => {
|
||||
const wrapper = createWrapper({
|
||||
hasCourses: false,
|
||||
hasAvailableDashboards: false,
|
||||
showSelectSessionModal: false,
|
||||
initIsPending: false,
|
||||
});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('there are available dashboards', () => {
|
||||
const wrapper = createWrapper({
|
||||
hasCourses: false,
|
||||
hasAvailableDashboards: true,
|
||||
showSelectSessionModal: false,
|
||||
initIsPending: false,
|
||||
});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('there is a select session modal', () => {
|
||||
const wrapper = createWrapper({
|
||||
hasCourses: false,
|
||||
hasAvailableDashboards: false,
|
||||
describe('courses loaded, show select session modal, no available dashboards', () => {
|
||||
testView({
|
||||
props: {
|
||||
hasCourses: true,
|
||||
hasAvailableDashboards: false,
|
||||
initIsPending: false,
|
||||
showSelectSessionModal: true,
|
||||
},
|
||||
content: ['LoadedView', <LoadedView />],
|
||||
showEnterpriseModal: false,
|
||||
showSelectSessionModal: true,
|
||||
initIsPending: false,
|
||||
});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('behavior', () => {
|
||||
it('initializes the app without courses', () => {
|
||||
const wrapper = createWrapper({
|
||||
hasCourses: false,
|
||||
hasAvailableDashboards: false,
|
||||
describe('there are no courses, there ARE available dashboards', () => {
|
||||
testView({
|
||||
props: {
|
||||
hasCourses: false,
|
||||
hasAvailableDashboards: true,
|
||||
initIsPending: false,
|
||||
showSelectSessionModal: false,
|
||||
},
|
||||
content: ['EmptyCourse', <EmptyCourse />],
|
||||
showEnterpriseModal: true,
|
||||
showSelectSessionModal: false,
|
||||
initIsPending: false,
|
||||
});
|
||||
expect(wrapper.find(EmptyCourse).length).toEqual(1);
|
||||
expect(wrapper.find(CourseList).length).toEqual(0);
|
||||
expect(wrapper.find(WidgetSidebar).length).toEqual(0);
|
||||
expect(wrapper.find(SelectSessionModal).length).toEqual(0);
|
||||
expect(wrapper.find(EnterpriseDashboardModal).length).toEqual(0);
|
||||
});
|
||||
it('initializes the app with courses, dashboard and select', () => {
|
||||
const wrapper = createWrapper({
|
||||
hasCourses: true,
|
||||
hasAvailableDashboards: true,
|
||||
showSelectSessionModal: true,
|
||||
initIsPending: false,
|
||||
});
|
||||
expect(wrapper.find(EmptyCourse).length).toEqual(0);
|
||||
expect(wrapper.find(CourseList).length).toEqual(1);
|
||||
expect(wrapper.find(WidgetSidebar).length).toEqual(1);
|
||||
expect(wrapper.find(SelectSessionModal).length).toEqual(1);
|
||||
expect(wrapper.find(EnterpriseDashboardModal).length).toEqual(1);
|
||||
});
|
||||
it('initializes the app with courses, dashboard and no select', () => {
|
||||
const wrapper = createWrapper({
|
||||
hasCourses: true,
|
||||
hasAvailableDashboards: true,
|
||||
showSelectSessionModal: false,
|
||||
initIsPending: false,
|
||||
});
|
||||
expect(wrapper.find(EmptyCourse).length).toEqual(0);
|
||||
expect(wrapper.find(CourseList).length).toEqual(1);
|
||||
expect(wrapper.find(WidgetSidebar).length).toEqual(1);
|
||||
expect(wrapper.find(SelectSessionModal).length).toEqual(0);
|
||||
expect(wrapper.find(EnterpriseDashboardModal).length).toEqual(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,7 +28,7 @@ export const GreetingBanner = ({ size }) => {
|
||||
<div
|
||||
className={classNames(
|
||||
'd-flex align-items-center justify-content-center',
|
||||
{ 'p-5': !isSmall, 'p-3.5': isSmall },
|
||||
{ 'pb-5': !isSmall, 'p-3.5': isSmall },
|
||||
)}
|
||||
>
|
||||
<a href={`${getConfig().LMS_BASE_URL}/dashboard`}>
|
||||
@@ -46,7 +46,10 @@ export const GreetingBanner = ({ size }) => {
|
||||
{formatMessage(greetMessage)}
|
||||
</h5>
|
||||
) : (
|
||||
<h1 role="presentation" className="text-center text-accent-b">
|
||||
<h1
|
||||
role="presentation"
|
||||
className="text-center text-accent-b display-1"
|
||||
>
|
||||
{formatMessage(greetMessage)}
|
||||
</h1>
|
||||
)}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
exports[`GreetingBanner snapshots with size large and afternoon 1`] = `
|
||||
<div
|
||||
className="d-flex align-items-center justify-content-center p-5"
|
||||
className="d-flex align-items-center justify-content-center pb-5"
|
||||
>
|
||||
<a
|
||||
href="http://localhost:18000/dashboard"
|
||||
@@ -22,7 +22,7 @@ exports[`GreetingBanner snapshots with size large and afternoon 1`] = `
|
||||
className="greetings-slash-container-large bg-brand-500"
|
||||
/>
|
||||
<h1
|
||||
className="text-center text-accent-b"
|
||||
className="text-center text-accent-b display-1"
|
||||
role="presentation"
|
||||
>
|
||||
Good Afternoon!
|
||||
@@ -32,7 +32,7 @@ exports[`GreetingBanner snapshots with size large and afternoon 1`] = `
|
||||
|
||||
exports[`GreetingBanner snapshots with size large and evening 1`] = `
|
||||
<div
|
||||
className="d-flex align-items-center justify-content-center p-5"
|
||||
className="d-flex align-items-center justify-content-center pb-5"
|
||||
>
|
||||
<a
|
||||
href="http://localhost:18000/dashboard"
|
||||
@@ -52,7 +52,7 @@ exports[`GreetingBanner snapshots with size large and evening 1`] = `
|
||||
className="greetings-slash-container-large bg-brand-500"
|
||||
/>
|
||||
<h1
|
||||
className="text-center text-accent-b"
|
||||
className="text-center text-accent-b display-1"
|
||||
role="presentation"
|
||||
>
|
||||
Good Evening!
|
||||
@@ -62,7 +62,7 @@ exports[`GreetingBanner snapshots with size large and evening 1`] = `
|
||||
|
||||
exports[`GreetingBanner snapshots with size large and morning 1`] = `
|
||||
<div
|
||||
className="d-flex align-items-center justify-content-center p-5"
|
||||
className="d-flex align-items-center justify-content-center pb-5"
|
||||
>
|
||||
<a
|
||||
href="http://localhost:18000/dashboard"
|
||||
@@ -82,7 +82,7 @@ exports[`GreetingBanner snapshots with size large and morning 1`] = `
|
||||
className="greetings-slash-container-large bg-brand-500"
|
||||
/>
|
||||
<h1
|
||||
className="text-center text-accent-b"
|
||||
className="text-center text-accent-b display-1"
|
||||
role="presentation"
|
||||
>
|
||||
Good Morning!
|
||||
|
||||
@@ -49,7 +49,7 @@ exports[`LearnerDashboardHeader snapshots without collapsed 1`] = `
|
||||
className="flex-column bg-primary"
|
||||
>
|
||||
<Image
|
||||
className="d-block w-100"
|
||||
className="d-block w-100 mb-4"
|
||||
src="icon/mock/path"
|
||||
/>
|
||||
<header
|
||||
|
||||
@@ -33,7 +33,7 @@ export const LearnerDashboardHeader = () => {
|
||||
<ConfirmEmailBanner />
|
||||
<div className="flex-column bg-primary">
|
||||
{!(isCollapsed) && (
|
||||
<Image className="d-block w-100" src={topBanner} />
|
||||
<Image className="d-block w-100 mb-4" src={topBanner} />
|
||||
)}
|
||||
<header className="learner-dashboard-header">
|
||||
<div className="d-flex">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
exports[`WidgetSidebar snapshots default 1`] = `
|
||||
<div
|
||||
className="widget-sidebar px-2 mt-5 pt-3"
|
||||
className="widget-sidebar px-2"
|
||||
>
|
||||
<div
|
||||
className="d-flex"
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from 'react';
|
||||
import LookingForChallengeWidget from './widgets/LookingForChallengeWidget';
|
||||
|
||||
export const WidgetSidebar = () => (
|
||||
<div className="widget-sidebar px-2 mt-5 pt-3">
|
||||
<div className="widget-sidebar px-2">
|
||||
<div className="d-flex">
|
||||
<LookingForChallengeWidget />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user