project-saver-hoc.test.jsx 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. import 'web-audio-test-api';
  2. import React from 'react';
  3. import configureStore from 'redux-mock-store';
  4. import {mount} from 'enzyme';
  5. import {LoadingState} from '../../../src/reducers/project-state';
  6. import VM from 'scratch-vm';
  7. import projectSaverHOC from '../../../src/lib/project-saver-hoc.jsx';
  8. describe('projectSaverHOC', () => {
  9. const mockStore = configureStore();
  10. let store;
  11. let vm;
  12. beforeEach(() => {
  13. store = mockStore({
  14. scratchGui: {
  15. projectChanged: false,
  16. projectState: {},
  17. projectTitle: 'Scratch Project',
  18. timeout: {
  19. autoSaveTimeoutId: null
  20. }
  21. },
  22. locales: {
  23. locale: 'en'
  24. }
  25. });
  26. vm = new VM();
  27. jest.useFakeTimers();
  28. });
  29. test('if canSave becomes true when showing a project with an id, project will be saved', () => {
  30. const mockedUpdateProject = jest.fn();
  31. const Component = () => <div />;
  32. const WrappedComponent = projectSaverHOC(Component);
  33. const mounted = mount(
  34. <WrappedComponent
  35. isShowingWithId
  36. canSave={false}
  37. isCreatingNew={false}
  38. isShowingSaveable={false} // set explicitly because it relies on ownProps.canSave
  39. isShowingWithoutId={false}
  40. isUpdating={false}
  41. loadingState={LoadingState.SHOWING_WITH_ID}
  42. store={store}
  43. vm={vm}
  44. onAutoUpdateProject={mockedUpdateProject}
  45. />
  46. );
  47. mounted.setProps({
  48. canSave: true,
  49. isShowingSaveable: true
  50. });
  51. expect(mockedUpdateProject).toHaveBeenCalled();
  52. });
  53. test('if canSave is alreatdy true and we show a project with an id, project will NOT be saved', () => {
  54. const mockedSaveProject = jest.fn();
  55. const Component = () => <div />;
  56. const WrappedComponent = projectSaverHOC(Component);
  57. const mounted = mount(
  58. <WrappedComponent
  59. canSave
  60. isCreatingNew={false}
  61. isShowingWithId={false}
  62. isShowingWithoutId={false}
  63. isUpdating={false}
  64. loadingState={LoadingState.LOADING_VM_WITH_ID}
  65. store={store}
  66. vm={vm}
  67. onAutoUpdateProject={mockedSaveProject}
  68. />
  69. );
  70. mounted.setProps({
  71. canSave: true,
  72. isShowingWithId: true,
  73. loadingState: LoadingState.SHOWING_WITH_ID
  74. });
  75. expect(mockedSaveProject).not.toHaveBeenCalled();
  76. });
  77. test('if canSave is false when showing a project without an id, project will NOT be created', () => {
  78. const mockedCreateProject = jest.fn();
  79. const Component = () => <div />;
  80. const WrappedComponent = projectSaverHOC(Component);
  81. const mounted = mount(
  82. <WrappedComponent
  83. isShowingWithoutId
  84. canSave={false}
  85. isCreatingNew={false}
  86. isShowingWithId={false}
  87. isUpdating={false}
  88. loadingState={LoadingState.LOADING_VM_NEW_DEFAULT}
  89. store={store}
  90. vm={vm}
  91. onCreateProject={mockedCreateProject}
  92. />
  93. );
  94. mounted.setProps({
  95. isShowingWithoutId: true,
  96. loadingState: LoadingState.SHOWING_WITHOUT_ID
  97. });
  98. expect(mockedCreateProject).not.toHaveBeenCalled();
  99. });
  100. test('if canCreateNew becomes true when showing a project without an id, project will be created', () => {
  101. const mockedCreateProject = jest.fn();
  102. const Component = () => <div />;
  103. const WrappedComponent = projectSaverHOC(Component);
  104. const mounted = mount(
  105. <WrappedComponent
  106. isShowingWithoutId
  107. canCreateNew={false}
  108. isCreatingNew={false}
  109. isShowingWithId={false}
  110. isUpdating={false}
  111. loadingState={LoadingState.SHOWING_WITHOUT_ID}
  112. store={store}
  113. vm={vm}
  114. onCreateProject={mockedCreateProject}
  115. />
  116. );
  117. mounted.setProps({
  118. canCreateNew: true
  119. });
  120. expect(mockedCreateProject).toHaveBeenCalled();
  121. });
  122. test('if canCreateNew is true and we transition to showing new project, project will be created', () => {
  123. const mockedCreateProject = jest.fn();
  124. const Component = () => <div />;
  125. const WrappedComponent = projectSaverHOC(Component);
  126. const mounted = mount(
  127. <WrappedComponent
  128. canCreateNew
  129. isCreatingNew={false}
  130. isShowingWithId={false}
  131. isShowingWithoutId={false}
  132. isUpdating={false}
  133. loadingState={LoadingState.LOADING_VM_NEW_DEFAULT}
  134. store={store}
  135. vm={vm}
  136. onCreateProject={mockedCreateProject}
  137. />
  138. );
  139. mounted.setProps({
  140. isShowingWithoutId: true,
  141. loadingState: LoadingState.SHOWING_WITHOUT_ID
  142. });
  143. expect(mockedCreateProject).toHaveBeenCalled();
  144. });
  145. test('if we enter creating new state, vm project should be requested', () => {
  146. const Component = () => <div />;
  147. const WrappedComponent = projectSaverHOC(Component);
  148. const mockedStoreProject = jest.fn(() => Promise.resolve());
  149. // The first wrapper is redux's Connect HOC
  150. WrappedComponent.WrappedComponent.prototype.storeProject = mockedStoreProject;
  151. const mounted = mount(
  152. <WrappedComponent
  153. canSave
  154. isCreatingCopy={false}
  155. isCreatingNew={false}
  156. isRemixing={false}
  157. isShowingWithId={false}
  158. isShowingWithoutId={false}
  159. isUpdating={false}
  160. loadingState={LoadingState.LOADING_VM_NEW_DEFAULT}
  161. reduxProjectId={'100'}
  162. store={store}
  163. vm={vm}
  164. />
  165. );
  166. mounted.setProps({
  167. isCreatingNew: true,
  168. loadingState: LoadingState.CREATING_NEW
  169. });
  170. expect(mockedStoreProject).toHaveBeenCalled();
  171. });
  172. test('if we enter remixing state, vm project should be requested, and alert should show', () => {
  173. const mockedShowCreatingRemixAlert = jest.fn();
  174. const Component = () => <div />;
  175. const WrappedComponent = projectSaverHOC(Component);
  176. const mockedStoreProject = jest.fn(() => Promise.resolve());
  177. // The first wrapper is redux's Connect HOC
  178. WrappedComponent.WrappedComponent.prototype.storeProject = mockedStoreProject;
  179. const mounted = mount(
  180. <WrappedComponent
  181. canSave
  182. isCreatingCopy={false}
  183. isCreatingNew={false}
  184. isRemixing={false}
  185. isShowingWithId={false}
  186. isShowingWithoutId={false}
  187. isUpdating={false}
  188. loadingState={LoadingState.SHOWING_WITH_ID}
  189. reduxProjectId={'100'}
  190. store={store}
  191. vm={vm}
  192. onShowCreatingRemixAlert={mockedShowCreatingRemixAlert}
  193. />
  194. );
  195. mounted.setProps({
  196. isRemixing: true,
  197. loadingState: LoadingState.REMIXING
  198. });
  199. expect(mockedStoreProject).toHaveBeenCalled();
  200. expect(mockedShowCreatingRemixAlert).toHaveBeenCalled();
  201. });
  202. test('if we enter creating copy state, vm project should be requested, and alert should show', () => {
  203. const mockedShowCreatingCopyAlert = jest.fn();
  204. const Component = () => <div />;
  205. const WrappedComponent = projectSaverHOC(Component);
  206. const mockedStoreProject = jest.fn(() => Promise.resolve());
  207. // The first wrapper is redux's Connect HOC
  208. WrappedComponent.WrappedComponent.prototype.storeProject = mockedStoreProject;
  209. const mounted = mount(
  210. <WrappedComponent
  211. canSave
  212. isCreatingCopy={false}
  213. isCreatingNew={false}
  214. isRemixing={false}
  215. isShowingWithId={false}
  216. isShowingWithoutId={false}
  217. isUpdating={false}
  218. loadingState={LoadingState.SHOWING_WITH_ID}
  219. reduxProjectId={'100'}
  220. store={store}
  221. vm={vm}
  222. onShowCreatingCopyAlert={mockedShowCreatingCopyAlert}
  223. />
  224. );
  225. mounted.setProps({
  226. isCreatingCopy: true,
  227. loadingState: LoadingState.CREATING_COPY
  228. });
  229. expect(mockedStoreProject).toHaveBeenCalled();
  230. expect(mockedShowCreatingCopyAlert).toHaveBeenCalled();
  231. });
  232. test('if we enter updating/saving state, vm project should be requested', () => {
  233. const Component = () => <div />;
  234. const WrappedComponent = projectSaverHOC(Component);
  235. const mockedStoreProject = jest.fn(() => Promise.resolve());
  236. // The first wrapper is redux's Connect HOC
  237. WrappedComponent.WrappedComponent.prototype.storeProject = mockedStoreProject;
  238. const mounted = mount(
  239. <WrappedComponent
  240. canSave
  241. isCreatingNew={false}
  242. isShowingWithId={false}
  243. isShowingWithoutId={false}
  244. isUpdating={false}
  245. loadingState={LoadingState.LOADING_VM_WITH_ID}
  246. reduxProjectId={'100'}
  247. store={store}
  248. vm={vm}
  249. />
  250. );
  251. mounted.setProps({
  252. isUpdating: true,
  253. loadingState: LoadingState.MANUAL_UPDATING
  254. });
  255. expect(mockedStoreProject).toHaveBeenCalled();
  256. });
  257. test('if we are already in updating/saving state, vm project ' +
  258. 'should NOT requested, alert should NOT show', () => {
  259. const mockedShowCreatingAlert = jest.fn();
  260. const Component = () => <div />;
  261. const WrappedComponent = projectSaverHOC(Component);
  262. const mockedStoreProject = jest.fn(() => Promise.resolve());
  263. // The first wrapper is redux's Connect HOC
  264. WrappedComponent.WrappedComponent.prototype.storeProject = mockedStoreProject;
  265. const mounted = mount(
  266. <WrappedComponent
  267. canSave
  268. isUpdating
  269. isCreatingNew={false}
  270. isShowingWithId={false}
  271. isShowingWithoutId={false}
  272. loadingState={LoadingState.MANUAL_UPDATING}
  273. reduxProjectId={'100'}
  274. store={store}
  275. vm={vm}
  276. onShowCreatingAlert={mockedShowCreatingAlert}
  277. />
  278. );
  279. mounted.setProps({
  280. isUpdating: true,
  281. loadingState: LoadingState.AUTO_UPDATING,
  282. reduxProjectId: '99' // random change to force a re-render and componentDidUpdate
  283. });
  284. expect(mockedStoreProject).not.toHaveBeenCalled();
  285. expect(mockedShowCreatingAlert).not.toHaveBeenCalled();
  286. });
  287. test('if user saves, inline saving alert should show', () => {
  288. const mockedShowSavingAlert = jest.fn();
  289. const Component = () => <div />;
  290. const WrappedComponent = projectSaverHOC(Component);
  291. const mounted = mount(
  292. <WrappedComponent
  293. canSave
  294. isShowingWithoutId
  295. canCreateNew={false}
  296. isCreatingNew={false}
  297. isManualUpdating={false}
  298. isShowingWithId={false}
  299. isUpdating={false}
  300. loadingState={LoadingState.SHOWING_WITH_ID}
  301. store={store}
  302. vm={vm}
  303. onShowSavingAlert={mockedShowSavingAlert}
  304. />
  305. );
  306. mounted.setProps({
  307. isManualUpdating: true,
  308. isUpdating: true
  309. });
  310. expect(mockedShowSavingAlert).toHaveBeenCalled();
  311. });
  312. test('if project is changed, it should autosave after interval', () => {
  313. const Component = () => <div />;
  314. const WrappedComponent = projectSaverHOC(Component);
  315. const mockedAutoUpdate = jest.fn(() => Promise.resolve());
  316. const mounted = mount(
  317. <WrappedComponent
  318. canSave
  319. isShowingSaveable
  320. isShowingWithId
  321. loadingState={LoadingState.SHOWING_WITH_ID}
  322. store={store}
  323. vm={vm}
  324. onAutoUpdateProject={mockedAutoUpdate}
  325. />
  326. );
  327. mounted.setProps({
  328. projectChanged: true
  329. });
  330. // Fast-forward until all timers have been executed
  331. jest.runAllTimers();
  332. expect(mockedAutoUpdate).toHaveBeenCalled();
  333. });
  334. test('if project is changed several times in a row, it should only autosave once', () => {
  335. const Component = () => <div />;
  336. const WrappedComponent = projectSaverHOC(Component);
  337. const mockedAutoUpdate = jest.fn(() => Promise.resolve());
  338. const mounted = mount(
  339. <WrappedComponent
  340. canSave
  341. isShowingSaveable
  342. isShowingWithId
  343. loadingState={LoadingState.SHOWING_WITH_ID}
  344. store={store}
  345. vm={vm}
  346. onAutoUpdateProject={mockedAutoUpdate}
  347. />
  348. );
  349. mounted.setProps({
  350. projectChanged: true,
  351. reduxProjectTitle: 'a'
  352. });
  353. mounted.setProps({
  354. projectChanged: true,
  355. reduxProjectTitle: 'b'
  356. });
  357. mounted.setProps({
  358. projectChanged: true,
  359. reduxProjectTitle: 'c'
  360. });
  361. // Fast-forward until all timers have been executed
  362. jest.runAllTimers();
  363. expect(mockedAutoUpdate).toHaveBeenCalledTimes(1);
  364. });
  365. test('if project is not changed, it should not autosave after interval', () => {
  366. const Component = () => <div />;
  367. const WrappedComponent = projectSaverHOC(Component);
  368. const mockedAutoUpdate = jest.fn(() => Promise.resolve());
  369. const mounted = mount(
  370. <WrappedComponent
  371. canSave
  372. isShowingSaveable
  373. isShowingWithId
  374. loadingState={LoadingState.SHOWING_WITH_ID}
  375. store={store}
  376. vm={vm}
  377. onAutoUpdateProject={mockedAutoUpdate}
  378. />
  379. );
  380. mounted.setProps({
  381. projectChanged: false
  382. });
  383. // Fast-forward until all timers have been executed
  384. jest.runAllTimers();
  385. expect(mockedAutoUpdate).not.toHaveBeenCalled();
  386. });
  387. test('when starting to remix, onRemixing should be called with param true', () => {
  388. const mockedOnRemixing = jest.fn();
  389. const mockedStoreProject = jest.fn(() => Promise.resolve());
  390. const Component = () => <div />;
  391. const WrappedComponent = projectSaverHOC(Component);
  392. WrappedComponent.WrappedComponent.prototype.storeProject = mockedStoreProject;
  393. const mounted = mount(
  394. <WrappedComponent
  395. isRemixing={false}
  396. store={store}
  397. vm={vm}
  398. onRemixing={mockedOnRemixing}
  399. />
  400. );
  401. mounted.setProps({
  402. isRemixing: true
  403. });
  404. expect(mockedOnRemixing).toHaveBeenCalledWith(true);
  405. });
  406. test('when starting to remix, onRemixing should be called with param false', () => {
  407. const mockedOnRemixing = jest.fn();
  408. const mockedStoreProject = jest.fn(() => Promise.resolve());
  409. const Component = () => <div />;
  410. const WrappedComponent = projectSaverHOC(Component);
  411. WrappedComponent.WrappedComponent.prototype.storeProject = mockedStoreProject;
  412. const mounted = mount(
  413. <WrappedComponent
  414. isRemixing
  415. store={store}
  416. vm={vm}
  417. onRemixing={mockedOnRemixing}
  418. />
  419. );
  420. mounted.setProps({
  421. isRemixing: false
  422. });
  423. expect(mockedOnRemixing).toHaveBeenCalledWith(false);
  424. });
  425. test('uses onSetProjectThumbnailer on mount/unmount', () => {
  426. const Component = () => <div />;
  427. const WrappedComponent = projectSaverHOC(Component);
  428. const setThumb = jest.fn();
  429. const mounted = mount(
  430. <WrappedComponent
  431. store={store}
  432. vm={vm}
  433. onSetProjectThumbnailer={setThumb}
  434. />
  435. );
  436. // Set project thumbnailer should be called on mount
  437. expect(setThumb).toHaveBeenCalledTimes(1);
  438. // And it should not pass that function on to wrapped element
  439. expect(mounted.find(Component).props().onSetProjectThumbnailer).toBeUndefined();
  440. // Unmounting should call it again with null
  441. mounted.unmount();
  442. expect(setThumb).toHaveBeenCalledTimes(2);
  443. expect(setThumb.mock.calls[1][0]).toBe(null);
  444. });
  445. test('uses onSetProjectSaver on mount/unmount', () => {
  446. const Component = () => <div />;
  447. const WrappedComponent = projectSaverHOC(Component);
  448. const setSaver = jest.fn();
  449. const mounted = mount(
  450. <WrappedComponent
  451. store={store}
  452. vm={vm}
  453. onSetProjectSaver={setSaver}
  454. />
  455. );
  456. // Set project saver should be called on mount
  457. expect(setSaver).toHaveBeenCalledTimes(1);
  458. // And it should not pass that function on to wrapped element
  459. expect(mounted.find(Component).props().onSetProjectSaver).toBeUndefined();
  460. // Unmounting should call it again with null
  461. mounted.unmount();
  462. expect(setSaver).toHaveBeenCalledTimes(2);
  463. expect(setSaver.mock.calls[1][0]).toBe(null);
  464. });
  465. });