components unit test (#23)

Co-authored-by: Ben Warzeski <bwarzeski@edx.org>
This commit is contained in:
leangseu-edx
2022-09-27 12:48:33 -04:00
committed by GitHub
parent 052dc98a70
commit 2ee19988b3
63 changed files with 4912 additions and 8666 deletions

View File

@@ -0,0 +1,36 @@
import { shallow } from 'enzyme';
import { hooks } from 'data/redux';
import BeginCourseButton from './BeginCourseButton';
jest.mock('data/redux', () => ({
hooks: {
useCardCourseRunData: jest.fn(),
useCardEnrollmentData: jest.fn(),
},
}));
describe('BeginCourseButton', () => {
const props = {
cardId: 'cardId',
};
hooks.useCardCourseRunData.mockReturnValue({
homeUrl: 'homeUrl',
});
describe('snapshot', () => {
test('renders default button when learner has access to the course', () => {
hooks.useCardEnrollmentData.mockReturnValueOnce({
hasAccess: true,
});
const wrapper = shallow(<BeginCourseButton {...props} />);
expect(wrapper).toMatchSnapshot();
});
test('renders disabled button when learner does not have access to the course', () => {
hooks.useCardEnrollmentData.mockReturnValueOnce({
hasAccess: false,
});
const wrapper = shallow(<BeginCourseButton {...props} />);
expect(wrapper).toMatchSnapshot();
});
});
});

View File

@@ -0,0 +1,56 @@
import { shallow } from 'enzyme';
import { hooks } from 'data/redux';
import ResumeButton from './ResumeButton';
jest.mock('data/redux', () => ({
hooks: {
useCardCourseRunData: jest.fn(),
useCardEnrollmentData: jest.fn(),
},
}));
describe('ResumeButton', () => {
const props = {
cardId: 'cardId',
};
hooks.useCardCourseRunData.mockReturnValue({
resumeUrl: 'resumeUrl',
});
describe('snapshot', () => {
test('renders default button when learner has access to the course', () => {
hooks.useCardEnrollmentData.mockReturnValueOnce({
hasAccess: true,
});
const wrapper = shallow(<ResumeButton {...props} />);
expect(wrapper).toMatchSnapshot();
});
test('renders disabled button when learner does not have access to the course', () => {
hooks.useCardEnrollmentData.mockReturnValueOnce({
hasAccess: false,
});
const wrapper = shallow(<ResumeButton {...props} />);
expect(wrapper).toMatchSnapshot();
});
});
describe('behavior', () => {
it('renders disabled button when audit access expired', () => {
hooks.useCardEnrollmentData.mockReturnValueOnce({
hasAccess: true,
isAudit: true,
isAuditAccessExpired: true,
});
const wrapper = shallow(<ResumeButton {...props} />);
expect(wrapper.prop('disabled')).toEqual(true);
});
it('renders enabled button when audit access not expired', () => {
hooks.useCardEnrollmentData.mockReturnValueOnce({
hasAccess: true,
isAudit: true,
isAuditAccessExpired: false,
});
const wrapper = shallow(<ResumeButton {...props} />);
expect(wrapper.prop('disabled')).toEqual(false);
});
});
});

View File

@@ -0,0 +1,61 @@
import { shallow } from 'enzyme';
import { hooks } from 'data/redux';
import SelectSessionButton from './SelectSessionButton';
jest.mock('data/redux', () => ({
hooks: {
useCardCourseRunData: jest.fn(),
useCardEnrollmentData: jest.fn(),
useCardEntitlementData: jest.fn(),
useUpdateSelectSessionModalCallback: () => jest.fn().mockName('mockOpenSessionModal'),
},
}));
describe('SelectSessionButton', () => {
const props = {
cardId: 'cardId',
};
hooks.useCardCourseRunData.mockReturnValue({
resumeUrl: 'resumeUrl',
});
const createWrapper = ({ hasAccess, canChange, hasSessions }) => {
hooks.useCardEnrollmentData.mockReturnValueOnce({
hasAccess,
});
hooks.useCardEntitlementData.mockReturnValueOnce({
canChange,
hasSessions,
});
return shallow(<SelectSessionButton {...props} />);
};
describe('snapshot', () => {
test('renders default button', () => {
const wrapper = createWrapper({ hasAccess: true, canChange: true, hasSessions: true });
expect(wrapper).toMatchSnapshot();
});
it('renders disabled button when user does not have access to the course', () => {
const wrapper = createWrapper({ hasAccess: false, canChange: true, hasSessions: true });
expect(wrapper).toMatchSnapshot();
});
});
describe('behavior', () => {
it('default render', () => {
const wrapper = createWrapper({ hasAccess: true, canChange: true, hasSessions: true });
expect(wrapper.prop('disabled')).toEqual(false);
expect(wrapper.prop('href')).toEqual('resumeUrl');
expect(wrapper.prop('onClick').getMockName())
.toEqual(hooks.useUpdateSelectSessionModalCallback().getMockName());
});
it('disabled if learner doesn\'t have access, cannot change sessions, or does not have sessions', () => {
const noAccess = createWrapper({ hasAccess: false, canChange: true, hasSessions: true });
expect(noAccess.prop('disabled')).toEqual(true);
const cannotChange = createWrapper({ hasAccess: true, canChange: false, hasSessions: true });
expect(cannotChange.prop('disabled')).toEqual(true);
const noSessions = createWrapper({ hasAccess: true, canChange: true, hasSessions: false });
expect(noSessions.prop('disabled')).toEqual(true);
});
});
});

View File

@@ -0,0 +1,31 @@
import { shallow } from 'enzyme';
import { hooks } from 'data/redux';
import UpgradeButton from './UpgradeButton';
jest.mock('data/redux', () => ({
hooks: {
useCardCourseRunData: jest.fn(),
useCardEnrollmentData: jest.fn(),
},
}));
describe('UpgradeButton', () => {
const props = {
cardId: 'cardId',
};
const upgradeUrl = 'upgradeUrl';
hooks.useCardCourseRunData.mockReturnValue({ upgradeUrl });
describe('snapshot', () => {
test('can upgrade', () => {
hooks.useCardEnrollmentData.mockReturnValueOnce({ canUpgrade: true });
const wrapper = shallow(<UpgradeButton {...props} />);
expect(wrapper).toMatchSnapshot();
});
test('cannot upgrade', () => {
hooks.useCardEnrollmentData.mockReturnValueOnce({ canUpgrade: false });
const wrapper = shallow(<UpgradeButton {...props} />);
expect(wrapper).toMatchSnapshot();
});
});
});

View File

@@ -0,0 +1,49 @@
import { shallow } from 'enzyme';
import { hooks } from 'data/redux';
import ViewCourseButton from './ViewCourseButton';
jest.mock('data/redux', () => ({
hooks: {
useCardCourseRunData: jest.fn(),
useCardEnrollmentData: jest.fn(),
useCardEntitlementData: jest.fn(),
},
}));
describe('ViewCourseButton', () => {
const props = {
cardId: 'cardId',
};
const marketingUrl = 'marketingUrl';
hooks.useCardCourseRunData.mockReturnValue({ marketingUrl });
const createWrapper = ({ hasAccess, isEntitlement, isExpired }) => {
hooks.useCardEnrollmentData.mockReturnValueOnce({ hasAccess });
hooks.useCardEntitlementData.mockReturnValueOnce({ isEntitlement, isExpired });
return shallow(<ViewCourseButton {...props} />);
};
describe('snapshot', () => {
test('default button', () => {
const wrapper = createWrapper({ hasAccess: true, isEntitlement: false, isExpired: false });
expect(wrapper).toMatchSnapshot();
});
test('disabled button', () => {
const wrapper = createWrapper({ hasAccess: false, isEntitlement: false, isExpired: false });
expect(wrapper).toMatchSnapshot();
});
});
describe('behavior', () => {
it('disabled button without access', () => {
const wrapper = createWrapper({ hasAccess: false, isEntitlement: false, isExpired: false });
expect(wrapper.prop('disabled')).toEqual(true);
});
it('disabled button with access', () => {
const wrapper = createWrapper({ hasAccess: true, isEntitlement: true, isExpired: true });
expect(wrapper.prop('disabled')).toEqual(true);
});
it('enabled button', () => {
const wrapper = createWrapper({ hasAccess: true, isEntitlement: false, isExpired: false });
expect(wrapper.prop('disabled')).toEqual(false);
});
});
});

View File

@@ -0,0 +1,21 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`BeginCourseButton snapshot renders default button when learner has access to the course 1`] = `
<Button
as="a"
disabled={false}
href="homeUrl"
>
Begin Course
</Button>
`;
exports[`BeginCourseButton snapshot renders disabled button when learner does not have access to the course 1`] = `
<Button
as="a"
disabled={true}
href="homeUrl"
>
Begin Course
</Button>
`;

View File

