sound-editor.test.jsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. import React from 'react';
  2. import {mountWithIntl} from '../../helpers/intl-helpers.jsx';
  3. import configureStore from 'redux-mock-store';
  4. import mockAudioBufferPlayer from '../../__mocks__/audio-buffer-player.js';
  5. import mockAudioEffects from '../../__mocks__/audio-effects.js';
  6. import SoundEditor from '../../../src/containers/sound-editor';
  7. import SoundEditorComponent from '../../../src/components/sound-editor/sound-editor';
  8. jest.mock('react-ga');
  9. jest.mock('../../../src/lib/audio/audio-buffer-player', () => mockAudioBufferPlayer);
  10. jest.mock('../../../src/lib/audio/audio-effects', () => mockAudioEffects);
  11. describe('Sound Editor Container', () => {
  12. const mockStore = configureStore();
  13. let store;
  14. let soundIndex;
  15. let soundBuffer;
  16. const samples = new Float32Array([0, 0, 0]); // eslint-disable-line no-undef
  17. let vm;
  18. beforeEach(() => {
  19. soundIndex = 0;
  20. soundBuffer = {
  21. sampleRate: 0,
  22. getChannelData: jest.fn(() => samples)
  23. };
  24. vm = {
  25. getSoundBuffer: jest.fn(() => soundBuffer),
  26. renameSound: jest.fn(),
  27. updateSoundBuffer: jest.fn(),
  28. editingTarget: {
  29. sprite: {
  30. sounds: [{name: 'first name', id: 'first id'}]
  31. }
  32. }
  33. };
  34. store = mockStore({scratchGui: {vm: vm}});
  35. });
  36. test('should pass the correct data to the component from the store', () => {
  37. const wrapper = mountWithIntl(
  38. <SoundEditor
  39. soundIndex={soundIndex}
  40. store={store}
  41. />
  42. );
  43. const componentProps = wrapper.find(SoundEditorComponent).props();
  44. // Data retreived and processed by the `connect` with the store
  45. expect(componentProps.name).toEqual('first name');
  46. expect(componentProps.chunkLevels).toEqual([0]);
  47. expect(mockAudioBufferPlayer.instance.samples).toEqual(samples);
  48. // Initial data
  49. expect(componentProps.playhead).toEqual(null);
  50. expect(componentProps.trimStart).toEqual(null);
  51. expect(componentProps.trimEnd).toEqual(null);
  52. });
  53. test('it plays when clicked and stops when clicked again', () => {
  54. const wrapper = mountWithIntl(
  55. <SoundEditor
  56. soundIndex={soundIndex}
  57. store={store}
  58. />
  59. );
  60. let component = wrapper.find(SoundEditorComponent);
  61. // Ensure rendering doesn't start playing any sounds
  62. expect(mockAudioBufferPlayer.instance.play.mock.calls).toEqual([]);
  63. expect(mockAudioBufferPlayer.instance.stop.mock.calls).toEqual([]);
  64. component.props().onPlay();
  65. expect(mockAudioBufferPlayer.instance.play).toHaveBeenCalled();
  66. // Mock the audio buffer player calling onUpdate
  67. mockAudioBufferPlayer.instance.onUpdate(0.5);
  68. wrapper.update();
  69. component = wrapper.find(SoundEditorComponent);
  70. expect(component.props().playhead).toEqual(0.5);
  71. component.props().onStop();
  72. wrapper.update();
  73. component = wrapper.find(SoundEditorComponent);
  74. expect(mockAudioBufferPlayer.instance.stop).toHaveBeenCalled();
  75. expect(component.props().playhead).toEqual(null);
  76. });
  77. test('it submits name changes to the vm', () => {
  78. const wrapper = mountWithIntl(
  79. <SoundEditor
  80. soundIndex={soundIndex}
  81. store={store}
  82. />
  83. );
  84. const component = wrapper.find(SoundEditorComponent);
  85. component.props().onChangeName('hello');
  86. expect(vm.renameSound).toHaveBeenCalledWith(soundIndex, 'hello');
  87. });
  88. test('it handles an effect by submitting the result and playing', async () => {
  89. const wrapper = mountWithIntl(
  90. <SoundEditor
  91. soundIndex={soundIndex}
  92. store={store}
  93. />
  94. );
  95. const component = wrapper.find(SoundEditorComponent);
  96. component.props().onReverse(); // Could be any of the effects, just testing the end result
  97. await mockAudioEffects.instance._finishProcessing(soundBuffer);
  98. expect(mockAudioBufferPlayer.instance.play).toHaveBeenCalled();
  99. expect(vm.updateSoundBuffer).toHaveBeenCalled();
  100. });
  101. test('it handles reverse effect correctly', () => {
  102. const wrapper = mountWithIntl(
  103. <SoundEditor
  104. soundIndex={soundIndex}
  105. store={store}
  106. />
  107. );
  108. const component = wrapper.find(SoundEditorComponent);
  109. component.props().onReverse();
  110. expect(mockAudioEffects.instance.name).toEqual(mockAudioEffects.effectTypes.REVERSE);
  111. expect(mockAudioEffects.instance.process).toHaveBeenCalled();
  112. });
  113. test('it handles louder effect correctly', () => {
  114. const wrapper = mountWithIntl(
  115. <SoundEditor
  116. soundIndex={soundIndex}
  117. store={store}
  118. />
  119. );
  120. const component = wrapper.find(SoundEditorComponent);
  121. component.props().onLouder();
  122. expect(mockAudioEffects.instance.name).toEqual(mockAudioEffects.effectTypes.LOUDER);
  123. expect(mockAudioEffects.instance.process).toHaveBeenCalled();
  124. });
  125. test('it handles softer effect correctly', () => {
  126. const wrapper = mountWithIntl(
  127. <SoundEditor
  128. soundIndex={soundIndex}
  129. store={store}
  130. />
  131. );
  132. const component = wrapper.find(SoundEditorComponent);
  133. component.props().onSofter();
  134. expect(mockAudioEffects.instance.name).toEqual(mockAudioEffects.effectTypes.SOFTER);
  135. expect(mockAudioEffects.instance.process).toHaveBeenCalled();
  136. });
  137. test('it handles faster effect correctly', () => {
  138. const wrapper = mountWithIntl(
  139. <SoundEditor
  140. soundIndex={soundIndex}
  141. store={store}
  142. />
  143. );
  144. const component = wrapper.find(SoundEditorComponent);
  145. component.props().onFaster();
  146. expect(mockAudioEffects.instance.name).toEqual(mockAudioEffects.effectTypes.FASTER);
  147. expect(mockAudioEffects.instance.process).toHaveBeenCalled();
  148. });
  149. test('it handles slower effect correctly', () => {
  150. const wrapper = mountWithIntl(
  151. <SoundEditor
  152. soundIndex={soundIndex}
  153. store={store}
  154. />
  155. );
  156. const component = wrapper.find(SoundEditorComponent);
  157. component.props().onSlower();
  158. expect(mockAudioEffects.instance.name).toEqual(mockAudioEffects.effectTypes.SLOWER);
  159. expect(mockAudioEffects.instance.process).toHaveBeenCalled();
  160. });
  161. test('it handles echo effect correctly', () => {
  162. const wrapper = mountWithIntl(
  163. <SoundEditor
  164. soundIndex={soundIndex}
  165. store={store}
  166. />
  167. );
  168. const component = wrapper.find(SoundEditorComponent);
  169. component.props().onEcho();
  170. expect(mockAudioEffects.instance.name).toEqual(mockAudioEffects.effectTypes.ECHO);
  171. expect(mockAudioEffects.instance.process).toHaveBeenCalled();
  172. });
  173. test('it handles robot effect correctly', () => {
  174. const wrapper = mountWithIntl(
  175. <SoundEditor
  176. soundIndex={soundIndex}
  177. store={store}
  178. />
  179. );
  180. const component = wrapper.find(SoundEditorComponent);
  181. component.props().onRobot();
  182. expect(mockAudioEffects.instance.name).toEqual(mockAudioEffects.effectTypes.ROBOT);
  183. expect(mockAudioEffects.instance.process).toHaveBeenCalled();
  184. });
  185. test('undo/redo stack state', async () => {
  186. const wrapper = mountWithIntl(
  187. <SoundEditor
  188. soundIndex={soundIndex}
  189. store={store}
  190. />
  191. );
  192. let component = wrapper.find(SoundEditorComponent);
  193. // Undo and redo should be disabled initially
  194. expect(component.prop('canUndo')).toEqual(false);
  195. expect(component.prop('canRedo')).toEqual(false);
  196. // Submitting new samples should make it possible to undo
  197. component.props().onFaster();
  198. await mockAudioEffects.instance._finishProcessing(soundBuffer);
  199. wrapper.update();
  200. component = wrapper.find(SoundEditorComponent);
  201. expect(component.prop('canUndo')).toEqual(true);
  202. expect(component.prop('canRedo')).toEqual(false);
  203. // Undoing should make it possible to redo and not possible to undo again
  204. await component.props().onUndo();
  205. wrapper.update();
  206. component = wrapper.find(SoundEditorComponent);
  207. expect(component.prop('canUndo')).toEqual(false);
  208. expect(component.prop('canRedo')).toEqual(true);
  209. // Redoing should make it possible to undo and not possible to redo again
  210. await component.props().onRedo();
  211. wrapper.update();
  212. component = wrapper.find(SoundEditorComponent);
  213. expect(component.prop('canUndo')).toEqual(true);
  214. expect(component.prop('canRedo')).toEqual(false);
  215. // New submission should clear the redo stack
  216. await component.props().onUndo(); // Undo to go back to a state where redo is enabled
  217. wrapper.update();
  218. component = wrapper.find(SoundEditorComponent);
  219. expect(component.prop('canRedo')).toEqual(true);
  220. component.props().onFaster();
  221. await mockAudioEffects.instance._finishProcessing(soundBuffer);
  222. wrapper.update();
  223. component = wrapper.find(SoundEditorComponent);
  224. expect(component.prop('canRedo')).toEqual(false);
  225. });
  226. test('undo and redo submit new samples and play the sound', async () => {
  227. const wrapper = mountWithIntl(
  228. <SoundEditor
  229. soundIndex={soundIndex}
  230. store={store}
  231. />
  232. );
  233. let component = wrapper.find(SoundEditorComponent);
  234. // Set up an undoable state
  235. component.props().onFaster();
  236. await mockAudioEffects.instance._finishProcessing(soundBuffer);
  237. wrapper.update();
  238. component = wrapper.find(SoundEditorComponent);
  239. // Undo should update the sound buffer and play the new samples
  240. await component.props().onUndo();
  241. expect(mockAudioBufferPlayer.instance.play).toHaveBeenCalled();
  242. expect(vm.updateSoundBuffer).toHaveBeenCalled();
  243. // Clear the mocks call history to assert again for redo.
  244. vm.updateSoundBuffer.mockClear();
  245. mockAudioBufferPlayer.instance.play.mockClear();
  246. // Undo should update the sound buffer and play the new samples
  247. await component.props().onRedo();
  248. expect(mockAudioBufferPlayer.instance.play).toHaveBeenCalled();
  249. expect(vm.updateSoundBuffer).toHaveBeenCalled();
  250. });
  251. });