Initial commit of 001code-html Scratch frontend project.
Includes scratch-gui, scratch-vm, scratch-blocks, scratch-render, scratch-l10n, and deployment config. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
491
scratch-gui/test/unit/util/project-saver-hoc.test.jsx
Normal file
491
scratch-gui/test/unit/util/project-saver-hoc.test.jsx
Normal file
@@ -0,0 +1,491 @@
|
||||
import 'web-audio-test-api';
|
||||
|
||||
import React from 'react';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import {mount} from 'enzyme';
|
||||
import {LoadingState} from '../../../src/reducers/project-state';
|
||||
import VM from 'scratch-vm';
|
||||
|
||||
import projectSaverHOC from '../../../src/lib/project-saver-hoc.jsx';
|
||||
|
||||
describe('projectSaverHOC', () => {
|
||||
const mockStore = configureStore();
|
||||
let store;
|
||||
let vm;
|
||||
|
||||
beforeEach(() => {
|
||||
store = mockStore({
|
||||
scratchGui: {
|
||||
projectChanged: false,
|
||||
projectState: {},
|
||||
projectTitle: 'Scratch Project',
|
||||
timeout: {
|
||||
autoSaveTimeoutId: null
|
||||
}
|
||||
},
|
||||
locales: {
|
||||
locale: 'en'
|
||||
}
|
||||
});
|
||||
vm = new VM();
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
test('if canSave becomes true when showing a project with an id, project will be saved', () => {
|
||||
const mockedUpdateProject = jest.fn();
|
||||
const Component = () => <div />;
|
||||
const WrappedComponent = projectSaverHOC(Component);
|
||||
const mounted = mount(
|
||||
<WrappedComponent
|
||||
isShowingWithId
|
||||
canSave={false}
|
||||
isCreatingNew={false}
|
||||
isShowingSaveable={false} // set explicitly because it relies on ownProps.canSave
|
||||
isShowingWithoutId={false}
|
||||
isUpdating={false}
|
||||
loadingState={LoadingState.SHOWING_WITH_ID}
|
||||
store={store}
|
||||
vm={vm}
|
||||
onAutoUpdateProject={mockedUpdateProject}
|
||||
/>
|
||||
);
|
||||
mounted.setProps({
|
||||
canSave: true,
|
||||
isShowingSaveable: true
|
||||
});
|
||||
expect(mockedUpdateProject).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('if canSave is already true and we show a project with an id, project will NOT be saved', () => {
|
||||
const mockedSaveProject = jest.fn();
|
||||
const Component = () => <div />;
|
||||
const WrappedComponent = projectSaverHOC(Component);
|
||||
const mounted = mount(
|
||||
<WrappedComponent
|
||||
canSave
|
||||
isCreatingNew={false}
|
||||
isShowingWithId={false}
|
||||
isShowingWithoutId={false}
|
||||
isUpdating={false}
|
||||
loadingState={LoadingState.LOADING_VM_WITH_ID}
|
||||
store={store}
|
||||
vm={vm}
|
||||
onAutoUpdateProject={mockedSaveProject}
|
||||
/>
|
||||
);
|
||||
mounted.setProps({
|
||||
canSave: true,
|
||||
isShowingWithId: true,
|
||||
loadingState: LoadingState.SHOWING_WITH_ID
|
||||
});
|
||||
expect(mockedSaveProject).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('if canSave is false when showing a project without an id, project will NOT be created', () => {
|
||||
const mockedCreateProject = jest.fn();
|
||||
const Component = () => <div />;
|
||||
const WrappedComponent = projectSaverHOC(Component);
|
||||
const mounted = mount(
|
||||
<WrappedComponent
|
||||
isShowingWithoutId
|
||||
canSave={false}
|
||||
isCreatingNew={false}
|
||||
isShowingWithId={false}
|
||||
isUpdating={false}
|
||||
loadingState={LoadingState.LOADING_VM_NEW_DEFAULT}
|
||||
store={store}
|
||||
vm={vm}
|
||||
onCreateProject={mockedCreateProject}
|
||||
/>
|
||||
);
|
||||
mounted.setProps({
|
||||
isShowingWithoutId: true,
|
||||
loadingState: LoadingState.SHOWING_WITHOUT_ID
|
||||
});
|
||||
expect(mockedCreateProject).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('if canCreateNew becomes true when showing a project without an id, project will be created', () => {
|
||||
const mockedCreateProject = jest.fn();
|
||||
const Component = () => <div />;
|
||||
const WrappedComponent = projectSaverHOC(Component);
|
||||
const mounted = mount(
|
||||
<WrappedComponent
|
||||
isShowingWithoutId
|
||||
canCreateNew={false}
|
||||
isCreatingNew={false}
|
||||
isShowingWithId={false}
|
||||
isUpdating={false}
|
||||
loadingState={LoadingState.SHOWING_WITHOUT_ID}
|
||||
store={store}
|
||||
vm={vm}
|
||||
onCreateProject={mockedCreateProject}
|
||||
/>
|
||||
);
|
||||
mounted.setProps({
|
||||
canCreateNew: true
|
||||
});
|
||||
expect(mockedCreateProject).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('if canCreateNew is true and we transition to showing new project, project will be created', () => {
|
||||
const mockedCreateProject = jest.fn();
|
||||
const Component = () => <div />;
|
||||
const WrappedComponent = projectSaverHOC(Component);
|
||||
const mounted = mount(
|
||||
<WrappedComponent
|
||||
canCreateNew
|
||||
isCreatingNew={false}
|
||||
isShowingWithId={false}
|
||||
isShowingWithoutId={false}
|
||||
isUpdating={false}
|
||||
loadingState={LoadingState.LOADING_VM_NEW_DEFAULT}
|
||||
store={store}
|
||||
vm={vm}
|
||||
onCreateProject={mockedCreateProject}
|
||||
/>
|
||||
);
|
||||
mounted.setProps({
|
||||
isShowingWithoutId: true,
|
||||
loadingState: LoadingState.SHOWING_WITHOUT_ID
|
||||
});
|
||||
expect(mockedCreateProject).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('if we enter creating new state, vm project should be requested', () => {
|
||||
const Component = () => <div />;
|
||||
const WrappedComponent = projectSaverHOC(Component);
|
||||
const mockedStoreProject = jest.fn(() => Promise.resolve());
|
||||
// The first wrapper is redux's Connect HOC
|
||||
WrappedComponent.WrappedComponent.prototype.storeProject = mockedStoreProject;
|
||||
const mounted = mount(
|
||||
<WrappedComponent
|
||||
canSave
|
||||
isCreatingCopy={false}
|
||||
isCreatingNew={false}
|
||||
isRemixing={false}
|
||||
isShowingWithId={false}
|
||||
isShowingWithoutId={false}
|
||||
isUpdating={false}
|
||||
loadingState={LoadingState.LOADING_VM_NEW_DEFAULT}
|
||||
reduxProjectId={'100'}
|
||||
store={store}
|
||||
vm={vm}
|
||||
/>
|
||||
);
|
||||
mounted.setProps({
|
||||
isCreatingNew: true,
|
||||
loadingState: LoadingState.CREATING_NEW
|
||||
});
|
||||
expect(mockedStoreProject).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('if we enter remixing state, vm project should be requested, and alert should show', () => {
|
||||
const mockedShowCreatingRemixAlert = jest.fn();
|
||||
const Component = () => <div />;
|
||||
const WrappedComponent = projectSaverHOC(Component);
|
||||
const mockedStoreProject = jest.fn(() => Promise.resolve());
|
||||
// The first wrapper is redux's Connect HOC
|
||||
WrappedComponent.WrappedComponent.prototype.storeProject = mockedStoreProject;
|
||||
const mounted = mount(
|
||||
<WrappedComponent
|
||||
canSave
|
||||
isCreatingCopy={false}
|
||||
isCreatingNew={false}
|
||||
isRemixing={false}
|
||||
isShowingWithId={false}
|
||||
isShowingWithoutId={false}
|
||||
isUpdating={false}
|
||||
loadingState={LoadingState.SHOWING_WITH_ID}
|
||||
reduxProjectId={'100'}
|
||||
store={store}
|
||||
vm={vm}
|
||||
onShowCreatingRemixAlert={mockedShowCreatingRemixAlert}
|
||||
/>
|
||||
);
|
||||
mounted.setProps({
|
||||
isRemixing: true,
|
||||
loadingState: LoadingState.REMIXING
|
||||
});
|
||||
expect(mockedStoreProject).toHaveBeenCalled();
|
||||
expect(mockedShowCreatingRemixAlert).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('if we enter creating copy state, vm project should be requested, and alert should show', () => {
|
||||
const mockedShowCreatingCopyAlert = jest.fn();
|
||||
const Component = () => <div />;
|
||||
const WrappedComponent = projectSaverHOC(Component);
|
||||
const mockedStoreProject = jest.fn(() => Promise.resolve());
|
||||
// The first wrapper is redux's Connect HOC
|
||||
WrappedComponent.WrappedComponent.prototype.storeProject = mockedStoreProject;
|
||||
const mounted = mount(
|
||||
<WrappedComponent
|
||||
canSave
|
||||
isCreatingCopy={false}
|
||||
isCreatingNew={false}
|
||||
isRemixing={false}
|
||||
isShowingWithId={false}
|
||||
isShowingWithoutId={false}
|
||||
isUpdating={false}
|
||||
loadingState={LoadingState.SHOWING_WITH_ID}
|
||||
reduxProjectId={'100'}
|
||||
store={store}
|
||||
vm={vm}
|
||||
onShowCreatingCopyAlert={mockedShowCreatingCopyAlert}
|
||||
/>
|
||||
);
|
||||
mounted.setProps({
|
||||
isCreatingCopy: true,
|
||||
loadingState: LoadingState.CREATING_COPY
|
||||
});
|
||||
expect(mockedStoreProject).toHaveBeenCalled();
|
||||
expect(mockedShowCreatingCopyAlert).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('if we enter updating/saving state, vm project should be requested', () => {
|
||||
const Component = () => <div />;
|
||||
const WrappedComponent = projectSaverHOC(Component);
|
||||
const mockedStoreProject = jest.fn(() => Promise.resolve());
|
||||
// The first wrapper is redux's Connect HOC
|
||||
WrappedComponent.WrappedComponent.prototype.storeProject = mockedStoreProject;
|
||||
const mounted = mount(
|
||||
<WrappedComponent
|
||||
canSave
|
||||
isCreatingNew={false}
|
||||
isShowingWithId={false}
|
||||
isShowingWithoutId={false}
|
||||
isUpdating={false}
|
||||
loadingState={LoadingState.LOADING_VM_WITH_ID}
|
||||
reduxProjectId={'100'}
|
||||
store={store}
|
||||
vm={vm}
|
||||
/>
|
||||
);
|
||||
mounted.setProps({
|
||||
isUpdating: true,
|
||||
loadingState: LoadingState.MANUAL_UPDATING
|
||||
});
|
||||
expect(mockedStoreProject).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('if we are already in updating/saving state, vm project ' +
|
||||
'should NOT requested, alert should NOT show', () => {
|
||||
const mockedShowCreatingAlert = jest.fn();
|
||||
const Component = () => <div />;
|
||||
const WrappedComponent = projectSaverHOC(Component);
|
||||
const mockedStoreProject = jest.fn(() => Promise.resolve());
|
||||
// The first wrapper is redux's Connect HOC
|
||||
WrappedComponent.WrappedComponent.prototype.storeProject = mockedStoreProject;
|
||||
const mounted = mount(
|
||||
<WrappedComponent
|
||||
canSave
|
||||
isUpdating
|
||||
isCreatingNew={false}
|
||||
isShowingWithId={false}
|
||||
isShowingWithoutId={false}
|
||||
loadingState={LoadingState.MANUAL_UPDATING}
|
||||
reduxProjectId={'100'}
|
||||
store={store}
|
||||
vm={vm}
|
||||
onShowCreatingAlert={mockedShowCreatingAlert}
|
||||
/>
|
||||
);
|
||||
mounted.setProps({
|
||||
isUpdating: true,
|
||||
loadingState: LoadingState.AUTO_UPDATING,
|
||||
reduxProjectId: '99' // random change to force a re-render and componentDidUpdate
|
||||
});
|
||||
expect(mockedStoreProject).not.toHaveBeenCalled();
|
||||
expect(mockedShowCreatingAlert).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('if user saves, inline saving alert should show', () => {
|
||||
const mockedShowSavingAlert = jest.fn();
|
||||
const Component = () => <div />;
|
||||
const WrappedComponent = projectSaverHOC(Component);
|
||||
const mounted = mount(
|
||||
<WrappedComponent
|
||||
canSave
|
||||
isShowingWithoutId
|
||||
canCreateNew={false}
|
||||
isCreatingNew={false}
|
||||
isManualUpdating={false}
|
||||
isShowingWithId={false}
|
||||
isUpdating={false}
|
||||
loadingState={LoadingState.SHOWING_WITH_ID}
|
||||
store={store}
|
||||
vm={vm}
|
||||
onShowSavingAlert={mockedShowSavingAlert}
|
||||
/>
|
||||
);
|
||||
mounted.setProps({
|
||||
isManualUpdating: true,
|
||||
isUpdating: true
|
||||
});
|
||||
expect(mockedShowSavingAlert).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('if project is changed, it should autosave after interval', () => {
|
||||
const Component = () => <div />;
|
||||
const WrappedComponent = projectSaverHOC(Component);
|
||||
const mockedAutoUpdate = jest.fn(() => Promise.resolve());
|
||||
const mounted = mount(
|
||||
<WrappedComponent
|
||||
canSave
|
||||
isShowingSaveable
|
||||
isShowingWithId
|
||||
loadingState={LoadingState.SHOWING_WITH_ID}
|
||||
store={store}
|
||||
vm={vm}
|
||||
onAutoUpdateProject={mockedAutoUpdate}
|
||||
/>
|
||||
);
|
||||
mounted.setProps({
|
||||
projectChanged: true
|
||||
});
|
||||
// Fast-forward until all timers have been executed
|
||||
jest.runAllTimers();
|
||||
expect(mockedAutoUpdate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('if project is changed several times in a row, it should only autosave once', () => {
|
||||
const Component = () => <div />;
|
||||
const WrappedComponent = projectSaverHOC(Component);
|
||||
const mockedAutoUpdate = jest.fn(() => Promise.resolve());
|
||||
const mounted = mount(
|
||||
<WrappedComponent
|
||||
canSave
|
||||
isShowingSaveable
|
||||
isShowingWithId
|
||||
loadingState={LoadingState.SHOWING_WITH_ID}
|
||||
store={store}
|
||||
vm={vm}
|
||||
onAutoUpdateProject={mockedAutoUpdate}
|
||||
/>
|
||||
);
|
||||
mounted.setProps({
|
||||
projectChanged: true,
|
||||
reduxProjectTitle: 'a'
|
||||
});
|
||||
mounted.setProps({
|
||||
projectChanged: true,
|
||||
reduxProjectTitle: 'b'
|
||||
});
|
||||
mounted.setProps({
|
||||
projectChanged: true,
|
||||
reduxProjectTitle: 'c'
|
||||
});
|
||||
// Fast-forward until all timers have been executed
|
||||
jest.runAllTimers();
|
||||
expect(mockedAutoUpdate).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('if project is not changed, it should not autosave after interval', () => {
|
||||
const Component = () => <div />;
|
||||
const WrappedComponent = projectSaverHOC(Component);
|
||||
const mockedAutoUpdate = jest.fn(() => Promise.resolve());
|
||||
const mounted = mount(
|
||||
<WrappedComponent
|
||||
canSave
|
||||
isShowingSaveable
|
||||
isShowingWithId
|
||||
loadingState={LoadingState.SHOWING_WITH_ID}
|
||||
store={store}
|
||||
vm={vm}
|
||||
onAutoUpdateProject={mockedAutoUpdate}
|
||||
/>
|
||||
);
|
||||
mounted.setProps({
|
||||
projectChanged: false
|
||||
});
|
||||
// Fast-forward until all timers have been executed
|
||||
jest.runAllTimers();
|
||||
expect(mockedAutoUpdate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('when starting to remix, onRemixing should be called with param true', () => {
|
||||
const mockedOnRemixing = jest.fn();
|
||||
const mockedStoreProject = jest.fn(() => Promise.resolve());
|
||||
const Component = () => <div />;
|
||||
const WrappedComponent = projectSaverHOC(Component);
|
||||
WrappedComponent.WrappedComponent.prototype.storeProject = mockedStoreProject;
|
||||
const mounted = mount(
|
||||
<WrappedComponent
|
||||
isRemixing={false}
|
||||
store={store}
|
||||
vm={vm}
|
||||
onRemixing={mockedOnRemixing}
|
||||
/>
|
||||
);
|
||||
mounted.setProps({
|
||||
isRemixing: true
|
||||
});
|
||||
expect(mockedOnRemixing).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
test('when starting to remix, onRemixing should be called with param false', () => {
|
||||
const mockedOnRemixing = jest.fn();
|
||||
const mockedStoreProject = jest.fn(() => Promise.resolve());
|
||||
const Component = () => <div />;
|
||||
const WrappedComponent = projectSaverHOC(Component);
|
||||
WrappedComponent.WrappedComponent.prototype.storeProject = mockedStoreProject;
|
||||
const mounted = mount(
|
||||
<WrappedComponent
|
||||
isRemixing
|
||||
store={store}
|
||||
vm={vm}
|
||||
onRemixing={mockedOnRemixing}
|
||||
/>
|
||||
);
|
||||
mounted.setProps({
|
||||
isRemixing: false
|
||||
});
|
||||
expect(mockedOnRemixing).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
test('uses onSetProjectThumbnailer on mount/unmount', () => {
|
||||
const Component = () => <div />;
|
||||
const WrappedComponent = projectSaverHOC(Component);
|
||||
const setThumb = jest.fn();
|
||||
const mounted = mount(
|
||||
<WrappedComponent
|
||||
store={store}
|
||||
vm={vm}
|
||||
onSetProjectThumbnailer={setThumb}
|
||||
/>
|
||||
);
|
||||
// Set project thumbnailer should be called on mount
|
||||
expect(setThumb).toHaveBeenCalledTimes(1);
|
||||
|
||||
// And it should not pass that function on to wrapped element
|
||||
expect(mounted.find(Component).props().onSetProjectThumbnailer).toBeUndefined();
|
||||
|
||||
// Unmounting should call it again with null
|
||||
mounted.unmount();
|
||||
expect(setThumb).toHaveBeenCalledTimes(2);
|
||||
expect(setThumb.mock.calls[1][0]).toBe(null);
|
||||
});
|
||||
|
||||
test('uses onSetProjectSaver on mount/unmount', () => {
|
||||
const Component = () => <div />;
|
||||
const WrappedComponent = projectSaverHOC(Component);
|
||||
const setSaver = jest.fn();
|
||||
const mounted = mount(
|
||||
<WrappedComponent
|
||||
store={store}
|
||||
vm={vm}
|
||||
onSetProjectSaver={setSaver}
|
||||
/>
|
||||
);
|
||||
// Set project saver should be called on mount
|
||||
expect(setSaver).toHaveBeenCalledTimes(1);
|
||||
|
||||
// And it should not pass that function on to wrapped element
|
||||
expect(mounted.find(Component).props().onSetProjectSaver).toBeUndefined();
|
||||
|
||||
// Unmounting should call it again with null
|
||||
mounted.unmount();
|
||||
expect(setSaver).toHaveBeenCalledTimes(2);
|
||||
expect(setSaver.mock.calls[1][0]).toBe(null);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user