@@ -0,0 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ResumeButton snapshot renders default button when learner has access to the course 1`] = `
<Button
as="a"
href="resumeUrl"
>
Resume
</Button>
`;
exports[`ResumeButton snapshot renders disabled button when learner does not have access to the course 1`] = `
<Button
as="a"
disabled={true}
href="resumeUrl"
>
Resume
</Button>
`;

View File

@@ -0,0 +1,23 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SelectSessionButton snapshot renders default button 1`] = `
<Button
as="a"
disabled={false}
href="resumeUrl"
onClick={[MockFunction mockOpenSessionModal]}
>
Resume
</Button>
`;
exports[`SelectSessionButton snapshot renders disabled button when user does not have access to the course 1`] = `
<Button
as="a"
disabled={true}
href="resumeUrl"
onClick={[MockFunction mockOpenSessionModal]}
>
Resume
</Button>
`;

View File

@@ -0,0 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`UpgradeButton snapshot can upgrade 1`] = `
<Button
as="a"
disabled={false}
href="upgradeUrl"
iconBefore={[MockFunction icons.Locked]}
variant="outline-primary"
>
Upgrade
</Button>
`;
exports[`UpgradeButton snapshot cannot upgrade 1`] = `
<Button
as="a"
disabled={true}
href="upgradeUrl"
iconBefore={[MockFunction icons.Locked]}
variant="outline-primary"
>
Upgrade
</Button>
`;

View File

@@ -0,0 +1,21 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ViewCourseButton snapshot default button 1`] = `
<Button
as="a"
disabled={false}
href="marketingUrl"
>
View Course
</Button>
`;
exports[`ViewCourseButton snapshot disabled button 1`] = `
<Button
as="a"
disabled={true}
href="marketingUrl"
>
View Course
</Button>
`;

View File

@@ -1,36 +1,54 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CourseCard Actions component does not render secondary button if null is returned for secondary props 1`] = `
exports[`CourseCardActions snapshot show begin course button when verified and not entitlement and has started 1`] = `
<ActionRow
data-test-id="CourseCardActions"
>
<Button
cardId="test-course-number"
prop1="primary-prop1"
prop2="primary-prop2"
>
primary-children
</Button>
<BeginCourseButton
cardId="cardId"
/>
</ActionRow>
`;
exports[`CourseCard Actions component loads primary and secondary button props from hook 1`] = `
exports[`CourseCardActions snapshot show resume button when verified and not entitlement and has started 1`] = `
<ActionRow
data-test-id="CourseCardActions"
>
<Button
cardId="test-course-number"
prop1="primary-prop1"
prop2="primary-prop2"
>
primary-children
</Button>
<Button
cardId="test-course-number"
prop1="primary-prop1"
prop2="primary-prop2"
>
primary-children
</Button>
<ResumeButton
cardId="cardId"
/>
</ActionRow>
`;
exports[`CourseCardActions snapshot show select session button when not verified and entitlement 1`] = `
<ActionRow
data-test-id="CourseCardActions"
>
<SelectSessionButton
cardId="cardId"
/>
</ActionRow>
`;
exports[`CourseCardActions snapshot show upgrade button when not verified and not entitlement 1`] = `
<ActionRow
data-test-id="CourseCardActions"
>
<UpgradeButton
cardId="cardId"
/>
<BeginCourseButton
cardId="cardId"
/>
</ActionRow>
`;
exports[`CourseCardActions snapshot show view course button when not verified and entitlement and fulfilled 1`] = `
<ActionRow
data-test-id="CourseCardActions"
>
<ViewCourseButton
cardId="cardId"
/>
</ActionRow>
`;

View File

@@ -0,0 +1,103 @@
import { shallow } from 'enzyme';
import { hooks } from 'data/redux';
import CourseCardActions from '.';
jest.mock('data/redux', () => ({
hooks: {
useCardCourseRunData: jest.fn(),
useCardEnrollmentData: jest.fn(),
useCardEntitlementData: jest.fn(),
},
}));
jest.mock('./UpgradeButton', () => 'UpgradeButton');
jest.mock('./SelectSessionButton', () => 'SelectSessionButton');
jest.mock('./ViewCourseButton', () => 'ViewCourseButton');
jest.mock('./BeginCourseButton', () => 'BeginCourseButton');
jest.mock('./ResumeButton', () => 'ResumeButton');
describe('CourseCardActions', () => {
const props = {
cardId: 'cardId',
};
const createWrapper = ({
isEntitlement, isFulfilled, isArchived, isVerified, hasStarted,
}) => {
hooks.useCardEntitlementData.mockReturnValueOnce({ isEntitlement, isFulfilled });
hooks.useCardCourseRunData.mockReturnValueOnce({ isArchived });
hooks.useCardEnrollmentData.mockReturnValueOnce({ isVerified, hasStarted });
return shallow(<CourseCardActions {...props} />);
};
describe('snapshot', () => {
test('show upgrade button when not verified and not entitlement', () => {
const wrapper = createWrapper({
isEntitlement: false, isFulfilled: false, isArchived: false, isVerified: false, hasStarted: false,
});
expect(wrapper).toMatchSnapshot();
});
test('show select session button when not verified and entitlement', () => {
const wrapper = createWrapper({
isEntitlement: true, isFulfilled: false, isArchived: false, isVerified: false, hasStarted: false,
});
expect(wrapper).toMatchSnapshot();
});
test('show begin course button when verified and not entitlement and has started', () => {
const wrapper = createWrapper({
isEntitlement: false, isFulfilled: false, isArchived: false, isVerified: true, hasStarted: false,
});
expect(wrapper).toMatchSnapshot();
});
test('show resume button when verified and not entitlement and has started', () => {
const wrapper = createWrapper({
isEntitlement: false, isFulfilled: false, isArchived: false, isVerified: true, hasStarted: true,
});
expect(wrapper).toMatchSnapshot();
});
test('show view course button when not verified and entitlement and fulfilled', () => {
const wrapper = createWrapper({
isEntitlement: true, isFulfilled: true, isArchived: false, isVerified: false, hasStarted: false,
});
expect(wrapper).toMatchSnapshot();
});
});
describe('behavior', () => {
it('show upgrade button when not verified and not entitlement', () => {
const wrapper = createWrapper({
isEntitlement: false, isFulfilled: false, isArchived: false, isVerified: false, hasStarted: false,
});
expect(wrapper.find('UpgradeButton')).toHaveLength(1);
});
it('show select session button when not verified and entitlement', () => {
const wrapper = createWrapper({
isEntitlement: true, isFulfilled: false, isArchived: false, isVerified: false, hasStarted: false,
});
expect(wrapper.find('SelectSessionButton')).toHaveLength(1);
});
it('show begin course button when verified and not entitlement and has started', () => {
const wrapper = createWrapper({
isEntitlement: false, isFulfilled: false, isArchived: false, isVerified: true, hasStarted: false,
});
expect(wrapper.find('BeginCourseButton')).toHaveLength(1);
});
it('show resume button when verified and not entitlement and has started', () => {
const wrapper = createWrapper({
isEntitlement: false, isFulfilled: false, isArchived: false, isVerified: true, hasStarted: true,
});
expect(wrapper.find('ResumeButton')).toHaveLength(1);
});
it('show view course button when not verified and entitlement and fulfilled', () => {
const wrapper = createWrapper({
isEntitlement: true, isFulfilled: true, isArchived: false, isVerified: false, hasStarted: false,
});
expect(wrapper.find('ViewCourseButton')).toHaveLength(1);
});
it('show view course button when not verified and entitlement and fulfilled and archived', () => {
const wrapper = createWrapper({
isEntitlement: true, isFulfilled: true, isArchived: true, isVerified: false, hasStarted: false,
});
expect(wrapper.find('ViewCourseButton')).toHaveLength(1);
});
});
});

View File

