const React = require('react'); const {shallowWithIntl} = require('../../helpers/intl-helpers.jsx'); import {mountWithIntl} from '../../helpers/intl-helpers.jsx'; const ComposeComment = require('../../../src/views/preview/comment/compose-comment.jsx'); import configureStore from 'redux-mock-store'; describe('Compose Comment test', () => { const mockStore = configureStore(); let _mockFormat; const defaultProps = () =>({ user: { thumbnailUrl: 'scratch.mit.edu', username: 'auser' } }); let defaultStore; beforeEach(() => { const mockFormat = { format: jest.fn() }; _mockFormat = Intl.RelativeTimeFormat = jest .fn() .mockImplementation(() => mockFormat); mockFormat.format.mockReturnValue(''); defaultStore = mockStore({ session: { session: { user: {}, permissions: { mute_status: {} } } } }); }); const getComposeCommentWrapper = (props, store) => { if (!store) { store = defaultStore; } const wrapper = shallowWithIntl( , {context: {store}} ); return wrapper; }; test('status is EDITING when props do not contain a muteStatus ', () => { const commentInstance = getComposeCommentWrapper({}).instance(); expect(commentInstance.state.status).toBe('EDITING'); }); test('status is COMPOSE_DISALLOWED when props contain a future mute', () => { jest.useFakeTimers(); const realDateNow = Date.now.bind(global.Date); global.Date.now = () => 0; const mutedStore = mockStore({ session: { session: { user: {}, permissions: { mute_status: { muteExpiresAt: 5, offenses: [], showWarning: true } } } } }); const component = getComposeCommentWrapper({}, mutedStore); const commentInstance = component.instance(); expect(commentInstance.state.status).toBe('COMPOSE_DISALLOWED'); global.Date.now = realDateNow; }); test('Modal & Comment status do not show ', () => { const component = getComposeCommentWrapper({}); // Comment compsoe box is there expect(component.find('FlexRow.compose-comment').exists()).toEqual(true); // No error message expect(component.find('FlexRow.compose-error-row').exists()).toEqual(false); expect(component.find('MuteModal').exists()).toEqual(false); expect(component.find('CommentingStatus').exists()).toEqual(false); // Buttons start enabled expect(component.find('Button.compose-post').props().disabled).toBe(false); expect(component.find('Button.compose-cancel').props().disabled).toBe(false); }); test('Error messages shows when comment rejected ', () => { const component = getComposeCommentWrapper({}); const commentInstance = component.instance(); commentInstance.setState({ error: 'isFlood', status: 'REJECTED' }); component.update(); expect(component.find('FlexRow.compose-error-row').exists()).toEqual(true); // Buttons stay enabled when comment rejected for non-mute reasons expect(component.find('Button.compose-post').props().disabled).toBe(false); expect(component.find('Button.compose-cancel').props().disabled).toBe(false); }); test('No error message shows when comment rejected because user is already muted ', () => { const component = getComposeCommentWrapper({}); const commentInstance = component.instance(); commentInstance.setState({ error: 'isMuted', status: 'COMPOSE_DISALLOWED' }); component.update(); expect(component.find('FlexRow.compose-error-row').exists()).toEqual(false); }); test('Comment Status shows but compose box does not when you load the page and you are already muted', () => { const realDateNow = Date.now.bind(global.Date); global.Date.now = () => 0; const component = getComposeCommentWrapper({}); const commentInstance = component.instance(); commentInstance.setState({muteExpiresAtMs: 100, status: 'COMPOSE_DISALLOWED'}); component.update(); // Compose box should be hidden if muted unless they got muted due to a comment they just posted. expect(component.find('FlexRow.compose-comment').exists()).toEqual(false); expect(component.find('MuteModal').exists()).toEqual(false); expect(component.find('CommentingStatus').exists()).toEqual(true); global.Date.now = realDateNow; }); test('Comment Status and compose box do not show on replies when muted, but mute modal does', () => { const realDateNow = Date.now.bind(global.Date); global.Date.now = () => 0; const store = mockStore({ session: { session: { user: {}, permissions: { mute_status: { muteExpiresAt: 5, offenses: [], showWarning: true } } } } }); const component = mountWithIntl( , {context: {store}} ); expect(component.find('FlexRow.compose-comment').exists()).toEqual(false); expect(component.find('MuteModal').exists()).toBe(true); expect(component.find('MuteModal').props().startStep).toBe(1); expect(component.find('CommentingStatus').exists()).toEqual(false); global.Date.now = realDateNow; }); test('Comment Status and compose box show on replies when not muted', () => { const realDateNow = Date.now.bind(global.Date); global.Date.now = () => 0; const component = getComposeCommentWrapper({isReply: true}); expect(component.find('FlexRow.compose-comment').exists()).toEqual(true); expect(component.find('CommentingStatus').exists()).toEqual(false); global.Date.now = realDateNow; }); test('Comment Status initialized properly when muted', () => { jest.useFakeTimers(); const realDateNow = Date.now.bind(global.Date); global.Date.now = () => 0; const mutedStore = mockStore({ session: { session: { user: {}, permissions: { mute_status: { muteExpiresAt: 5, offenses: [], showWarning: true } } } } }); const component = getComposeCommentWrapper({}, mutedStore); const commentInstance = component.instance(); // Check conversion to ms from seconds is done at init time. expect(commentInstance.state.muteExpiresAtMs).toEqual(5 * 1000); // Check we setup a timeout to expire the widget when timeout reached. expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 5 * 1000); expect(commentInstance.state.showWarning).toBe(true); // Compose box should be hidden if muted unless they got muted due to a comment they just posted. expect(component.find('FlexRow.compose-comment').exists()).toEqual(false); expect(component.find('MuteModal').exists()).toEqual(false); expect(component.find('CommentingStatus').exists()).toEqual(true); global.Date.now = realDateNow; }); test('Comment Status shows when user just submitted a reply comment that got them muted', () => { const realDateNow = Date.now.bind(global.Date); global.Date.now = () => 0; const component = getComposeCommentWrapper({isReply: true}); const commentInstance = component.instance(); commentInstance.setState({ status: 'REJECTED_MUTE', muteExpiresAtMs: 100 }); component.update(); expect(component.find('FlexRow.compose-comment').exists()).toEqual(true); expect(component.find('MuteModal').exists()).toEqual(false); expect(component.find('CommentingStatus').exists()).toEqual(true); // Compose box exists but is disabled expect(component.find('InplaceInput.compose-input').exists()).toEqual(true); expect(component.find('InplaceInput.compose-input').props().disabled).toBe(true); expect(component.find('Button.compose-post').props().disabled).toBe(true); expect(component.find('Button.compose-cancel').props().disabled).toBe(true); global.Date.now = realDateNow; }); test('Comment Status shows when user just submitted a comment that got them muted', () => { const realDateNow = Date.now.bind(global.Date); global.Date.now = () => 0; const component = getComposeCommentWrapper({}); const commentInstance = component.instance(); commentInstance.setState({ status: 'REJECTED_MUTE', muteExpiresAtMs: 100 }); component.update(); expect(component.find('FlexRow.compose-comment').exists()).toEqual(true); expect(component.find('MuteModal').exists()).toEqual(false); expect(component.find('CommentingStatus').exists()).toEqual(true); // Compose box exists but is disabled expect(component.find('InplaceInput.compose-input').exists()).toEqual(true); expect(component.find('InplaceInput.compose-input').props().disabled).toBe(true); expect(component.find('Button.compose-post').props().disabled).toBe(true); expect(component.find('Button.compose-cancel').props().disabled).toBe(true); global.Date.now = realDateNow; }); test('Comment Error does not show for mutes', () => { const realDateNow = Date.now.bind(global.Date); global.Date.now = () => 0; const component = getComposeCommentWrapper({}); const commentInstance = component.instance(); commentInstance.setState({ status: 'REJECTED_MUTE', error: 'a mute error' }); component.update(); expect(component.find('FlexRow.compose-error-row').exists()).toEqual(false); expect(component.find('FlexRow.compose-comment').exists()).toEqual(true); global.Date.now = realDateNow; }); test('Comment Error does show for non-mute errors', () => { const component = getComposeCommentWrapper({}); const commentInstance = component.instance(); commentInstance.setState({ error: 'some error', status: 'REJECTED' }); component.update(); expect(component.find('FlexRow.compose-error-row').exists()).toEqual(true); expect(component.find('FlexRow.compose-comment').exists()).toEqual(true); expect(component.find('InplaceInput.compose-input').exists()).toEqual(true); expect(component.find('InplaceInput.compose-input').props().disabled).toBe(false); expect(component.find('Button.compose-post').props().disabled).toBe(false); expect(component.find('Button.compose-cancel').props().disabled).toBe(false); }); test('Mute Modal shows when muteOpen is true ', () => { const realDateNow = Date.now.bind(global.Date); global.Date.now = () => 0; const store = mockStore({ session: { session: { user: {}, permissions: { mute_status: {} } } } }); const component = mountWithIntl( , {context: {store}} ); // set state on the ComposeComment component, not the wrapper const commentInstance = component.find('ComposeComment').instance(); commentInstance.setState({muteOpen: true}); component.update(); expect(component.find('MuteModal').exists()).toEqual(true); expect(component.find('MuteModal').props().startStep).toEqual(0); expect(component.find('MuteModal').props().showWarning).toBe(false); global.Date.now = realDateNow; }); test('Mute Modal gets showWarning props from state', () => { const store = mockStore({ session: { session: { user: {}, permissions: { mute_status: {} } } } }); const component = mountWithIntl( , {context: {store}} ); // set state on the ComposeComment component, not the wrapper const commentInstance = component.find('ComposeComment').instance(); commentInstance.setState({muteOpen: true}); component.update(); expect(component.find('MuteModal').exists()).toEqual(true); expect(component.find('MuteModal').props().showWarning).toBe(false); commentInstance.setState({ muteOpen: true, showWarning: true }); component.update(); expect(component.find('MuteModal').props().showWarning).toBe(true); }); test('Mute Modal gets showFeedback props from state', () => { const store = mockStore({ session: { session: { user: {}, permissions: { mute_status: {} } } } }); const component = mountWithIntl( , {context: {store}} ); const commentInstance = component.find('ComposeComment').instance(); commentInstance.setState({ status: 'REJECTED_MUTE', error: 'isBad', muteOpen: true }); component.update(); expect(component.find('MuteModal').exists()).toEqual(true); expect(component.find('MuteModal').props().showFeedback).toBe(true); commentInstance.setState({ status: 'COMPOSE_DISALLOWED', error: 'isMute', showWarning: true, muteOpen: true }); component.update(); expect(component.find('MuteModal').exists()).toEqual(true); expect(component.find('MuteModal').props().showFeedback).toBe(false); commentInstance.setState({ status: 'REJECTED', error: 'isBad', showWarning: true, muteOpen: true }); component.update(); expect(component.find('MuteModal').exists()).toEqual(true); expect(component.find('MuteModal').props().showFeedback).toBe(false); }); test('shouldShowMuteModal is false when muteStatus is undefined ', () => { const commentInstance = getComposeCommentWrapper({}).instance(); expect(commentInstance.shouldShowMuteModal()).toBe(false); }); test('shouldShowMuteModal is false when list is undefined ', () => { const muteStatus = {}; const commentInstance = getComposeCommentWrapper({}).instance(); expect(commentInstance.shouldShowMuteModal(muteStatus)).toBe(false); }); test('shouldShowMuteModal is false when list empty ', () => { const muteStatus = { offenses: [] }; const commentInstance = getComposeCommentWrapper({}).instance(); expect(commentInstance.shouldShowMuteModal(muteStatus)).toBe(false); }); test('shouldShowMuteModal is true when only 1 recent offesnse ', () => { const realDateNow = Date.now.bind(global.Date); global.Date.now = () => 0; // Since Date.now mocked to 0 above, we just need a small number to make // it look like it was created < 2 minutes ago. const offense = { expiresAt: '1000', createdAt: '-60' // ~1 ago min given shouldShowMuteModal's conversions, }; const muteStatus = { offenses: [offense] }; const commentInstance = getComposeCommentWrapper({}).instance(); expect(commentInstance.shouldShowMuteModal(muteStatus, true)).toBe(true); global.Date.now = realDateNow; }); test('shouldShowMuteModal is false when multiple offenses, even if 1 is recent ', () => { const offenses = []; const realDateNow = Date.now.bind(global.Date); global.Date.now = () => 0; // Since Date.now mocked to 0 above, we just need a small number to make // it look like it was created more than 2 minutes ago. let offense = { expiresAt: '1000', createdAt: '-119' // just shy of two min ago }; offenses.push(offense); offense.createdAt = '-180'; // 3 minutes ago; offenses.push(offense); const muteStatus = { offenses: offenses }; const commentInstance = getComposeCommentWrapper({}).instance(); expect(commentInstance.shouldShowMuteModal(muteStatus, true)).toBe(false); global.Date.now = realDateNow; }); test('shouldShowMuteModal is true when showWarning is true even with multiple offenses', () => { const offenses = []; const realDateNow = Date.now.bind(global.Date); global.Date.now = () => 0; // Since Date.now mocked to 0 above, we just need a small number to make // it look like it was created more than 2 minutes ago. let offense = { expiresAt: '1000', createdAt: '-119' // just shy of two min ago }; offenses.push(offense); offense.createdAt = '-180'; // 3 minutes ago; offenses.push(offense); const muteStatus = { offenses: offenses, showWarning: true }; const commentInstance = getComposeCommentWrapper({}).instance(); expect(commentInstance.shouldShowMuteModal(muteStatus, true)).toBe(true); global.Date.now = realDateNow; }); test('shouldShowMuteModal is false when the user is already muted, even when only 1 recent offesnse ', () => { const realDateNow = Date.now.bind(global.Date); global.Date.now = () => 0; // Since Date.now mocked to 0 above, we just need a small number to make // it look like it was created < 2 minutes ago. const offense = { expiresAt: '1000', createdAt: '-60' // ~1 ago min given shouldShowMuteModal's conversions, }; const muteStatus = { offenses: [offense] }; const justMuted = false; const commentInstance = getComposeCommentWrapper({}).instance(); expect(commentInstance.shouldShowMuteModal(muteStatus, justMuted)).toBe(false); global.Date.now = realDateNow; }); test('shouldShowMuteModal is true when the user is already muted if the comment is a reply', () => { const realDateNow = Date.now.bind(global.Date); global.Date.now = () => 0; // Since Date.now mocked to 0 above, we just need a small number to make // it look like it was created < 2 minutes ago. const offense = { expiresAt: '1000', createdAt: '-60' // ~1 ago min given shouldShowMuteModal's conversions, }; const muteStatus = { offenses: [offense] }; const justMuted = false; const commentInstance = getComposeCommentWrapper({isReply: true}).instance(); expect(commentInstance.shouldShowMuteModal(muteStatus, justMuted)).toBe(true); global.Date.now = realDateNow; }); test('getMuteModalStartStep: not a reply', () => { const commentInstance = getComposeCommentWrapper({}).instance(); expect(commentInstance.getMuteModalStartStep()).toBe(0); }); test('getMuteModalStartStep: A reply that got them muted ', () => { const commentInstance = getComposeCommentWrapper({isReply: true}).instance(); commentInstance.setState({ status: 'REJECTED_MUTE' }); expect(commentInstance.getMuteModalStartStep()).toBe(0); }); test('getMuteModalStartStep: A reply click when already muted ', () => { const commentInstance = getComposeCommentWrapper({isReply: true}).instance(); commentInstance.setState({ status: 'COMPOSE_DISALLOWED' }); expect(commentInstance.getMuteModalStartStep()).toBe(1); }); test('isMuted: expiration is in the future ', () => { const realDateNow = Date.now.bind(global.Date); global.Date.now = () => 0; // Set "now" to 0 for easier testing. const commentInstance = getComposeCommentWrapper({}).instance(); commentInstance.setState({muteExpiresAtMs: 100}); expect(commentInstance.isMuted()).toBe(true); global.Date.now = realDateNow; }); test('isMuted: expiration is in the past ', () => { const realDateNow = Date.now.bind(global.Date); global.Date.now = () => 0; const commentInstance = getComposeCommentWrapper({}).instance(); commentInstance.setState({muteExpiresAtMs: -100}); expect(commentInstance.isMuted()).toBe(false); global.Date.now = realDateNow; }); test('isMuted: expiration is not set ', () => { const realDateNow = Date.now.bind(global.Date); global.Date.now = () => 0; const commentInstance = getComposeCommentWrapper({}).instance(); expect(commentInstance.isMuted()).toBe(false); global.Date.now = realDateNow; }); test('getMuteMessageInfo: muteType set and just got muted', () => { const justMuted = true; const commentInstance = getComposeCommentWrapper({}).instance(); commentInstance.setState({muteType: 'unconstructive'}); expect(commentInstance.getMuteMessageInfo(justMuted).commentType).toBe('comment.type.unconstructive'); expect(commentInstance.getMuteMessageInfo(justMuted) .muteStepContent[0]).toBe('comment.unconstructive.content1'); }); test('getMuteMessageInfo: muteType set and already muted', () => { const justMuted = false; const commentInstance = getComposeCommentWrapper({}).instance(); commentInstance.setState({muteType: 'pii'}); expect(commentInstance.getMuteMessageInfo(justMuted).commentType).toBe('comment.type.pii.past'); // PII has the same content1 regardless of whether you were just muted expect(commentInstance.getMuteMessageInfo(justMuted).muteStepContent[0]).toBe('comment.pii.content1'); commentInstance.setState({muteType: 'vulgarity'}); expect(commentInstance.getMuteMessageInfo(justMuted).commentType).toBe('comment.type.vulgarity.past'); expect(commentInstance.getMuteMessageInfo(justMuted).muteStepContent[0]).toBe('comment.type.vulgarity.past'); }); test('getMuteMessageInfo: muteType not set and just got muted', () => { const justMuted = true; const commentInstance = getComposeCommentWrapper({}).instance(); expect(commentInstance.getMuteMessageInfo(justMuted).commentType).toBe('comment.type.general'); // general has the same content1 regardless of whether you were just muted expect(commentInstance.getMuteMessageInfo(justMuted).muteStepContent[0]).toBe('comment.general.content1'); }); test('getMuteMessageInfo: muteType not set and already muted', () => { const justMuted = false; const commentInstance = getComposeCommentWrapper({}).instance(); expect(commentInstance.getMuteMessageInfo(justMuted).commentType).toBe('comment.type.general.past'); }); test('getMuteMessageInfo: muteType set to something we don\'t have messages for and just got muted', () => { const justMuted = true; const commentInstance = getComposeCommentWrapper({}).instance(); commentInstance.setState({muteType: 'spaghetti'}); expect(commentInstance.getMuteMessageInfo(justMuted).commentType).toBe('comment.type.general'); }); test('getMuteMessageInfo: muteType set to something we don\'t have messages for and already muted', () => { const justMuted = false; const commentInstance = getComposeCommentWrapper({}).instance(); commentInstance.setState({muteType: 'spaghetti'}); expect(commentInstance.getMuteMessageInfo(justMuted).commentType).toBe('comment.type.general.past'); }); });