Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/gui/gui.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@ GUIComponent.defaultProps = {
canSave: false,
canSaveAsCopy: false,
canShare: false,
onUpdateProjectTitle: () => {},
stageSizeMode: STAGE_SIZE_MODES.large
};

Expand Down
2 changes: 1 addition & 1 deletion src/components/menu-bar/menu-bar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ class MenuBar extends React.Component {
)}
</MenuSection>
<MenuSection>
<SBFileUploader>
<SBFileUploader onUpdateProjectTitle={this.props.onUpdateProjectTitle}>
{(renderFileInput, loadProject) => (
<MenuItem
onClick={loadProject}
Expand Down
30 changes: 18 additions & 12 deletions src/containers/sb-file-uploader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {defineMessages, injectIntl, intlShape} from 'react-intl';

import analytics from '../lib/analytics';
import log from '../lib/log';
import {setProjectTitle} from '../reducers/project-title';
import {LoadingStates, onLoadedProject, onProjectUploadStarted} from '../reducers/project-state';

import {
Expand Down Expand Up @@ -42,12 +41,20 @@ class SBFileUploader extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'getProjectTitleFromFilename',
'renderFileInput',
'setFileInput',
'handleChange',
'handleClick'
]);
}
getProjectTitleFromFilename (fileInputFilename) {
if (!fileInputFilename) return '';
// only parse title from files like "filename.sb2" or "filename.sb3"
const matches = fileInputFilename.match(/^(.*)\.sb[23]$/);
if (!matches) return '';
return matches[1].substring(0, 100); // truncate project title to max 100 chars
}
// called when user has finished selecting a file to upload
handleChange (e) {
// Remove the hash if any (without triggering a hash change event or a reload)
Expand Down Expand Up @@ -77,14 +84,8 @@ class SBFileUploader extends React.Component {
if (thisFileInput.files) { // Don't attempt to load if no file was selected
this.props.onLoadingStarted();
reader.readAsArrayBuffer(thisFileInput.files[0]);
// extract the title from the file and set it as current project title
if (thisFileInput.files[0].name) {
const matches = thisFileInput.files[0].name.match(/^(.*)\.sb3$/);
if (matches) {
const truncatedProjectTitle = matches[1].substring(0, 100);
this.props.onSetProjectTitle(truncatedProjectTitle);
}
}
const uploadedProjectTitle = this.getProjectTitleFromFilename(thisFileInput.files[0].name);
this.props.onUpdateProjectTitle(uploadedProjectTitle);
}
}
handleClick () {
Expand Down Expand Up @@ -116,7 +117,7 @@ SBFileUploader.propTypes = {
loadingState: PropTypes.oneOf(LoadingStates),
onLoadingFinished: PropTypes.func,
onLoadingStarted: PropTypes.func,
onSetProjectTitle: PropTypes.func,
onUpdateProjectTitle: PropTypes.func,
vm: PropTypes.shape({
loadProject: PropTypes.func
})
Expand All @@ -131,14 +132,19 @@ const mapDispatchToProps = dispatch => ({
dispatch(onLoadedProject(loadingState));
dispatch(closeLoadingProject());
},
onSetProjectTitle: title => dispatch(setProjectTitle(title)),
onLoadingStarted: () => {
dispatch(openLoadingProject());
dispatch(onProjectUploadStarted());
}
});

// Allow incoming props to override redux-provided props. Used to mock in tests.
const mergeProps = (stateProps, dispatchProps, ownProps) => Object.assign(
{}, stateProps, dispatchProps, ownProps
);

export default connect(
mapStateToProps,
mapDispatchToProps
mapDispatchToProps,
mergeProps
)(injectIntl(SBFileUploader));
87 changes: 87 additions & 0 deletions test/unit/containers/sb-file-uploader.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React from 'react';

import {shallowWithIntl} from '../../helpers/intl-helpers.jsx';
import configureStore from 'redux-mock-store';
import SBFileUploader from '../../../src/containers/sb-file-uploader';
import {LoadingState} from '../../../src/reducers/project-state';

jest.mock('react-ga'); // must mock this entire library, or lib/analytics causes error

describe('SBFileUploader Container', () => {
const mockStore = configureStore();
let onLoadingFinished;
let onLoadingStarted;
let onUpdateProjectTitle;
let store;

// Wrap this in a function so it gets test specific states and can be reused.
const getContainer = function () {
return (
<SBFileUploader
onLoadingFinished={onLoadingFinished}
onLoadingStarted={onLoadingStarted}
onUpdateProjectTitle={onUpdateProjectTitle}
>
{(renderFileInput, loadProject) => (
<div
onClick={loadProject}
/>
)}
</SBFileUploader>
);
};

beforeEach(() => {
store = mockStore({
scratchGui: {
projectState: {
loadingState: LoadingState.SHOWING_WITH_ID
},
vm: {}
}
});
onUpdateProjectTitle = jest.fn();
onLoadingFinished = jest.fn();
onLoadingStarted = jest.fn();
});

test('correctly sets title with .sb3 filename', () => {
const wrapper = shallowWithIntl(getContainer(), {context: {store}});
const instance = wrapper
.dive() // unwrap redux Connect(InjectIntl(SBFileUploader))
.dive() // unwrap InjectIntl(SBFileUploader)
.instance(); // SBFileUploader
const projectName = instance.getProjectTitleFromFilename('my project is great.sb3');
expect(projectName).toBe('my project is great');
});

test('correctly sets title with .sb2 filename', () => {
const wrapper = shallowWithIntl(getContainer(), {context: {store}});
const instance = wrapper
.dive() // unwrap redux Connect(InjectIntl(SBFileUploader))
.dive() // unwrap InjectIntl(SBFileUploader)
.instance(); // SBFileUploader
const projectName = instance.getProjectTitleFromFilename('my project is great.sb2');
expect(projectName).toBe('my project is great');
});

test('sets blank title with .sb filename', () => {
const wrapper = shallowWithIntl(getContainer(), {context: {store}});
const instance = wrapper
.dive() // unwrap redux Connect(InjectIntl(SBFileUploader))
.dive() // unwrap InjectIntl(SBFileUploader)
.instance(); // SBFileUploader
const projectName = instance.getProjectTitleFromFilename('my project is great.sb');
expect(projectName).toBe('');
});

test('sets blank title with filename with no extension', () => {
const wrapper = shallowWithIntl(getContainer(), {context: {store}});
const instance = wrapper
.dive() // unwrap redux Connect(InjectIntl(SBFileUploader))
.dive() // unwrap InjectIntl(SBFileUploader)
.instance(); // SBFileUploader
const projectName = instance.getProjectTitleFromFilename('my project is great');
expect(projectName).toBe('');
});
});