@@ -0,0 +1,151 @@
import { shallow } from 'enzyme';
import { hooks } from 'data/redux';
import CertificateBanner from './CertificateBanner';
import messages from './messages';
jest.mock('data/redux', () => ({
hooks: {
useCardCertificateData: jest.fn(),
useCardEnrollmentData: jest.fn(),
useCardGradeData: jest.fn(),
useCardCourseRunData: jest.fn(),
usePlatformSettingsData: jest.fn(),
},
}));
jest.mock('Components/Banner', () => 'Banner');
describe('CertificateBanner', () => {
const props = {
cardId: 'cardId',
};
hooks.usePlatformSettingsData.mockReturnValue({
supportEmail: 'suport@email',
billingEmail: 'billing@email',
});
hooks.useCardCourseRunData.mockReturnValue({
minPassingGrade: 0.8,
progressUrl: 'progressUrl',
});
const defaultCertificate = {
isRestricted: false,
isDownloadable: false,
isEarnedButUnavailable: false,
};
const defaultEnrollment = {
isAudit: false,
isVerified: false,
hasFinished: false,
};
const defaultGrade = {
isPassing: false,
};
const createWrapper = ({
certificate = {},
enrollment = {},
grade = {},
}) => {
hooks.useCardGradeData.mockReturnValueOnce({ ...defaultGrade, ...grade });
hooks.useCardCertificateData.mockReturnValueOnce({ ...defaultCertificate, ...certificate });
hooks.useCardEnrollmentData.mockReturnValueOnce({ ...defaultEnrollment, ...enrollment });
return shallow(<CertificateBanner {...props} />);
};
describe('snapshot', () => {
test('is restricted', () => {
const wrapper = createWrapper({
certificate: {
isRestricted: true,
},
});
expect(wrapper).toMatchSnapshot();
});
test('is restricted and verified', () => {
const wrapper = createWrapper({
certificate: {
isRestricted: true,
},
enrollment: {
isVerified: true,
},
});
expect(wrapper).toMatchSnapshot();
});
test('not passing and audit', () => {
const wrapper = createWrapper({
enrollment: {
isAudit: true,
},
});
expect(wrapper).toMatchSnapshot();
});
test('not passing and has finished', () => {
const wrapper = createWrapper({
enrollment: {
hasFinished: true,
},
});
expect(wrapper).toMatchSnapshot();
});
test('not passing and not audit and not finished', () => {
const wrapper = createWrapper({});
expect(wrapper).toMatchSnapshot();
});
test('is passing and is downloadable', () => {
const wrapper = createWrapper({
grade: {
isPassing: true,
},
certificate: {
isDownloadable: true,
},
});
expect(wrapper).toMatchSnapshot();
});
test('is passing and is earned but unavailable', () => {
const wrapper = createWrapper({
grade: {
isPassing: true,
},
certificate: {
isEarnedButUnavailable: true,
},
});
expect(wrapper).toMatchSnapshot();
});
test('is passing and not downloadable render empty', () => {
const wrapper = createWrapper({
grade: {
isPassing: true,
},
});
expect(wrapper).toMatchSnapshot();
});
});
describe('behavior', () => {
it('is restricted', () => {
const wrapper = createWrapper({
certificate: {
isRestricted: true,
},
});
const bannerMessage = wrapper.find('format-message-function').map(el => el.prop('message').defaultMessage).join('\n');
expect(bannerMessage).toEqual(messages.certRestricted.defaultMessage);
expect(bannerMessage).toContain(messages.certRestricted.defaultMessage);
});
it('is restricted and verified', () => {
const wrapper = createWrapper({
certificate: {
isRestricted: true,
},
enrollment: {
isVerified: true,
},
});
const bannerMessage = wrapper.find('format-message-function').map(el => el.prop('message').defaultMessage).join('\n');
expect(bannerMessage).toContain(messages.certRestricted.defaultMessage);
expect(bannerMessage).toContain(messages.certRefundContactBilling.defaultMessage);
});
});
});

View File

