From 6aaedfc5005ef6ade545501c3eaffbf066a1ed6f Mon Sep 17 00:00:00 2001 From: Kristin Aoki <42981026+KristinAoki@users.noreply.github.com> Date: Wed, 3 May 2023 09:55:31 -0400 Subject: [PATCH] feat: add social share widget (#323) --- .../__snapshots__/index.test.jsx.snap | 95 +++++++++ .../components/SocialShareWidget/index.jsx | 110 +++++++++++ .../SocialShareWidget/index.test.jsx | 185 ++++++++++++++++++ .../components/SocialShareWidget/messages.js | 46 +++++ .../__snapshots__/index.test.jsx.snap | 47 ----- .../components/VideoSourceWidget/index.jsx | 41 +--- .../components/VideoSettingsModal/index.jsx | 2 + src/editors/data/redux/thunkActions/video.js | 30 ++- .../data/redux/thunkActions/video.test.js | 68 ++++++- src/editors/data/redux/video/reducer.js | 6 +- src/editors/data/redux/video/selectors.js | 1 + src/editors/data/services/cms/api.js | 2 +- src/editors/data/services/cms/api.test.js | 7 +- 13 files changed, 540 insertions(+), 100 deletions(-) create mode 100644 src/editors/containers/VideoEditor/components/VideoSettingsModal/components/SocialShareWidget/__snapshots__/index.test.jsx.snap create mode 100644 src/editors/containers/VideoEditor/components/VideoSettingsModal/components/SocialShareWidget/index.jsx create mode 100644 src/editors/containers/VideoEditor/components/VideoSettingsModal/components/SocialShareWidget/index.test.jsx create mode 100644 src/editors/containers/VideoEditor/components/VideoSettingsModal/components/SocialShareWidget/messages.js diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/SocialShareWidget/__snapshots__/index.test.jsx.snap b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/SocialShareWidget/__snapshots__/index.test.jsx.snap new file mode 100644 index 000000000..9042c97b5 --- /dev/null +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/SocialShareWidget/__snapshots__/index.test.jsx.snap @@ -0,0 +1,95 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SocialShareWidget rendered with videoSharingEnabled true and allowVideoSharing value equals false should have subtitle with text that reads Enabled 1`] = ` + +
+ +
+ +
+ This video is shareable to social media +
+
+
+ +
+
+ + Learn more about social sharing + +
+
+`; + +exports[`SocialShareWidget rendered with videoSharingEnabled true and allowVideoSharing value equals true should have subtitle with text that reads Enabled 1`] = ` + +
+ +
+ +
+ This video is shareable to social media +
+
+
+ +
+
+ + Learn more about social sharing + +
+
+`; + +exports[`SocialShareWidget rendered with with videoSharingEnabled false should return null 1`] = `""`; diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/SocialShareWidget/index.jsx b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/SocialShareWidget/index.jsx new file mode 100644 index 000000000..dfef57b4f --- /dev/null +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/SocialShareWidget/index.jsx @@ -0,0 +1,110 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import { + FormattedMessage, + injectIntl, + intlShape, +} from '@edx/frontend-platform/i18n'; +import { + Hyperlink, + Form, +} from '@edx/paragon'; + +import { selectors, actions } from '../../../../../../data/redux'; +import CollapsibleFormWidget from '../CollapsibleFormWidget'; +import messages from './messages'; + +/** + * Collapsible Form widget controlling video thumbnail + */ +export const SocialShareWidget = ({ + // injected + intl, + // redux + allowVideoSharing, + videoSharingEnabledForCourse, + videoSharingLearnMoreLink, + updateField, +}) => { + const isSetByCourse = allowVideoSharing.level === 'course'; + const getSubtitle = () => { + if (allowVideoSharing.value) { + return intl.formatMessage(messages.enabledSubtitle); + } + return intl.formatMessage(messages.disabledSubtitle); + }; + + return (videoSharingEnabledForCourse ? ( + +
+ +
+ updateField({ + allowVideoSharing: { + ...allowVideoSharing, + value: e.target.checked, + }, + })} + > +
+ {intl.formatMessage(messages.socialSharingCheckboxLabel)} +
+
+
+ +
+ {isSetByCourse && ( +
+ +
+ )} +
+ + {intl.formatMessage(messages.learnMoreLinkLabel)} + +
+
+ ) : null); +}; + +SocialShareWidget.defaultProps = { + allowVideoSharing: { + level: 'block', + value: false, + }, + videoSharingEnabledForCourse: false, +}; + +SocialShareWidget.propTypes = { + // injected + intl: intlShape.isRequired, + // redux + allowVideoSharing: PropTypes.shape({ + level: PropTypes.string.isRequired, + value: PropTypes.bool.isRequired, + }), + videoSharingEnabledForCourse: PropTypes.bool, + videoSharingLearnMoreLink: PropTypes.string.isRequired, + updateField: PropTypes.func.isRequired, +}; + +export const mapStateToProps = (state) => ({ + allowVideoSharing: selectors.video.allowVideoSharing(state), + videoSharingLearnMoreLink: selectors.video.videoSharingLearnMoreLink(state), + videoSharingEnabledForCourse: selectors.video.videoSharingEnabledForCourse(state), +}); + +export const mapDispatchToProps = (dispatch) => ({ + updateField: (stateUpdate) => dispatch(actions.video.updateField(stateUpdate)), +}); + +export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(SocialShareWidget)); diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/SocialShareWidget/index.test.jsx b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/SocialShareWidget/index.test.jsx new file mode 100644 index 000000000..e329a7318 --- /dev/null +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/SocialShareWidget/index.test.jsx @@ -0,0 +1,185 @@ +import React from 'react'; +import { shallow } from 'enzyme'; + +import { formatMessage } from '../../../../../../../testUtils'; +import { actions, selectors } from '../../../../../../data/redux'; +import { SocialShareWidget, mapStateToProps, mapDispatchToProps } from '.'; +import messages from './messages'; + +jest.mock('react', () => { + const updateState = jest.fn(); + return { + ...jest.requireActual('react'), + updateState, + useContext: jest.fn(() => ({ license: ['error.license', jest.fn().mockName('error.setLicense')] })), + }; +}); + +jest.mock('../../../../../../data/redux', () => ({ + actions: { + video: { + updateField: jest.fn().mockName('actions.video.updateField'), + }, + }, + selectors: { + video: { + allowVideoSharing: jest.fn(state => ({ allowVideoSharing: state })), + videoSharingEnabledForCourse: jest.fn(state => ({ videoSharingEnabledForCourse: state })), + videoSharingLearnMoreLink: jest.fn(state => ({ videoSharingLearnMoreLink: state })), + }, + }, +})); + +describe('SocialShareWidget', () => { + const props = { + title: 'tiTLE', + intl: { formatMessage }, + videoSharingEnabledForCourse: false, + allowVideoSharing: { + level: 'block', + value: false, + }, + videoSharingLearnMoreLink: 'sOMeURl.cOM', + updateField: jest.fn().mockName('args.updateField'), + }; + + describe('rendered with with videoSharingEnabled false', () => { + it('should return null', () => { + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); + }); + }); + + describe('rendered with videoSharingEnabled true', () => { + describe('and allowVideoSharing value equals true', () => { + describe(' with level equal to course', () => { + const wrapper = shallow(); + it('should have setting location message', () => { + const settingLocationDisclaimer = wrapper.find('FormattedMessage').at(2).prop('defaultMessage'); + expect(settingLocationDisclaimer).toEqual(messages.disclaimerSettingLocation.defaultMessage); + }); + it('should have checkbox disabled prop equal true', () => { + const disabledCheckbox = wrapper.children().at(1).prop('disabled'); + expect(disabledCheckbox).toEqual(true); + }); + }); + describe(' with level equal to block', () => { + const wrapper = shallow(); + it('should not have setting location message', () => { + const formattedMessages = wrapper.find('FormattedMessage'); + expect(formattedMessages.length).toEqual(2); + expect(formattedMessages.at(0)).not.toEqual(messages.disclaimerSettingLocation.defaultMessage); + expect(formattedMessages.at(1)).not.toEqual(messages.disclaimerSettingLocation.defaultMessage); + }); + it('should have checkbox disabled prop equal false', () => { + const disabledCheckbox = wrapper.children().at(1).prop('disabled'); + expect(disabledCheckbox).toEqual(false); + }); + }); + it('should have subtitle with text that reads Enabled', () => { + const wrapper = shallow(); + const subtitle = wrapper.prop('subtitle'); + expect(wrapper).toMatchSnapshot(); + expect(subtitle).toEqual('Enabled'); + }); + }); + describe('and allowVideoSharing value equals false', () => { + describe(' with level equal to course', () => { + const wrapper = shallow(); + it('should have setting location message', () => { + const settingLocationDisclaimer = wrapper.find('FormattedMessage').at(2).prop('defaultMessage'); + expect(settingLocationDisclaimer).toEqual(messages.disclaimerSettingLocation.defaultMessage); + }); + it('should have checkbox disabled prop equal true', () => { + const disabledCheckbox = wrapper.children().at(1).prop('disabled'); + expect(disabledCheckbox).toEqual(true); + }); + }); + describe(' with level equal to block', () => { + const wrapper = shallow(); + it('should not have setting location message', () => { + const formattedMessages = wrapper.find('FormattedMessage'); + expect(formattedMessages.length).toEqual(2); + expect(formattedMessages.at(0)).not.toEqual(messages.disclaimerSettingLocation.defaultMessage); + expect(formattedMessages.at(1)).not.toEqual(messages.disclaimerSettingLocation.defaultMessage); + }); + it('should have checkbox disabled prop equal false', () => { + const disabledCheckbox = wrapper.children().at(1).prop('disabled'); + expect(disabledCheckbox).toEqual(false); + }); + }); + it('should have subtitle with text that reads Enabled', () => { + const wrapper = shallow(); + const subtitle = wrapper.prop('subtitle'); + expect(wrapper).toMatchSnapshot(); + expect(subtitle).toEqual('Disabled'); + }); + }); + }); + describe('mapStateToProps', () => { + const testState = { A: 'pple', B: 'anana', C: 'ucumber' }; + test('allowVideoSharing from video.allowVideoSharing', () => { + expect( + mapStateToProps(testState).allowVideoSharing, + ).toEqual(selectors.video.allowVideoSharing(testState)); + }); + test('videoSharingEnabledForCourse from video.videoSharingEnabledForCourse', () => { + expect( + mapStateToProps(testState).videoSharingEnabledForCourse, + ).toEqual(selectors.video.videoSharingEnabledForCourse(testState)); + }); + test('videoSharingLearnMoreLink from video.videoSharingLearnMoreLink', () => { + expect( + mapStateToProps(testState).videoSharingLearnMoreLink, + ).toEqual(selectors.video.videoSharingLearnMoreLink(testState)); + }); + }); + describe('mapDispatchToProps', () => { + const dispatch = jest.fn(); + test('updateField from actions.video.updateField', () => { + expect(mapDispatchToProps.updateField).toEqual(dispatch(actions.video.updateField)); + }); + }); +}); diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/SocialShareWidget/messages.js b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/SocialShareWidget/messages.js new file mode 100644 index 000000000..7b47f4312 --- /dev/null +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/SocialShareWidget/messages.js @@ -0,0 +1,46 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + title: { + id: 'authoring.videoeditor.socialShare.title', + defaultMessage: 'Social Sharing', + description: 'Title for socialShare widget', + }, + disabledSubtitle: { + id: 'authoring.videoeditor.socialShare.disabled.subtitle', + defaultMessage: 'Disabled', + description: 'Subtitle for unavailable socialShare widget', + }, + enabledSubtitle: { + id: 'authoring.videoeditor.socialShare.enabled.subtitle', + defaultMessage: 'Enabled', + description: 'Subtitle for when thumbnail has been uploaded to the widget', + }, + learnMoreLinkLabel: { + id: 'authoring.videoeditor.socialShare.learnMore.link', + defaultMessage: 'Learn more about social sharing', + description: 'Text for link to learn more about social sharing', + }, + socialSharingDescription: { + id: 'authoring.videoeditor.socialShare.description', + defaultMessage: 'Allow this video to be shareable to social media', + description: 'Description for sociail sharing setting', + }, + socialSharingCheckboxLabel: { + id: 'authoring.videoeditor.socialShare.checkbox.label', + defaultMessage: 'This video is shareable to social media', + description: 'Label for checkbox for allowing video to be share', + }, + overrideSocialSharingNote: { + id: 'authoring.videoeditor.socialShare.overrideNote', + defaultMessage: 'Note: This setting is overridden by the course outline page.', + description: 'Message that the setting can be overriden in the course outline', + }, + disclaimerSettingLocation: { + id: 'authoring.videoeditor.socialShare.settingsDisclaimer', + defaultMessage: 'Change this setting on the course outline page.', + description: 'Message for disabled checkbox that notifies user that setting can be modified in course outline', + }, +}); + +export default messages; diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/__snapshots__/index.test.jsx.snap b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/__snapshots__/index.test.jsx.snap index 94de9e80f..b5129af5b 100644 --- a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/__snapshots__/index.test.jsx.snap +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/__snapshots__/index.test.jsx.snap @@ -287,53 +287,6 @@ exports[`VideoSourceWidget snapshots snapshots: renders as expected with videoSh - - -
- -
-
- - - - } - placement="top" - > - - - -
{ const dispatch = useDispatch(); const { @@ -41,7 +37,6 @@ export const VideoSourceWidget = ({ videoSource: source, fallbackVideos, allowVideoDownloads: allowDownload, - allowVideoSharing: allowSharing, } = widgetHooks.widgetValues({ dispatch, fields: { @@ -49,7 +44,6 @@ export const VideoSourceWidget = ({ [widgetHooks.selectorKeys.videoId]: widgetHooks.genericWidget, [widgetHooks.selectorKeys.fallbackVideos]: widgetHooks.arrayWidget, [widgetHooks.selectorKeys.allowVideoDownloads]: widgetHooks.genericWidget, - [widgetHooks.selectorKeys.allowVideoSharing]: widgetHooks.genericWidget, }, }); const { videoIdChangeAlert } = hooks.videoIdChangeAlert(); @@ -144,31 +138,6 @@ export const VideoSourceWidget = ({ - {videoSharingEnabledForCourse && ( - - -
- -
-
- - - - )} - > - - - -
- )}