Skip to content

Commit

Permalink
feat(AU-1863): Add feedback widget UI mock
Browse files Browse the repository at this point in the history
* feat(AU-1863): Add feedback widget UI mock

* feat(AU-1863): Add unit tests

* feat(AU-1863): Fix snapshot

* feat(AU-1863): Clean Sequence component logEvent calls

* feat(AU-1863): Clean unit test

* feat(AU-1863): Put feedback widget behind whole course translation flag

* feat(AU-1863): Fix useFeedbackWidget test
  • Loading branch information
Rodra authored Mar 4, 2024
1 parent b2c4e98 commit a842f82
Show file tree
Hide file tree
Showing 8 changed files with 377 additions and 46 deletions.
104 changes: 58 additions & 46 deletions src/courseware/course/sequence/Sequence.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import messages from './messages';
import HiddenAfterDue from './hidden-after-due';
import { SequenceNavigation, UnitNavigation } from './sequence-navigation';
import SequenceContent from './SequenceContent';
import FeedbackWidget from './Unit/feedback-widget';

const Sequence = ({
unitId,
Expand All @@ -37,7 +38,11 @@ const Sequence = ({
previousSequenceHandler,
}) => {
const intl = useIntl();
const course = useModel('coursewareMeta', courseId);
const {
canAccessProctoredExams,
license,
wholeCourseTranslationEnabled,
} = useModel('coursewareMeta', courseId);
const {
isStaff,
originalUserIsStaff,
Expand All @@ -49,7 +54,8 @@ const Sequence = ({
const shouldDisplayNotificationTriggerInSequence = useWindowSize().width < breakpoints.small.minWidth;
const enableNewSidebar = getConfig().ENABLE_NEW_SIDEBAR;

const handleNext = () => {
const handleNext = (position) => {
logEvent('edx.ui.lms.sequence.next_selected', position);
const nextIndex = sequence.unitIds.indexOf(unitId) + 1;
if (nextIndex < sequence.unitIds.length) {
const newUnitId = sequence.unitIds[nextIndex];
Expand All @@ -59,7 +65,8 @@ const Sequence = ({
}
};

const handlePrevious = () => {
const handlePrevious = (position) => {
logEvent('edx.ui.lms.sequence.previous_selected', position);
const previousIndex = sequence.unitIds.indexOf(unitId) - 1;
if (previousIndex >= 0) {
const newUnitId = sequence.unitIds[previousIndex];
Expand Down Expand Up @@ -144,55 +151,60 @@ const Sequence = ({
const gated = sequence && sequence.gatedContent !== undefined && sequence.gatedContent.gated;

const defaultContent = (
<div className="sequence-container d-inline-flex flex-row">
<div className={classNames('sequence w-100', { 'position-relative': shouldDisplayNotificationTriggerInSequence })}>
<SequenceNavigation
sequenceId={sequenceId}
unitId={unitId}
className="mb-4"
nextHandler={() => {
logEvent('edx.ui.lms.sequence.next_selected', 'top');
handleNext();
}}
onNavigate={(destinationUnitId) => {
logEvent('edx.ui.lms.sequence.tab_selected', 'top', destinationUnitId);
handleNavigate(destinationUnitId);
}}
previousHandler={() => {
logEvent('edx.ui.lms.sequence.previous_selected', 'top');
handlePrevious();
}}
/>
{shouldDisplayNotificationTriggerInSequence && (
enableNewSidebar === 'true' ? <NewSidebarTriggers /> : <SidebarTriggers />
)}

<div className="unit-container flex-grow-1">
<SequenceContent
courseId={courseId}
gated={gated}
sequenceId={sequenceId}
unitId={unitId}
unitLoadedHandler={handleUnitLoaded}
/>
{unitHasLoaded && (
<UnitNavigation
<>
<div className="sequence-container d-inline-flex flex-row">
<div className={classNames('sequence w-100', { 'position-relative': shouldDisplayNotificationTriggerInSequence })}>
<SequenceNavigation
sequenceId={sequenceId}
unitId={unitId}
onClickPrevious={() => {
logEvent('edx.ui.lms.sequence.previous_selected', 'bottom');
handlePrevious();
className="mb-4"
nextHandler={() => {
handleNext('top');
}}
onClickNext={() => {
logEvent('edx.ui.lms.sequence.next_selected', 'bottom');
handleNext();
onNavigate={(destinationUnitId) => {
logEvent('edx.ui.lms.sequence.tab_selected', 'top', destinationUnitId);
handleNavigate(destinationUnitId);
}}
previousHandler={() => {
handlePrevious('top');
}}
/>
{shouldDisplayNotificationTriggerInSequence && (
enableNewSidebar === 'true' ? <NewSidebarTriggers /> : <SidebarTriggers />
)}

<div className="unit-container flex-grow-1">
<SequenceContent
courseId={courseId}
gated={gated}
sequenceId={sequenceId}
unitId={unitId}
unitLoadedHandler={handleUnitLoaded}
/>
{unitHasLoaded && (
<UnitNavigation
sequenceId={sequenceId}
unitId={unitId}
onClickPrevious={() => {
handlePrevious('bottom');
}}
onClickNext={() => {
handleNext('bottom');
}}
/>
)}
</div>
</div>
{enableNewSidebar === 'true' ? <NewSidebar /> : <Sidebar />}
</div>
{enableNewSidebar === 'true' ? <NewSidebar /> : <Sidebar />}
</div>
{
wholeCourseTranslationEnabled && (
<div className="sequence-container d-inline-flex flex-row">
<FeedbackWidget />
</div>
)
}
</>
);

if (sequenceStatus === 'loaded') {
Expand All @@ -203,11 +215,11 @@ const Sequence = ({
courseId={courseId}
isStaff={isStaff}
originalUserIsStaff={originalUserIsStaff}
canAccessProctoredExams={course.canAccessProctoredExams}
canAccessProctoredExams={canAccessProctoredExams}
>
{defaultContent}
</SequenceExamWrapper>
<CourseLicense license={course.license || undefined} />
<CourseLicense license={license || undefined} />
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<FeedbackWidget /> renders 1`] = `
<div
className="sequence w-100"
>
<div
className="ml-4 mr-2"
>
<ActionRow>
Rate this page translation
<Spacer />
<div>
<IconButton
alt="positive-feedback"
className="m-1"
iconAs="Icon"
id="positive-feedback-button"
onClick={[MockFunction sendFeedback]}
src="ThumbUpOutline"
variant="secondary"
/>
<IconButton
alt="negative-feedback"
className="mr-2"
iconAs="Icon"
id="negative-feedback-button"
onClick={[MockFunction sendFeedback]}
src="ThumbDownOffAlt"
variant="secondary"
/>
</div>
<div
className="mb-1 text-light action-row-divider"
>
|
</div>
<div>
<IconButton
alt="close-feedback"
className="ml-1 mr-2 float-right"
iconAs="Icon"
id="close-feedback-button"
onClick={[MockFunction closeFeedbackWidget]}
src="Close"
variant="secondary"
/>
</div>
</ActionRow>
</div>
</div>
`;
99 changes: 99 additions & 0 deletions src/courseware/course/sequence/Unit/feedback-widget/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useIntl } from '@edx/frontend-platform/i18n';
import { ActionRow, IconButton, Icon } from '@edx/paragon';
import { Close, ThumbUpOutline, ThumbDownOffAlt } from '@edx/paragon/icons';

import './index.scss';
import messages from './messages';
import useFeedbackWidget from './useFeedbackWidget';

const FeedbackWidget = ({
courseId,
languageCode,
unitId,
userId,
}) => {
const { formatMessage } = useIntl();
const {
closeFeedbackWidget,
sendFeedback,
showFeedbackWidget,
showGratitudeText,
} = useFeedbackWidget({
courseId,
languageCode,
unitId,
userId,
});
return (
(showFeedbackWidget || showGratitudeText) && (
<div className="sequence w-100">
{
showFeedbackWidget && (
<div className="ml-4 mr-2">
<ActionRow>
{formatMessage(messages.rateTranslationText)}
<ActionRow.Spacer />
<div>
<IconButton
src={ThumbUpOutline}
iconAs={Icon}
alt="positive-feedback"
onClick={sendFeedback}
variant="secondary"
className="m-1"
id="positive-feedback-button"
/>
<IconButton
src={ThumbDownOffAlt}
iconAs={Icon}
alt="negative-feedback"
onClick={sendFeedback}
variant="secondary"
className="mr-2"
id="negative-feedback-button"
/>
</div>
<div className="mb-1 text-light action-row-divider">
|
</div>
<div>
<IconButton
src={Close}
iconAs={Icon}
alt="close-feedback"
onClick={closeFeedbackWidget}
variant="secondary"
className="ml-1 mr-2 float-right"
id="close-feedback-button"
/>
</div>
</ActionRow>
</div>
)
}
{
showGratitudeText && (
<div className="ml-4 mr-4">
<ActionRow className="m-2 justify-content-center">
{formatMessage(messages.gratitudeText)}
</ActionRow>
</div>
)
}
</div>
)
);
};

FeedbackWidget.propTypes = {
courseId: PropTypes.string.isRequired,
languageCode: PropTypes.string.isRequired,
userId: PropTypes.string.isRequired,
unitId: PropTypes.string.isRequired,
};

FeedbackWidget.defaultProps = {};

export default FeedbackWidget;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.action-row-divider {
font-size: 31px;
font-weight: 100;
}
51 changes: 51 additions & 0 deletions src/courseware/course/sequence/Unit/feedback-widget/index.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { shallow } from '@edx/react-unit-test-utils';

import FeedbackWidget from './index';

jest.mock('@edx/paragon', () => jest.requireActual('@edx/react-unit-test-utils').mockComponents({
ActionRow: {
Spacer: 'Spacer',
},
IconButton: 'IconButton',
Icon: 'Icon',
}));
jest.mock('@edx/paragon/icons', () => ({
Close: 'Close',
ThumbUpOutline: 'ThumbUpOutline',
ThumbDownOffAlt: 'ThumbDownOffAlt',
}));
jest.mock('./useFeedbackWidget', () => () => ({
closeFeedbackWidget: jest.fn().mockName('closeFeedbackWidget'),
openFeedbackWidget: jest.fn().mockName('openFeedbackWidget'),
sendFeedback: jest.fn().mockName('sendFeedback'),
showFeedbackWidget: true,
showGratitudeText: false,
}));
jest.mock('@edx/frontend-platform/i18n', () => {
const i18n = jest.requireActual('@edx/frontend-platform/i18n');
const { formatMessage } = jest.requireActual('@edx/react-unit-test-utils');
// this provide consistent for the test on different platform/timezone
const formatDate = jest.fn(date => new Date(date).toISOString()).mockName('useIntl.formatDate');
return {
...i18n,
useIntl: jest.fn(() => ({
formatMessage,
formatDate,
})),
defineMessages: m => m,
FormattedMessage: () => 'FormattedMessage',
};
});

describe('<FeedbackWidget />', () => {
const props = {
courseId: 'course-v1:edX+DemoX+Demo_Course',
languageCode: 'es',
unitId: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@37b72b3915204b70acb00c55b604b563',
userId: '123',
};
it('renders', () => {
const wrapper = shallow(<FeedbackWidget {...props} />);
expect(wrapper.snapshot).toMatchSnapshot();
});
});
16 changes: 16 additions & 0 deletions src/courseware/course/sequence/Unit/feedback-widget/messages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { defineMessages } from '@edx/frontend-platform/i18n';

const messages = defineMessages({
rateTranslationText: {
id: 'feedbackWidget.rateTranslationText',
defaultMessage: 'Rate this page translation',
description: 'Title for the feedback widget action row.',
},
gratitudeText: {
id: 'feedbackWidget.gratitudeText',
defaultMessage: 'Thank you! Your feedback matters.',
description: 'Title for secondary action row.',
},
});

export default messages;
Loading

0 comments on commit a842f82

Please sign in to comment.