diff --git a/src/actions/action-creators.test.js b/src/actions/action-creators.test.js index bb460cf..dd752b5 100644 --- a/src/actions/action-creators.test.js +++ b/src/actions/action-creators.test.js @@ -3,10 +3,58 @@ import * as types from '../constants/action-types'; import thunkMiddleware from 'redux-thunk'; import configureMockStore from 'redux-mock-store'; import panes from '../constants/pane-types'; +import { initializeCalculator } from '../lib/calculator'; const middlewares = [thunkMiddleware]; const mockStore = configureMockStore(middlewares); +const initialState = { + images: { + frames: {}, + frameIDs: [], + gifProgress: 0, + gifData: '', + caption: '', + fontColor: '#000000', + gifFileName: '' + }, + settings: { + image: { + width: 300, + height: 300, + interval: 100, + oversample: false + }, + bounds: { + left: -10, + right: 10, + bottom: -10, + top: 10 + }, + strategy: 'contain' + }, + ui: { + expandedPane: panes.NONE, + previewIdx: 0, + playing: false, + error: '' + } +}; + +const desmosMock = { + GraphingCalculator: jest.fn(() => { + return { + asyncScreenshot: (opts, cb) => cb(''), + getExpressions: () => [{ id: 1, latex: 'x = 3' }, { id: 2, latex: '' }], + setExpression: () => null + }; + }) +}; + +const calcContainerMock = { current: undefined }; + +// Tests + describe('Action creators', () => { describe('synchronous action creators', () => { it('return an action to add a frame', () => { @@ -127,6 +175,88 @@ describe('Action creators', () => { }); describe('async action creators', () => { - // TODO + let store; + + beforeEach(() => { + store = mockStore(initialState); + }); + + it('return an action to temporarily display error message', () => { + jest.useFakeTimers(); + const expectedActions = [ + { type: types.SET_ERROR, payload: { message: 'error' } }, + { type: types.CLEAR_ERROR } + ]; + store.dispatch(actions.flashError('error')); + jest.runAllTimers(); + expect(store.getActions()).toEqual(expectedActions); + }); + + it('return an action to request a frame from the calculator', () => { + const expectedActions = [ + { + type: types.ADD_FRAME, + payload: { id: 2, imageData: expect.any(String) } + } + ]; + const opts = { + height: 300, + targetPixelRatio: 1, + width: 300 + }; + initializeCalculator(desmosMock, calcContainerMock); + return store.dispatch(actions.requestFrame(opts)).then(() => { + expect(store.getActions()).toEqual(expectedActions); + }); + }); + + it('return an action to request a burst of frames from the calculator', () => { + const opts = { + height: 300, + idx: 1, + max: 3, + min: -3, + oversample: false, + step: 1, + width: 300 + }; + initializeCalculator(desmosMock, calcContainerMock); + return store.dispatch(actions.requestBurst(opts)).then(() => { + // slide with min/max of -3/3 should dispatch addFrame 7 times + expect(store.getActions().length).toEqual(7); + }); + }); + + it('return actions to play animation via frames in state', () => { + const expectedActions = [ + { type: types.PLAY_PREVIEW }, + { type: types.UPDATE_PREVIEW_IDX, payload: { idx: 1 } } + ]; + store.dispatch(actions.startAnimation()); + expect(store.getActions()).toEqual(expectedActions); + }); + + it('return action to generate a GIF via frames in state', () => { + const expectedActions = [ + { type: types.UPDATE_GIF_PROGRESS, payload: { progress: 100 } }, + { type: types.ADD_GIF, payload: { imageData: expect.any(String) } } + ]; + const opts = { + gifHeight: 300, + gifWidth: 300, + images: ['img1', 'img2', 'img3'], + interval: 0.1, + progressCallback: jest.fn() + }; + // gifshot mock + const gifshot = { + createGIF: (args, cb) => { + args.progressCallback(100); + return cb({ image: 'test' }); + } + }; + store.dispatch(actions.generateGIF([], opts, gifshot)); + expect(store.getActions()).toEqual(expectedActions); + }); }); }); diff --git a/src/actions/index.js b/src/actions/index.js index e300546..ca6e557 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -231,7 +231,10 @@ export const startAnimation = () => (dispatch, getState) => { // The gifshot library is loaded in index.html const gifshot = window.gifshot; -export const generateGIF = (images, opts) => (dispatch, getState) => { +export const generateGIF = (images, opts, gifMaker = gifshot) => ( + dispatch, + getState +) => { // Have to check state interval and not opts because opts is in seconds const { interval } = getState().settings.image; const settingsErrors = getSettingsErrors({ interval }); @@ -245,7 +248,7 @@ export const generateGIF = (images, opts) => (dispatch, getState) => { ...opts, progressCallback: progress => dispatch(updateGIFProgress(progress)) }; - gifshot.createGIF(gifshotArgs, data => { + gifMaker.createGIF(gifshotArgs, data => { if (data.error) { dispatch(flashError(gifCreationProblem())); } else { diff --git a/src/components/App.js b/src/components/App.js index 6706115..df8c720 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -7,12 +7,12 @@ import SettingsContainer from '../containers/SettingsContainer'; import FolderContainer from '../containers/FolderContainer'; import ErrorToastContainer from '../containers/ErrorToastContainer'; import CALCULATOR_OPTIONS from '../constants/calculator-options'; +import { initializeCalculator } from '../lib/calculator'; import './App.css'; // The Desmos API is loaded in index.html const Desmos = window.Desmos; const calcContainer = React.createRef(); -export let calculator; class App extends Component { constructor(props) { @@ -26,11 +26,7 @@ class App extends Component { } componentDidMount() { - calculator = Desmos.GraphingCalculator( - calcContainer.current, - CALCULATOR_OPTIONS - ); - + initializeCalculator(Desmos, calcContainer, CALCULATOR_OPTIONS); window.addEventListener('keydown', this.handleKeyDown); } diff --git a/src/lib/calc-helpers.js b/src/lib/calc-helpers.js index 598ce17..42957f9 100644 --- a/src/lib/calc-helpers.js +++ b/src/lib/calc-helpers.js @@ -6,7 +6,7 @@ * and concise. */ -import { calculator } from '../components/App'; +import { calculator } from './calculator'; import { noSuchExpression, notASlider } from './error-messages'; import { saveGraphToLocal, getGraphFromLocal } from './local-storage-helpers'; diff --git a/src/lib/calculator.js b/src/lib/calculator.js new file mode 100644 index 0000000..9be4f4b --- /dev/null +++ b/src/lib/calculator.js @@ -0,0 +1,9 @@ +let calculator; + +function initializeCalculator(desmos, calcContainer, calcOptions) { + calculator = desmos.GraphingCalculator(calcContainer.current, calcOptions); + window.calculator = calculator; + return calculator; +} + +export { calculator, initializeCalculator }; diff --git a/src/reducers/images.test.js b/src/reducers/images.test.js index 0ca83b8..873a888 100644 --- a/src/reducers/images.test.js +++ b/src/reducers/images.test.js @@ -13,7 +13,10 @@ const initialState = { frames: {}, frameIDs: [], gifProgress: 0, - gifData: '' + gifData: '', + caption: '', + fontColor: '#000000', + gifFileName: '' }; describe('reducers', () => { diff --git a/src/setupFiles.js b/src/setupFiles.js new file mode 100644 index 0000000..e69de29