@@ -61,7 +61,7 @@ const render = (overrides = {}) => {
};
describe('CourseBanner', () => {
it('initializes data with course number from enrollment, course and course run data', () => {
test('initializes data with course number from enrollment, course and course run data', () => {
render();
expect(appHooks.useCardCourseData).toHaveBeenCalledWith(cardId);
expect(appHooks.useCardCourseRunData).toHaveBeenCalledWith(cardId);
@@ -145,9 +145,6 @@ describe('CourseBanner', () => {
test('snapshot: isStaff', () => {
expect(el).toMatchSnapshot();
});
test('messages: staffAccessOnly', () => {
expect(el.text()).toContain(messages.staffAccessOnly.defaultMessage);
});
});
test('snapshot: stacking banners', () => {
render({

View File

@@ -39,7 +39,7 @@ const render = (overrides = {}) => {
const dispatch = useDispatch();
describe('EntitlementBanner', () => {
it('initializes data with course number from entitlement', () => {
test('initializes data with course number from entitlement', () => {
render();
expect(appHooks.useCardEntitlementData).toHaveBeenCalledWith(cardId);
expect(appHooks.useUpdateSelectSessionModalCallback).toHaveBeenCalledWith(dispatch, cardId);

View File

@@ -0,0 +1,120 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CertificateBanner snapshot is passing and is downloadable 1`] = `
<Banner
icon={[MockFunction icons.CheckCircle]}
variant="success"
>
Congratulations. Your certificate is ready.
<Hyperlink>
View Certificate.
</Hyperlink>
</Banner>
`;
exports[`CertificateBanner snapshot is passing and is earned but unavailable 1`] = `
<Banner>
Your grade and certificate will be ready after Invalid Date.
</Banner>
`;
exports[`CertificateBanner snapshot is passing and not downloadable render empty 1`] = `""`;
exports[`CertificateBanner snapshot is restricted 1`] = `
<Banner
variant="danger"
>
<format-message-function
message={
Object {
"defaultMessage": "Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know by contacting {supportEmail}.",
"description": "Restricted certificate warning message",
"id": "learner-dash.courseCard.banners.certificateRestricted",
}
}
values={
Object {
"supportEmail": <MailtoLink
to="suport@email"
>
suport@email
</MailtoLink>,
}
}
/>
</Banner>
`;
exports[`CertificateBanner snapshot is restricted and verified 1`] = `
<Banner
variant="danger"
>
<format-message-function
message={
Object {
"defaultMessage": "Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know by contacting {supportEmail}.",
"description": "Restricted certificate warning message",
"id": "learner-dash.courseCard.banners.certificateRestricted",
}
}
values={
Object {
"supportEmail": <MailtoLink
to="suport@email"
>
suport@email
</MailtoLink>,
}
}
/>
<format-message-function
message={
Object {
"defaultMessage": "If you would like a refund on your Certificate of Achievement, please contact our billing address {billingEmail}",
"description": "Message to learners to contact billing for certificate refunds",
"id": "learner-dash.courseCard.banners.certificateRefundContactBilling",
}
}
values={
Object {
"billingEmail": <MailtoLink
to="billing@email"
>
billing@email
</MailtoLink>,
}
}
/>
</Banner>
`;
exports[`CertificateBanner snapshot not passing and audit 1`] = `
<Banner>
Grade required to pass the course: 0.8
</Banner>
`;
exports[`CertificateBanner snapshot not passing and has finished 1`] = `
<Banner
variant="warning"
>
You are not eligible for a certificate.
.
<Hyperlink
destination="progressUrl"
>
View grades.
</Hyperlink>
</Banner>
`;
exports[`CertificateBanner snapshot not passing and not audit and not finished 1`] = `
<Banner
variant="warning"
>
Grade required for a certificate: 0.8
</Banner>
`;

View File

@@ -38,27 +38,9 @@ exports[`CourseBanner course run active and cannot upgrade snapshot: (upgradseDe
</Fragment>
`;
exports[`CourseBanner snapshot: stacking banners 1`] = `
<Fragment>
<Banner>
You can't access this course just yet because the course hasn't started yet. The course will start on 11/11/3030.
</Banner>
<Banner>
You can't access this course just yet because you have not met the pre-requisites.
</Banner>
<Banner>
Staff access only.
</Banner>
</Fragment>
`;
exports[`CourseBanner snapshot: stacking banners 1`] = `<Fragment />`;
exports[`CourseBanner staff snapshot: isStaff 1`] = `
<Fragment>
<Banner>
Staff access only.
</Banner>
</Fragment>
`;
exports[`CourseBanner staff snapshot: isStaff 1`] = `<Fragment />`;
exports[`CourseBanner too early snapshot: tooEarly 1`] = `
<Fragment>

View File

@@ -9,11 +9,11 @@ import { hooks as appHooks } from 'data/redux';
import RelatedProgramsBadge from './RelatedProgramsBadge';
import CourseCardMenu from './CourseCardMenu';
import messages from '../messages';
import CourseCardActions from './CourseCardActions';
import CourseCardDetails from './CourseCardDetails';
import messages from '../messages';
export const CourseCardContent = ({ cardId, orientation }) => {
const { formatMessage } = useIntl();
const { courseName, bannerImgSrc } = appHooks.useCardCourseData(cardId);

View File

@@ -0,0 +1,36 @@
import { shallow } from 'enzyme';
import { hooks } from 'data/redux';
import CourseCardContent from './CourseCardContent';
jest.mock('data/redux', () => ({
hooks: {
useCardCourseData: jest.fn(),
},
}));
jest.mock('./CourseCardActions', () => 'CourseCardActions');
jest.mock('./CourseCardDetails', () => 'CourseCardDetails');
jest.mock('./RelatedProgramsBadge', () => 'RelatedProgramsBadge');
jest.mock('./CourseCardMenu', () => 'CourseCardMenu');
describe('CourseCardContent', () => {
const props = {
cardId: 'test-card-id',
orientation: 'vertical',
};
hooks.useCardCourseData.mockReturnValue({
courseName: 'test-course-name',
bannerImgSrc: 'test-banner-img-src',
});
describe('snapshot', () => {
test('orientation vertical', () => {
const wrapper = shallow(<CourseCardContent {...props} />);
expect(wrapper).toMatchSnapshot();
});
test('orientation horizontal', () => {
const wrapper = shallow(<CourseCardContent {...props} orientation="horizontal" />);
expect(wrapper).toMatchSnapshot();
});
});
});

View File

@@ -15,7 +15,7 @@ jest.mock('./hooks', () => ({
const cardId = 'test-card-id';
describe('CourseCard Details component', () => {
it('has change session button on entitlement course', () => {
test('has change session button on entitlement course', () => {
const mockHook = (args) => () => ({
providerName: 'provider-name',
accessMessage: 'access-message',
@@ -34,7 +34,7 @@ describe('CourseCard Details component', () => {
expect(el.text().match(/•/g)).toHaveLength(3);
});
it('does not have change session button on regular course', () => {
test('does not have change session button on regular course', () => {
const mockHook = (args) => () => ({
providerName: 'provider-name',
accessMessage: 'acess-message',

View File

@@ -0,0 +1,29 @@
import { shallow } from 'enzyme';
import CourseCardLayout from './CourseCardLayout';
import { useIsCollapsed } from '../hooks';
jest.mock('../hooks', () => ({
useIsCollapsed: jest.fn(),
}));
jest.mock('./CourseCardBanners', () => 'CourseCardBanners');
jest.mock('./CourseCardContent', () => 'CourseCardContent');
describe('CourseCardLayout', () => {
const props = {
cardId: 'test-card-id',
};
describe('snapshot', () => {
test('is collapsed', () => {
useIsCollapsed.mockReturnValue(true);
const wrapper = shallow(<CourseCardLayout {...props} />);
expect(wrapper).toMatchSnapshot();
});
test('is not collapsed', () => {
useIsCollapsed.mockReturnValue(false);
const wrapper = shallow(<CourseCardLayout {...props} />);
expect(wrapper).toMatchSnapshot();
});
});
});

View File

@@ -0,0 +1,48 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CourseCardMenu snapshot 1`] = `
<Fragment>
<Dropdown>
<Dropdown.Toggle
alt="Actions dropdown"
as="IconButton"
iconAs="Icon"
id="dropdown-toggle-with-iconbutton"
src={[MockFunction icons.MoreVert]}
variant="primary"
/>
<Dropdown.Menu>
<Dropdown.Item
onClick={[MockFunction unenrollShow]}
>
Unenroll
</Dropdown.Item>
<Dropdown.Item
onClick={[MockFunction emailSettingShow]}
>
Email Settings
</Dropdown.Item>
<Dropdown.Item
href="#/action-3"
>
Share to Facebook
</Dropdown.Item>
<Dropdown.Item
href="#/action-3"
>
Share to Twitter
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
<UnenrollConfirmModal
cardId="test-card-id"
closeModal={[MockFunction unenrollHide]}
show={false}
/>
<EmailSettingsModal
cardId="test-card-id"
closeModal={[MockFunction emailSettingHide]}
show={false}
/>
</Fragment>
`;

View File

@@ -0,0 +1,58 @@
import { MockUseState } from 'testUtils';
import * as hooks from './hooks';
const state = new MockUseState(hooks);
describe('CourseCardMenu hooks', () => {
describe('state values', () => {
state.testGetter(state.keys.isUnenrollConfirmVisible);
state.testGetter(state.keys.isEmailSettingsVisible);
});
describe('useUnenrollData', () => {
let out;
beforeEach(() => {
state.mock();
out = hooks.useUnenrollData();
});
afterEach(state.restore);
test('default state', () => {
expect(out.isVisible).toEqual(state.stateVals.isUnenrollConfirmVisible);
});
test('show', () => {
out.show();
state.expectSetStateCalledWith(state.keys.isUnenrollConfirmVisible, true);
});
test('hide', () => {
out.hide();
state.expectSetStateCalledWith(state.keys.isUnenrollConfirmVisible, false);
});
});
describe('useEmailSettings', () => {
let out;
beforeEach(() => {
state.mock();
out = hooks.useEmailSettings();
});
afterEach(state.restore);
test('default state', () => {
expect(out.isVisible).toEqual(state.stateVals.isEmailSettingsVisible);
});
test('show', () => {
out.show();
state.expectSetStateCalledWith(state.keys.isEmailSettingsVisible, true);
});
test('hide', () => {
out.hide();
state.expectSetStateCalledWith(state.keys.isEmailSettingsVisible, false);
});
});
});

View File

@@ -0,0 +1,31 @@
import { shallow } from 'enzyme';
import CourseCardMenu from '.';
import useCourseCardMenuData from './hooks';
jest.mock('./hooks', () => jest.fn());
describe('CourseCardMenu', () => {
const props = {
cardId: 'test-card-id',
};
const defaultEmailSettingsModal = {
isVisible: false,
show: jest.fn().mockName('emailSettingShow'),
hide: jest.fn().mockName('emailSettingHide'),
};
const defaultUnenrollModal = {
isVisible: false,
show: jest.fn().mockName('unenrollShow'),
hide: jest.fn().mockName('unenrollHide'),
};
test('snapshot', () => {
useCourseCardMenuData.mockReturnValue({
emailSettingsModal: defaultEmailSettingsModal,
unenrollModal: defaultUnenrollModal,
});
const wrapper = shallow(<CourseCardMenu {...props} />);
expect(wrapper).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,87 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CourseCardContent snapshot orientation horizontal 1`] = `
<Fragment>
<Card.ImageCap
src="test-banner-img-src"
srcAlt="Course thumbnail"
/>
<Card.Body>
<Card.Header
actions={
<CourseCardMenu
cardId="test-card-id"
/>
}
title={
<span
data-testid="CourseCardTitle"
>
test-course-name
</span>
}
/>
<Card.Section
className="pt-0"
>
<CourseCardDetails
cardId="test-card-id"
/>
</Card.Section>
<Card.Footer
orientation="vertical"
textElement={
<RelatedProgramsBadge
cardId="test-card-id"
/>
}
>
<CourseCardActions
cardId="test-card-id"
/>
</Card.Footer>
</Card.Body>
</Fragment>
`;
exports[`CourseCardContent snapshot orientation vertical 1`] = `
<Fragment>
<Card.ImageCap
src="test-banner-img-src"
srcAlt="Course thumbnail"
/>
<Card.Body>
<Card.Header
actions={
<CourseCardMenu
cardId="test-card-id"
/>
}
title={
<span
data-testid="CourseCardTitle"
>
test-course-name
</span>
}
/>
<Card.Section
className="pt-0"
>
<CourseCardDetails
cardId="test-card-id"
/>
</Card.Section>
<RelatedProgramsBadge
cardId="test-card-id"
/>
<Card.Footer
orientation="horizontal"
>
<CourseCardActions
cardId="test-card-id"
/>
</Card.Footer>
</Card.Body>
</Fragment>
`;

View File

@@ -0,0 +1,63 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CourseCardLayout snapshot is collapsed 1`] = `
<div
className="mb-4.5 course-card"
data-testid="CourseCard"
>
<Card
orientation="vertical"
>
<div
className="d-flex flex-column w-100"
>
<div
className="d-flex"
>
<CourseCardContent
cardId="test-card-id"
/>
</div>
<div
className="course-card-banners"
data-testid="CourseCardBanners"
>
<CourseCardBanners
cardId="test-card-id"
/>
</div>
</div>
</Card>
</div>
`;
exports[`CourseCardLayout snapshot is not collapsed 1`] = `
<div
className="mb-4.5 course-card"
data-testid="CourseCard"
>
<Card
orientation="horizontal"
>
<div
className="d-flex flex-column w-100"
>
<div
className="d-flex"
>
<CourseCardContent
cardId="test-card-id"
/>
</div>
<div
className="course-card-banners"
data-testid="CourseCardBanners"
>
<CourseCardBanners
cardId="test-card-id"
/>
</div>
</div>
</Card>
</div>
`;

View File

@@ -0,0 +1,21 @@
import { shallow } from 'enzyme';
import { FilterKeys } from 'data/constants/app';
import ActiveCourseFilters from './ActiveCourseFilters';
describe('ActiveCourseFilters', () => {
const props = {
filters: Object.values(FilterKeys),
setFilters: {
remove: jest.fn().mockName('setFilters.remove'),
clear: jest.fn().mockName('setFilters.clear'),
},
handleRemoveFilter: jest.fn().mockName('handleRemoveFilter'),
};
describe('snapshot', () => {
test('renders', () => {
const wrapper = shallow(<ActiveCourseFilters {...props} />);
expect(wrapper).toMatchSnapshot();
});
});
});

View File

@@ -0,0 +1,45 @@
import { shallow } from 'enzyme';
import { breakpoints, useWindowSize } from '@edx/paragon';
import CourseFilterControls from './CourseFilterControls';
import useCourseFilterControlsData from './hooks';
jest.mock('./hooks', () => jest.fn().mockName('useCourseFilterControlsData'));
jest.mock('./components/FilterForm', () => 'FilterForm');
jest.mock('./components/SortForm', () => 'SortForm');
describe('CourseFilterControls', () => {
const props = {
sortBy: 'test-sort-by',
setSortBy: jest.fn().mockName('setSortBy'),
filters: ['test-filter'],
setFilters: {
add: jest.fn().mockName('setFilters.add'),
remove: jest.fn().mockName('setFilters.remove'),
},
};
useCourseFilterControlsData.mockReturnValue({
isOpen: false,
open: jest.fn().mockName('open'),
close: jest.fn().mockName('close'),
target: 'test-target',
setTarget: jest.fn().mockName('setTarget'),
handleFilterChange: jest.fn().mockName('handleFilterChange'),
handleSortChange: jest.fn().mockName('handleSortChange'),
});
describe('snapshot', () => {
test('is mobile', () => {
useWindowSize.mockReturnValueOnce({ width: breakpoints.small.minWidth - 1 });
const wrapper = shallow(<CourseFilterControls {...props} />);
expect(wrapper).toMatchSnapshot();
});
test('is not mobile', () => {
useWindowSize.mockReturnValueOnce({ width: breakpoints.small.minWidth });
const wrapper = shallow(<CourseFilterControls {...props} />);
expect(wrapper).toMatchSnapshot();
});
});
});

View File

@@ -0,0 +1,44 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ActiveCourseFilters snapshot renders 1`] = `
<div
id="course-list-active-filters"
>
<Chip
key="inProgress"
variant="primary"
>
In-Progress
</Chip>
<Chip
key="notStarted"
variant="primary"
>
Not Started
</Chip>
<Chip
key="done"
variant="primary"
>
Done
</Chip>
<Chip
key="notEnrolled"
variant="primary"
>
Not Enrolled
</Chip>
<Chip
key="upgraded"
variant="primary"
>
Upgraded
</Chip>
<Button
onClick={[MockFunction setFilters.clear]}
variant="link"
>
Clear all
</Button>
</div>
`;

View File

@@ -0,0 +1,115 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CourseFilterControls snapshot is mobile 1`] = `
<div
id="course-filter-controls"
>
<Button
iconBefore={[MockFunction icons.Tune]}
onClick={[MockFunction open]}
variant="outline-primary"
>
Refine
</Button>
<Form>
<Sheet
className="w-75"
onClose={[MockFunction close]}
position="left"
show={false}
>
<div
className="p-1 mr-3"
>
<b>
Refine
</b>
</div>
<hr />
<div
className="filter-form-row"
>
<FilterForm
filters={
Array [
"test-filter",
]
}
handleFilterChange={[MockFunction handleFilterChange]}
/>
</div>
<div
className="filter-form-row text-left m-1"
>
<SortForm
handleSortChange={[MockFunction handleSortChange]}
sortBy="test-sort-by"
/>
</div>
<div
className="pgn__modal-close-container"
>
<ModalCloseButton
onClick={[MockFunction close]}
variant="tertiary"
>
<Icon
src={[MockFunction icons.Close]}
/>
</ModalCloseButton>
</div>
</Sheet>
</Form>
</div>
`;
exports[`CourseFilterControls snapshot is not mobile 1`] = `
<div
id="course-filter-controls"
>
<Button
iconBefore={[MockFunction icons.Tune]}
onClick={[MockFunction open]}
variant="outline-primary"
>
Refine
</Button>
<Form>
<ModalPopup
isOpen={false}
onClose={[MockFunction close]}
placement="bottom-end"
positionRef="test-target"
>
<div
className="bg-white p-3 rounded shadow d-flex flex-row"
id="course-filter-controls-card"
>
<div
className="filter-form-col"
>
<FilterForm
filters={
Array [
"test-filter",
]
}
handleFilterChange={[MockFunction handleFilterChange]}
/>
</div>
<hr
className="h-100 bg-primary-200 m-1"
/>
<div
className="filter-form-col text-left m-1"
>
<SortForm
handleSortChange={[MockFunction handleSortChange]}
sortBy="test-sort-by"
/>
</div>
</div>
</ModalPopup>
</Form>
</div>
`;

View File

@@ -0,0 +1,15 @@
import { shallow } from 'enzyme';
import { FilterKeys } from 'data/constants/app';
import Checkbox from './Checkbox';
describe('Checkbox', () => {
describe('snapshot', () => {
Object.keys(FilterKeys).forEach((filterKey) => {
it(`renders ${filterKey}`, () => {
const wrapper = shallow(<Checkbox filterKey={filterKey} />);
expect(wrapper).toMatchSnapshot();
});
});
});
});

View File

@@ -0,0 +1,29 @@
import { shallow } from 'enzyme';
import { FilterKeys } from 'data/constants/app';
import FilterForm, { filterOrder } from './FilterForm';
jest.mock('./Checkbox', () => 'Checkbox');
describe('FilterForm', () => {
const props = {
filters: ['test-filter'],
handleFilterChange: jest.fn().mockName('handleFilterChange'),
};
describe('snapshot', () => {
test('renders', () => {
const wrapper = shallow(<FilterForm {...props} />);
expect(wrapper).toMatchSnapshot();
});
});
test('filterOrder', () => {
expect(filterOrder).toEqual([
FilterKeys.inProgress,
FilterKeys.notStarted,
FilterKeys.done,
FilterKeys.notEnrolled,
FilterKeys.upgraded,
]);
});
});

View File

@@ -0,0 +1,19 @@
import { shallow } from 'enzyme';
import { SortKeys } from 'data/constants/app';
import SortForm from './SortForm';
jest.mock('./Checkbox', () => 'Checkbox');
describe('SortForm', () => {
const props = {
handleSortChange: jest.fn().mockName('handleSortChange'),
sortBy: SortKeys.enrolled,
};
describe('snapshot', () => {
test('renders', () => {
const wrapper = shallow(<SortForm {...props} />);
expect(wrapper).toMatchSnapshot();
});
});
});

View File

@@ -0,0 +1,46 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Checkbox snapshot renders done 1`] = `
<Form.Checkbox
className="py-2"
value="done"
>
Done
</Form.Checkbox>
`;
exports[`Checkbox snapshot renders inProgress 1`] = `
<Form.Checkbox
className="py-2"
value="inProgress"
>
In-Progress
</Form.Checkbox>
`;
exports[`Checkbox snapshot renders notEnrolled 1`] = `
<Form.Checkbox
className="py-2"
value="notEnrolled"
>
Not Enrolled
</Form.Checkbox>
`;
exports[`Checkbox snapshot renders notStarted 1`] = `
<Form.Checkbox
className="py-2"
value="notStarted"
>
Not Started
</Form.Checkbox>
`;
exports[`Checkbox snapshot renders upgraded 1`] = `
<Form.Checkbox
className="py-2"
value="upgraded"
>
Upgraded
</Form.Checkbox>
`;

View File

@@ -0,0 +1,41 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`FilterForm snapshot renders 1`] = `
<Form.Group>
<div
className="filter-form-heading mb-1"
>
Course Status
</div>
<Form.CheckboxSet
name="course-status-filters"
onChange={[MockFunction handleFilterChange]}
value={
Array [
"test-filter",
]
}
>
<Checkbox
filterKey="inProgress"
key="inProgress"
/>
<Checkbox
filterKey="notStarted"
key="notStarted"
/>
<Checkbox
filterKey="done"
key="done"
/>
<Checkbox
filterKey="notEnrolled"
key="notEnrolled"
/>
<Checkbox
filterKey="upgraded"
key="upgraded"
/>
</Form.CheckboxSet>
</Form.Group>
`;

View File

@@ -0,0 +1,29 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SortForm snapshot renders 1`] = `
<Fragment>
<div
className="filter-form-heading mb-1"
>
Sort
</div>
<Form.RadioSet
name="sort"
onChange={[MockFunction handleSortChange]}
value="enrolled"
>
<Form.Radio
className="py-2"
value="enrolled"
>
Last enrolled
</Form.Radio>
<Form.Radio
className="py-2"
value="title"
>
Title (A-Z)
</Form.Radio>
</Form.RadioSet>
</Fragment>
`;

View File

@@ -1,13 +1,20 @@
import React from 'react';
import { useToggle } from '@edx/paragon';
import { StrictDict } from 'utils';
import * as module from './hooks';
export const state = StrictDict({
target: (val) => React.useState(val), // eslint-disable-line
});
export const useCourseFilterControlsData = ({
setFilters,
setSortBy,
}) => {
const [isOpen, open, close] = useToggle(false);
const [target, setTarget] = React.useState(null);
const [target, setTarget] = module.state.target(null);
const handleFilterChange = ({ target: { checked, value } }) => {
const update = checked ? setFilters.add : setFilters.remove;
update(value);

View File

@@ -0,0 +1,82 @@
import { useToggle } from '@edx/paragon';
import { MockUseState } from 'testUtils';
import * as hooks from './hooks';
const state = new MockUseState(hooks);
describe('CourseFilterControls hooks', () => {
let out;
const setSortBy = jest.fn();
const setFilters = {
add: jest.fn(),
remove: jest.fn(),
};
const toggleOpen = jest.fn();
const toggleClose = jest.fn();
describe('state values', () => {
state.testGetter(state.keys.target);
});
beforeEach(() => {
jest.clearAllMocks();
});
describe('useCourseFilterControlsData', () => {
beforeEach(() => {
useToggle.mockReturnValueOnce([false, toggleOpen, toggleClose]);
state.mock();
out = hooks.useCourseFilterControlsData({
setFilters,
setSortBy,
});
});
afterEach(state.restore);
test('default state', () => {
expect(out.isOpen).toEqual(false);
expect(out.open).toEqual(toggleOpen);
expect(out.close).toEqual(toggleClose);
expect(out.target).toEqual(state.stateVals.target);
});
test('isOpen is true when target is set', () => {
useToggle.mockReturnValueOnce([true, toggleOpen, toggleClose]);
expect(out.target).toEqual(null);
state.mockVal(state.keys.target, 'foo');
out = hooks.useCourseFilterControlsData({
setFilters,
setSortBy,
});
expect(out.isOpen).toEqual(true);
expect(out.target).toEqual('foo');
});
test('handle filter change', () => {
const value = 'a';
out.handleFilterChange({
target: {
checked: true,
value,
},
});
expect(setFilters.add).toHaveBeenCalledWith(value);
out.handleFilterChange({
target: {
checked: false,
value,
},
});
expect(setFilters.remove).toHaveBeenCalledWith(value);
});
test('handle sort change', () => {
const value = 'a';
out.handleSortChange({
target: {
value,
},
});
expect(setSortBy).toHaveBeenCalledWith(value);
});
});
});

View File

@@ -0,0 +1,118 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CourseList snapshots with filters 1`] = `
<div
className="course-list-container"
>
<div
id="course-list-heading-container"
>
<h2
className="my-2"
>
My Courses
</h2>
<div
className="text-right"
id="course-filter-controls-container"
>
<CourseFilterControls
abitary="filter"
/>
</div>
</div>
<div
id="course-list-active-filters-container"
>
<ActiveCourseFilters
abitary="filter"
/>
</div>
<div
className="d-flex flex-column flex-grow-1"
>
<Pagination
onPageSelect={[MockFunction setPageNumber]}
pageCount={1}
paginationLabel="Course List"
variant="secondary"
/>
</div>
</div>
`;
exports[`CourseList snapshots with multiple courses and pages 1`] = `
<div
className="course-list-container"
>
<div
id="course-list-heading-container"
>
<h2
className="my-2"
>
My Courses
</h2>
<div
className="text-right"
id="course-filter-controls-container"
>
<CourseFilterControls />
</div>
</div>
<div
className="d-flex flex-column flex-grow-1"
>
<CourseCard
cardId="foo"
key="foo"
/>
<CourseCard
cardId="bar"
key="bar"
/>
<CourseCard
cardId="baz"
key="baz"
/>
<Pagination
onPageSelect={[MockFunction setPageNumber]}
pageCount={3}
paginationLabel="Course List"
variant="secondary"
/>
</div>
</div>
`;
exports[`CourseList snapshots with no filters 1`] = `
<div
className="course-list-container"
>
<div
id="course-list-heading-container"
>
<h2
className="my-2"
>
My Courses
</h2>
<div
className="text-right"
id="course-filter-controls-container"
>
<CourseFilterControls />
</div>
</div>
<div
className="d-flex flex-column flex-grow-1"
>
<Pagination
onPageSelect={[MockFunction setPageNumber]}
pageCount={1}
paginationLabel="Course List"
variant="secondary"
/>
</div>
</div>
`;

View File

@@ -0,0 +1,43 @@
import React from 'react';
import { useCheckboxSetValues } from '@edx/paragon';
import { StrictDict } from 'utils';
import { hooks as appHooks } from 'data/redux';
import { ListPageSize, SortKeys } from 'data/constants/app';
import * as module from './hooks';
export const state = StrictDict({
pageNumber: (val) => React.useState(val), // eslint-disable-line
sortBy: (val) => React.useState(val), // eslint-disable-line
});
export const useCourseListData = () => {
const [pageNumber, setPageNumber] = module.state.pageNumber(1);
const [sortBy, setSortBy] = module.state.sortBy(SortKeys.title);
const [filters, setFilters] = useCheckboxSetValues([]);
const { numPages, visible } = appHooks.useCurrentCourseList({
sortBy,
isAscending: true,
filters,
pageNumber,
pageSize: ListPageSize,
});
const handleRemoveFilter = (filter) => () => setFilters.remove(filter);
return {
numPages,
setPageNumber,
visibleList: visible,
filterOptions: {
sortBy,
setSortBy,
filters,
setFilters,
handleRemoveFilter,
},
showFilters: filters.length > 0,
};
};
export default useCourseListData;

View File

@@ -0,0 +1,55 @@
import { MockUseState } from 'testUtils';
import { hooks as appHooks } from 'data/redux';
import * as hooks from './hooks';
jest.mock('data/redux', () => ({
hooks: {
useCurrentCourseList: jest.fn(),
},
}));
const state = new MockUseState(hooks);
describe('CourseList hooks', () => {
let out;
describe('state values', () => {
state.testGetter(state.keys.pageNumber);
state.testGetter(state.keys.sortBy);
});
describe('useCourseListData', () => {
beforeEach(() => state.mock());
afterEach(state.restore);
test('empty initializes', () => {
appHooks.useCurrentCourseList.mockReturnValueOnce({
numPages: 1,
visible: [],
});
out = hooks.useCourseListData();
expect(out.numPages).toEqual(1);
expect(out.visibleList).toEqual([]);
});
test('page count and visble list', () => {
const result = {
numPages: 2,
visible: ['a', 'b'],
};
appHooks.useCurrentCourseList.mockReturnValueOnce(result);
out = hooks.useCourseListData();
expect(out.numPages).toEqual(result.numPages);
expect(out.visibleList).toEqual(result.visible);
});
test('handle remove filter', () => {
appHooks.useCurrentCourseList.mockReturnValueOnce({
numPages: 1,
visible: [],
});
out = hooks.useCourseListData();
out.filterOptions.handleRemoveFilter('a')();
expect(out.filterOptions.setFilters.remove).toHaveBeenCalledWith('a');
});
});
});

View File

@@ -1,48 +1,19 @@
import React from 'react';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import {
Pagination,
useCheckboxSetValues,
} from '@edx/paragon';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Pagination } from '@edx/paragon';
import { hooks as appHooks } from 'data/redux';
import { ListPageSize, SortKeys } from 'data/constants/app';
import { ActiveCourseFilters, CourseFilterControls } from 'containers/CourseFilterControls';
import CourseCard from 'containers/CourseCard';
import { useCourseListData } from './hooks';
import messages from './messages';
import './index.scss';
export const useCourseListData = () => {
const [pageNumber, setPageNumber] = React.useState(1);
const [sortBy, setSortBy] = React.useState(SortKeys.title);
const [filters, setFilters] = useCheckboxSetValues([]);
const { numPages, visible } = appHooks.useCurrentCourseList({
sortBy,
isAscending: true,
filters,
pageNumber,
pageSize: ListPageSize,
});
const handleRemoveFilter = (filter) => () => setFilters.remove(filter);
return {
numPages,
setPageNumber,
visibleList: visible,
filterOptions: {
sortBy,
setSortBy,
filters,
setFilters,
handleRemoveFilter,
},
showFilters: filters.length > 0,
};
};
export const CourseList = () => {
const { formatMessage } = useIntl();
const {
filterOptions,
setPageNumber,
@@ -53,8 +24,8 @@ export const CourseList = () => {
return (
<div className="course-list-container">
<div id="course-list-heading-container">
<h2 className="my-3">
<FormattedMessage {...messages.myCourses} />
<h2 className="my-2">
{formatMessage(messages.myCourses)}
</h2>
<div
id="course-filter-controls-container"

View File

@@ -0,0 +1,54 @@
import { shallow } from 'enzyme';
import CourseList from '.';
import { useCourseListData } from './hooks';
jest.mock('./hooks', () => ({
useCourseListData: jest.fn(),
}));
jest.mock('containers/CourseCard', () => 'CourseCard');
jest.mock('containers/CourseFilterControls', () => ({
ActiveCourseFilters: 'ActiveCourseFilters',
CourseFilterControls: 'CourseFilterControls',
}));
describe('CourseList', () => {
const defaultCourseListData = {
filterOptions: {},
numPages: 1,
setPageNumber: jest.fn().mockName('setPageNumber'),
showFilters: false,
visibleList: [],
};
const createWrapper = (courseListData) => {
useCourseListData.mockReturnValueOnce({
...defaultCourseListData,
...courseListData,
});
return shallow(<CourseList />);
};
describe('snapshots', () => {
test('with no filters', () => {
const wrapper = createWrapper();
expect(wrapper).toMatchSnapshot();
});
test('with filters', () => {
const wrapper = createWrapper({
filterOptions: {
abitary: 'filter',
},
showFilters: true,
});
expect(wrapper).toMatchSnapshot();
});
test('with multiple courses and pages', () => {
const wrapper = createWrapper({
visibleList: [{ cardId: 'foo' }, { cardId: 'bar' }, { cardId: 'baz' }],
numPages: 3,
});
expect(wrapper).toMatchSnapshot();
});
});
});

View File

@@ -0,0 +1,86 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Dashboard snapshots there are available dashboards 1`] = `
<div
className="d-flex flex-column p-2"
id="dashboard-container"
>
<EnterpriseDashboardModal />
<EmptyCourse />
</div>
`;
exports[`Dashboard snapshots there are courses 1`] = `
<div
className="d-flex flex-column p-2"
id="dashboard-container"
>
<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 there are no courses 1`] = `
<div
className="d-flex flex-column p-2"
id="dashboard-container"
>
<EmptyCourse />
</div>
`;
exports[`Dashboard snapshots there is a select session modal 1`] = `
<div
className="d-flex flex-column p-2"
id="dashboard-container"
>
<EmptyCourse />
</div>
`;

View File

@@ -0,0 +1,120 @@
import { shallow } from 'enzyme';
import { hooks } 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 Dashboard from '.';
jest.mock('data/redux', () => ({
thunkActions: {
app: {
initialize: jest.fn(),
},
},
hooks: {
useHasCourses: jest.fn(),
useHasAvailableDashboards: jest.fn(),
useShowSelectSessionModal: jest.fn(),
},
}));
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');
describe('Dashboard', () => {
const createWrapper = ({
hasCourses,
hasAvailableDashboards,
showSelectSessionModal,
}) => {
hooks.useHasCourses.mockReturnValueOnce(hasCourses);
hooks.useHasAvailableDashboards.mockReturnValueOnce(hasAvailableDashboards);
hooks.useShowSelectSessionModal.mockReturnValueOnce(showSelectSessionModal);
return shallow(<Dashboard />);
};
describe('snapshots', () => {
test('there are courses', () => {
const wrapper = createWrapper({
hasCourses: true,
hasAvailableDashboards: false,
showSelectSessionModal: false,
});
expect(wrapper).toMatchSnapshot();
});
test('there are no courses', () => {
const wrapper = createWrapper({
hasCourses: false,
hasAvailableDashboards: false,
showSelectSessionModal: false,
});
expect(wrapper).toMatchSnapshot();
});
test('there are available dashboards', () => {
const wrapper = createWrapper({
hasCourses: false,
hasAvailableDashboards: true,
showSelectSessionModal: false,
});
expect(wrapper).toMatchSnapshot();
});
test('there is a select session modal', () => {
const wrapper = createWrapper({
hasCourses: false,
hasAvailableDashboards: false,
showSelectSessionModal: true,
});
expect(wrapper).toMatchSnapshot();
});
});
describe('behavior', () => {
it('initializes the app without courses', () => {
const wrapper = createWrapper({
hasCourses: false,
hasAvailableDashboards: false,
showSelectSessionModal: 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,
});
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,
});
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);
});
});
});

View File

@@ -0,0 +1,46 @@
import { shallow } from 'enzyme';
import { hooks as appHooks } from 'data/redux';
import { AuthenticatedUserDropdown } from './AuthenticatedUserDropdown';
import { useIsCollapsed } from './hooks';
jest.mock('@edx/frontend-platform/react', () => ({
AppContext: {
authenticatedUser: {
profileImage: 'profileImage',
},
},
}));
jest.mock('data/redux', () => ({
hooks: {
useEnterpriseDashboardData: jest.fn(),
},
}));
jest.mock('containers/LearnerDashboardHeader/hooks', () => ({
useIsCollapsed: jest.fn(),
}));
describe('AuthenticatedUserDropdown', () => {
const props = {
username: 'username',
};
const defaultDashboardData = {
label: 'label',
url: 'url',
};
describe('snapshots', () => {
test('with enterprise dashboard', () => {
appHooks.useEnterpriseDashboardData.mockReturnValueOnce(defaultDashboardData);
useIsCollapsed.mockReturnValueOnce(true);
const wrapper = shallow(<AuthenticatedUserDropdown {...props} />);
expect(wrapper).toMatchSnapshot();
});
test('without enterprise dashboard and expanded', () => {
appHooks.useEnterpriseDashboardData.mockReturnValueOnce(null);
useIsCollapsed.mockReturnValueOnce(false);
const wrapper = shallow(<AuthenticatedUserDropdown {...props} />);
expect(wrapper).toMatchSnapshot();
});
});
});

View File

@@ -3,12 +3,14 @@ import classNames from 'classnames';
import PropTypes from 'prop-types';
import { getConfig } from '@edx/frontend-platform';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Image } from '@edx/paragon';
import messages from './messages';
export const GreetingBanner = ({ size }) => {
const { formatMessage } = useIntl();
let greetMessage;
const hour = new Date().getHours();
@@ -39,11 +41,11 @@ export const GreetingBanner = ({ size }) => {
{isSmall
? (
<h5 className="text-center text-accent-b">
<FormattedMessage {...greetMessage} />
{formatMessage(greetMessage)}
</h5>
) : (
<h1 className="text-center text-accent-b">
<FormattedMessage {...greetMessage} />
{formatMessage(greetMessage)}
</h1>
)}
</div>

View File

@@ -0,0 +1,29 @@
import { shallow } from 'enzyme';
import { GreetingBanner } from './GreetingBanner';
describe('GreetingBanner', () => {
const morning = new Date('2021-01-01T11:59:59.999');
const afternoon = new Date('2021-01-01T16:59:59.999');
const evening = new Date('2021-01-01T18:00:00');
afterAll(() => jest.useRealTimers());
describe('snapshots', () => {
['small', 'large'].forEach((size) => {
test(`with size ${size} and morning`, () => {
jest.useFakeTimers('modern').setSystemTime(morning);
const wrapper = shallow(<GreetingBanner size={size} />);
expect(wrapper).toMatchSnapshot();
});
test(`with size ${size} and afternoon`, () => {
jest.useFakeTimers('modern').setSystemTime(afternoon);
const wrapper = shallow(<GreetingBanner size={size} />);
expect(wrapper).toMatchSnapshot();
});
test(`with size ${size} and evening`, () => {
jest.useFakeTimers('modern').setSystemTime(evening);
const wrapper = shallow(<GreetingBanner size={size} />);
expect(wrapper).toMatchSnapshot();
});
});
});
});

View File

@@ -0,0 +1,121 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AuthenticatedUserDropdown snapshots with enterprise dashboard 1`] = `
<Dropdown
className="user-dropdown"
>
<Dropdown.Toggle
id="user"
src="profileImage"
variant="primary"
>
<span
className="d-none d-md-inline"
data-hj-suppress={true}
>
username
</span>
</Dropdown.Toggle>
<Dropdown.Menu
className="dropdown-menu-right"
>
<Dropdown.Header>
SWITCH DASHBOARD
</Dropdown.Header>
<Dropdown.Item
as="a"
className="active"
href="/edx-dashboard"
>
Personal
</Dropdown.Item>
<Dropdown.Item
as="a"
href="url"
key="label"
>
label
Dashboard
</Dropdown.Item>
<Dropdown.Divider />
<Dropdown.Item
href="http://localhost:18000/u/username"
>
Profile
</Dropdown.Item>
<Dropdown.Item
href="http://localhost:18000/u/username"
>
View Programs
</Dropdown.Item>
<Dropdown.Item
href="http://localhost:18000/account/settings"
>
Account
</Dropdown.Item>
<Dropdown.Item>
Help
</Dropdown.Item>
<Dropdown.Divider />
<Dropdown.Item
href="http://localhost:18000/logout"
>
Sign Out
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
`;
exports[`AuthenticatedUserDropdown snapshots without enterprise dashboard and expanded 1`] = `
<Dropdown
className="user-dropdown"
>
<Dropdown.Toggle
id="user"
src="profileImage"
variant="primary"
>
<span
className="d-none d-md-inline"
data-hj-suppress={true}
>
username
</span>
</Dropdown.Toggle>
<Dropdown.Menu
className="dropdown-menu-right"
>
<Dropdown.Header>
SWITCH DASHBOARD
</Dropdown.Header>
<Dropdown.Item
as="a"
className="active"
href="/edx-dashboard"
>
Personal
</Dropdown.Item>
<Dropdown.Divider />
<Dropdown.Item
href="http://localhost:18000/u/username"
>
Profile
</Dropdown.Item>
<Dropdown.Item
href="http://localhost:18000/account/settings"
>
Account
</Dropdown.Item>
<Dropdown.Item>
Help
</Dropdown.Item>
<Dropdown.Divider />
<Dropdown.Item
href="http://localhost:18000/logout"
>
Sign Out
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
`;

View File

@@ -0,0 +1,151 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`GreetingBanner snapshots with size large and afternoon 1`] = `
<div
className="d-flex align-items-center justify-content-center p-5"
>
<Image
alt="localhost"
className="d-block"
src="https://edx-cdn.org/v3/default/logo-white.svg"
style={
Object {
"width": "148px",
}
}
/>
<div
className="greetings-slash-container-large bg-brand-500"
/>
<h1
className="text-center text-accent-b"
>
Good Afternoon!
</h1>
</div>
`;
exports[`GreetingBanner snapshots with size large and evening 1`] = `
<div
className="d-flex align-items-center justify-content-center p-5"
>
<Image
alt="localhost"
className="d-block"
src="https://edx-cdn.org/v3/default/logo-white.svg"
style={
Object {
"width": "148px",
}
}
/>
<div
className="greetings-slash-container-large bg-brand-500"
/>
<h1
className="text-center text-accent-b"
>
Good Evening!
</h1>
</div>
`;
exports[`GreetingBanner snapshots with size large and morning 1`] = `
<div
className="d-flex align-items-center justify-content-center p-5"
>
<Image
alt="localhost"
className="d-block"
src="https://edx-cdn.org/v3/default/logo-white.svg"
style={
Object {
"width": "148px",
}
}
/>
<div
className="greetings-slash-container-large bg-brand-500"
/>
<h1
className="text-center text-accent-b"
>
Good Morning!
</h1>
</div>
`;
exports[`GreetingBanner snapshots with size small and afternoon 1`] = `
<div
className="d-flex align-items-center justify-content-center p-3.5"
>
<Image
alt="localhost"
className="d-block"
src="https://edx-cdn.org/v3/default/logo-white.svg"
style={
Object {
"width": "46px",
}
}
/>
<div
className="greetings-slash-container-small bg-brand-500"
/>
<h5
className="text-center text-accent-b"
>
Good Afternoon!
</h5>
</div>
`;
exports[`GreetingBanner snapshots with size small and evening 1`] = `
<div
className="d-flex align-items-center justify-content-center p-3.5"
>
<Image
alt="localhost"
className="d-block"
src="https://edx-cdn.org/v3/default/logo-white.svg"
style={
Object {
"width": "46px",
}
}
/>
<div
className="greetings-slash-container-small bg-brand-500"
/>
<h5
className="text-center text-accent-b"
>
Good Evening!
</h5>
</div>
`;
exports[`GreetingBanner snapshots with size small and morning 1`] = `
<div
className="d-flex align-items-center justify-content-center p-3.5"
>
<Image
alt="localhost"
className="d-block"
src="https://edx-cdn.org/v3/default/logo-white.svg"
style={
Object {
"width": "46px",
}
}
/>
<div
className="greetings-slash-container-small bg-brand-500"
/>
<h5
className="text-center text-accent-b"
>
Good Morning!
</h5>
</div>
`;

View File

@@ -0,0 +1,74 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LearnerDashboardHeader UserMenu snapshots with authenticated user 1`] = `
<AuthenticatedUserDropdown
username="test-username"
/>
`;
exports[`LearnerDashboardHeader UserMenu snapshots without authenticated user 1`] = `""`;
exports[`LearnerDashboardHeader snapshots with collapsed 1`] = `
<Fragment>
<ConfirmEmailBanner />
<div
className="flex-column bg-primary"
>
<header
className="learner-dashboard-header"
>
<div
className="d-flex"
>
<div
className="flex-grow-1"
>
<GreetingBanner
size="small"
/>
</div>
<div
className="my-auto ml-1"
>
<UserMenu />
</div>
</div>
</header>
</div>
<MasqueradeBar />
<hr />
</Fragment>
`;
exports[`LearnerDashboardHeader snapshots without collapsed 1`] = `
<Fragment>
<ConfirmEmailBanner />
<div
className="flex-column bg-primary"
>
<header
className="learner-dashboard-header"
>
<div
className="d-flex"
>
<Button
iconBefore={[MockFunction icons.Program]}
variant="inverse-tertiary"
>
Switch to Programs
</Button>
<div
className="flex-grow-1"
/>
<UserMenu />
</div>
</header>
<GreetingBanner
size="large"
/>
</div>
<MasqueradeBar />
<hr />
</Fragment>
`;

View File

@@ -0,0 +1,15 @@
import { useWindowSize, breakpoints } from '@edx/paragon';
import { useIsCollapsed } from './hooks';
describe('LearnerDashboardHeader hooks', () => {
describe('useIsCollapsed', () => {
test('large screen is not collapsed', () => {
useWindowSize.mockReturnValueOnce({ width: breakpoints.large.maxWidth + 1 });
expect(useIsCollapsed()).toEqual(false);
});
test('small screen is collapsed', () => {
useWindowSize.mockReturnValueOnce({ width: breakpoints.large.maxWidth - 1 });
expect(useIsCollapsed()).toEqual(true);
});
});
});

View File

@@ -6,10 +6,11 @@ import { Program } from '@edx/paragon/icons';
import { Button } from '@edx/paragon';
import MasqueradeBar from 'containers/MasqueradeBar';
import AuthenticatedUserDropdown from './AuthenticatedUserDropdown';
import AuthenticatedUserDropdown from './AuthenticatedUserDropdown';
import GreetingBanner from './GreetingBanner';
import ConfirmEmailBanner from './ConfirmEmailBanner';
import { useIsCollapsed } from './hooks';
import messages from './messages';
import './index.scss';

View File

@@ -0,0 +1,51 @@
import { shallow } from 'enzyme';
import { AppContext } from '@edx/frontend-platform/react';
import LearnerDashboardHeader, { UserMenu } from '.';
import { useIsCollapsed } from './hooks';
jest.mock('@edx/frontend-platform/react', () => ({
AppContext: {
authenticatedUser: {
username: 'test-username',
},
},
}));
jest.mock('./hooks', () => ({
useIsCollapsed: jest.fn(),
}));
jest.mock('containers/MasqueradeBar', () => 'MasqueradeBar');
jest.mock('./ConfirmEmailBanner', () => 'ConfirmEmailBanner');
jest.mock('./AuthenticatedUserDropdown', () => 'AuthenticatedUserDropdown');
jest.mock('./GreetingBanner', () => 'GreetingBanner');
describe('LearnerDashboardHeader', () => {
describe('snapshots', () => {
test('with collapsed', () => {
useIsCollapsed.mockReturnValueOnce(true);
const wrapper = shallow(<LearnerDashboardHeader />);
expect(wrapper).toMatchSnapshot();
});
test('without collapsed', () => {
useIsCollapsed.mockReturnValueOnce(false);
const wrapper = shallow(<LearnerDashboardHeader />);
expect(wrapper).toMatchSnapshot();
});
});
describe('UserMenu', () => {
describe('snapshots', () => {
test('with authenticated user', () => {
const wrapper = shallow(<UserMenu />);
expect(wrapper).toMatchSnapshot();
});
test('without authenticated user', () => {
AppContext.authenticatedUser = null;
const wrapper = shallow(<UserMenu />);
expect(wrapper).toMatchSnapshot();
});
});
});
});

View File

@@ -0,0 +1,33 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`WidgetSidebar snapshots default 1`] = `
<div
className="widget-sidebar"
>
<div
className="d-flex"
>
<Card
orientation="horizontal"
>
<Card.ImageCap
src="icon/mock/path"
srcAlt="course side widget"
/>
<Card.Body
className="m-auto pr-2"
>
<h4>
Looking for a new challenge?
</h4>
<Hyperlink
destination="#"
variant="brand"
>
Explore courses
</Hyperlink>
</Card.Body>
</Card>
</div>
</div>
`;

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Hyperlink, Card } from '@edx/paragon';
import moreCoursesSVG from 'assets/more-courses-sidewidget.svg';
@@ -8,25 +8,28 @@ import messages from './messages';
import './index.scss';
export const WidgetSidebar = () => (
<div className="widget-sidebar px-2 mt-5 pt-3">
<div className="d-flex">
<Card orientation="horizontal">
<Card.ImageCap
src={moreCoursesSVG}
srcAlt="course side widget"
/>
<Card.Body className="m-auto pr-2">
<h4>
<FormattedMessage {...messages.lookingForChallengePrompt} />
</h4>
<Hyperlink variant="brand" destination="#">
<FormattedMessage {...messages.findCoursesButton} />
</Hyperlink>
</Card.Body>
</Card>
export const WidgetSidebar = () => {
const { formatMessage } = useIntl();
return (
<div className="widget-sidebar">
<div className="d-flex">
<Card orientation="horizontal">
<Card.ImageCap
src={moreCoursesSVG}
srcAlt="course side widget"
/>
<Card.Body className="m-auto pr-2">
<h4>
{formatMessage(messages.lookingForChallengePrompt)}
</h4>
<Hyperlink variant="brand" destination="#">
{formatMessage(messages.findCoursesButton)}
</Hyperlink>
</Card.Body>
</Card>
</div>
</div>
</div>
);
);
};
export default WidgetSidebar;

View File

@@ -0,0 +1,12 @@
import { shallow } from 'enzyme';
import WidgetSidebar from '.';
describe('WidgetSidebar', () => {
describe('snapshots', () => {
test('default', () => {
const wrapper = shallow(<WidgetSidebar />);
expect(wrapper).toMatchSnapshot();
});
});
});