Use testing-library instead of enzyme for tests

This commit is contained in:
Andrew Nicolaou 2020-06-28 15:05:33 +02:00
parent 840e27b3fd
commit 9d55fa378a
13 changed files with 1032 additions and 1069 deletions

View file

@ -1,183 +0,0 @@
import React from 'react';
import { shallow } from 'enzyme';
import { FileNode } from '../../modules/IDE/components/FileNode';
describe('<FileNode />', () => {
let component;
let props = {};
let input;
let renameTriggerButton;
const changeName = (newFileName) => {
renameTriggerButton.simulate('click');
input.simulate('change', { target: { value: newFileName } });
input.simulate('blur');
};
const getState = () => component.state();
const getUpdatedName = () => getState().updatedName;
describe('with valid props, regardless of filetype', () => {
['folder', 'file'].forEach((fileType) => {
beforeEach(() => {
props = {
...props,
id: '0',
name: 'test.jsx',
fileType,
canEdit: true,
children: [],
authenticated: false,
setSelectedFile: jest.fn(),
deleteFile: jest.fn(),
updateFileName: jest.fn(),
resetSelectedFile: jest.fn(),
newFile: jest.fn(),
newFolder: jest.fn(),
showFolderChildren: jest.fn(),
hideFolderChildren: jest.fn(),
openUploadFileModal: jest.fn(),
setProjectName: jest.fn(),
};
component = shallow(<FileNode {...props} />);
});
describe('when changing name', () => {
beforeEach(() => {
input = component.find('.sidebar__file-item-input');
renameTriggerButton = component
.find('.sidebar__file-item-option')
.first();
component.setState({ isEditing: true });
});
describe('to an empty name', () => {
const newName = '';
beforeEach(() => changeName(newName));
it('should not save', () => expect(props.updateFileName).not.toHaveBeenCalled());
it('should reset name', () => expect(getUpdatedName()).toEqual(props.name));
});
});
});
});
describe('as file with valid props', () => {
beforeEach(() => {
props = {
...props,
id: '0',
name: 'test.jsx',
fileType: 'file',
canEdit: true,
children: [],
authenticated: false,
setSelectedFile: jest.fn(),
deleteFile: jest.fn(),
updateFileName: jest.fn(),
resetSelectedFile: jest.fn(),
newFile: jest.fn(),
newFolder: jest.fn(),
showFolderChildren: jest.fn(),
hideFolderChildren: jest.fn(),
openUploadFileModal: jest.fn()
};
component = shallow(<FileNode {...props} />);
});
describe('when changing name', () => {
beforeEach(() => {
input = component.find('.sidebar__file-item-input');
renameTriggerButton = component
.find('.sidebar__file-item-option')
.first();
component.setState({ isEditing: true });
});
it('should render', () => expect(component).toBeDefined());
// it('should debug', () => console.log(component.debug()));
describe('to a valid filename', () => {
const newName = 'newname.jsx';
beforeEach(() => changeName(newName));
it('should save the name', () => {
expect(props.updateFileName).toBeCalledWith(props.id, newName);
});
});
// Failure Scenarios
describe('to an extensionless filename', () => {
const newName = 'extensionless';
beforeEach(() => changeName(newName));
});
it('should not save', () => expect(props.setProjectName).not.toHaveBeenCalled());
it('should reset name', () => expect(getUpdatedName()).toEqual(props.name));
describe('to different extension', () => {
const newName = 'name.gif';
beforeEach(() => changeName(newName));
it('should not save', () => expect(props.setProjectName).not.toHaveBeenCalled());
it('should reset name', () => expect(getUpdatedName()).toEqual(props.name));
});
describe('to just an extension', () => {
const newName = '.jsx';
beforeEach(() => changeName(newName));
it('should not save', () => expect(props.updateFileName).not.toHaveBeenCalled());
it('should reset name', () => expect(getUpdatedName()).toEqual(props.name));
});
});
});
describe('as folder with valid props', () => {
beforeEach(() => {
props = {
...props,
id: '0',
children: [],
name: 'filename',
fileType: 'folder',
canEdit: true,
authenticated: false,
setSelectedFile: jest.fn(),
deleteFile: jest.fn(),
updateFileName: jest.fn(),
resetSelectedFile: jest.fn(),
newFile: jest.fn(),
newFolder: jest.fn(),
showFolderChildren: jest.fn(),
hideFolderChildren: jest.fn(),
openUploadFileModal: jest.fn()
};
component = shallow(<FileNode {...props} />);
});
describe('when changing name', () => {
beforeEach(() => {
input = component.find('.sidebar__file-item-input');
renameTriggerButton = component
.find('.sidebar__file-item-option')
.first();
component.setState({ isEditing: true });
});
describe('to a foldername', () => {
const newName = 'newfoldername';
beforeEach(() => changeName(newName));
it('should save', () => expect(props.updateFileName).toBeCalledWith(props.id, newName));
it('should update name', () => expect(getUpdatedName()).toEqual(newName));
});
describe('to a filename', () => {
const newName = 'filename.jsx';
beforeEach(() => changeName(newName));
it('should not save', () => expect(props.updateFileName).not.toHaveBeenCalled());
it('should reset name', () => expect(getUpdatedName()).toEqual(props.name));
});
});
});
});

View file

@ -1,9 +1,9 @@
import React from 'react'; import React from 'react';
import { shallow } from 'enzyme'; import { render } from '@testing-library/react';
import renderer from 'react-test-renderer';
import { NavComponent } from './../Nav';
import { NavComponent } from '../Nav';
describe('Nav', () => { describe('Nav', () => {
const props = { const props = {
@ -46,17 +46,9 @@ describe('Nav', () => {
id: 'root-file' id: 'root-file'
} }
}; };
const getWrapper = () => shallow(<NavComponent {...props} />);
test('it renders main navigation', () => {
const nav = getWrapper();
expect(nav.exists('.nav')).toEqual(true);
});
it('renders correctly', () => { it('renders correctly', () => {
const tree = renderer const { asFragment } = render(<NavComponent {...props} />);
.create(<NavComponent {...props} />) expect(asFragment()).toMatchSnapshot();
.toJSON();
expect(tree).toMatchSnapshot();
}); });
}); });

View file

@ -1,105 +0,0 @@
import React from 'react';
import { shallow } from 'enzyme';
import { ToolbarComponent } from '../../modules/IDE/components/Toolbar';
const initialProps = {
isPlaying: false,
preferencesIsVisible: false,
stopSketch: jest.fn(),
setProjectName: jest.fn(),
openPreferences: jest.fn(),
showEditProjectName: jest.fn(),
hideEditProjectName: jest.fn(),
infiniteLoop: false,
autorefresh: false,
setAutorefresh: jest.fn(),
setTextOutput: jest.fn(),
setGridOutput: jest.fn(),
startSketch: jest.fn(),
startAccessibleSketch: jest.fn(),
saveProject: jest.fn(),
currentUser: 'me',
originalProjectName: 'testname',
owner: {
username: 'me'
},
project: {
name: 'testname',
isEditingName: false,
id: 'id',
},
};
describe('<ToolbarComponent />', () => {
let component;
let props = initialProps;
let input;
let renameTriggerButton;
const changeName = (newFileName) => {
component.find('.toolbar__project-name').simulate('click', { preventDefault: jest.fn() });
input = component.find('.toolbar__project-name-input');
renameTriggerButton = component.find('.toolbar__edit-name-button');
renameTriggerButton.simulate('click');
input.simulate('change', { target: { value: newFileName } });
input.simulate('blur');
};
const setProps = (additionalProps) => {
props = {
...props,
...additionalProps,
project: {
...props.project,
...(additionalProps || {}).project
},
};
};
// Test Cases
describe('with valid props', () => {
beforeEach(() => {
setProps();
component = shallow(<ToolbarComponent {...props} />);
});
it('renders', () => expect(component).toBeDefined());
describe('when use owns sketch', () => {
beforeEach(() => setProps({ currentUser: props.owner.username }));
describe('when changing sketch name', () => {
beforeEach(() => {
setProps({
project: { isEditingName: true, name: 'testname' },
setProjectName: jest.fn(name => component.setProps({ project: { name } })),
});
component = shallow(<ToolbarComponent {...props} />);
});
describe('to a valid name', () => {
beforeEach(() => changeName('hello'));
it('should save', () => expect(props.setProjectName).toBeCalledWith('hello'));
});
describe('to an empty name', () => {
beforeEach(() => changeName(''));
it('should set name to empty', () => expect(props.setProjectName).toBeCalledWith(''));
it(
'should detect empty name and revert to original',
() => expect(props.setProjectName).toHaveBeenLastCalledWith(initialProps.project.name)
);
});
});
});
describe('when user does not own sketch', () => {
beforeEach(() => setProps({ currentUser: 'not-the-owner' }));
it('should disable edition', () => expect(component.find('.toolbar__edit-name-button')).toEqual({}));
});
});
});

View file

@ -1,325 +1,236 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Nav renders correctly 1`] = ` exports[`Nav renders correctly 1`] = `
<header> <DocumentFragment>
<header>
<nav <nav
className="nav" class="nav"
title="main-navigation" title="main-navigation"
> >
<ul <ul
className="nav__items-left" class="nav__items-left"
> >
<li <li
className="nav__item-logo" class="nav__item-logo"
> >
<test-file-stub <test-file-stub
aria-label="p5.js Logo" aria-label="p5.js Logo"
className="svg__logo" classname="svg__logo"
focusable="false" focusable="false"
role="img" role="img"
/> />
</li> </li>
<li <li
className="nav__item" class="nav__item"
>
<button
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOver={[Function]}
> >
<button>
<span <span
className="nav__item-header" class="nav__item-header"
> >
File File
</span> </span>
<test-file-stub <test-file-stub
aria-hidden="true" aria-hidden="true"
className="nav__item-header-triangle" classname="nav__item-header-triangle"
focusable="false" focusable="false"
/> />
</button> </button>
<ul <ul
className="nav__dropdown" class="nav__dropdown"
> >
<li <li
className="nav__dropdown-item" class="nav__dropdown-item"
>
<button
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
> >
<button>
New New
</button> </button>
</li> </li>
<li <li
className="nav__dropdown-item" class="nav__dropdown-item"
>
<button
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
> >
<button>
Duplicate Duplicate
</button> </button>
</li> </li>
<li <li
className="nav__dropdown-item" class="nav__dropdown-item"
>
<button
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
> >
<button>
Share Share
</button> </button>
</li> </li>
<li <li
className="nav__dropdown-item" class="nav__dropdown-item"
>
<button
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
> >
<button>
Download Download
</button> </button>
</li> </li>
<li <li
className="nav__dropdown-item" class="nav__dropdown-item"
>
<a
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
style={Object {}}
> >
<a>
Open Open
</a> </a>
</li> </li>
</ul> </ul>
</li> </li>
<li <li
className="nav__item" class="nav__item"
>
<button
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOver={[Function]}
> >
<button>
<span <span
className="nav__item-header" class="nav__item-header"
> >
Edit Edit
</span> </span>
<test-file-stub <test-file-stub
aria-hidden="true" aria-hidden="true"
className="nav__item-header-triangle" classname="nav__item-header-triangle"
focusable="false" focusable="false"
/> />
</button> </button>
<ul <ul
className="nav__dropdown" class="nav__dropdown"
> >
<li <li
className="nav__dropdown-item" class="nav__dropdown-item"
>
<button
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
> >
<button>
Tidy Code Tidy Code
<span <span
className="nav__keyboard-shortcut" class="nav__keyboard-shortcut"
> >
⇧+Tab
+Tab
</span> </span>
</button> </button>
</li> </li>
<li <li
className="nav__dropdown-item" class="nav__dropdown-item"
>
<button
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
> >
<button>
Find Find
<span <span
className="nav__keyboard-shortcut" class="nav__keyboard-shortcut"
> >
⌃+F
+F
</span> </span>
</button> </button>
</li> </li>
<li <li
className="nav__dropdown-item" class="nav__dropdown-item"
>
<button
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
> >
<button>
Find Next Find Next
<span <span
className="nav__keyboard-shortcut" class="nav__keyboard-shortcut"
> >
⌃+G
+G
</span> </span>
</button> </button>
</li> </li>
<li <li
className="nav__dropdown-item" class="nav__dropdown-item"
>
<button
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
> >
<button>
Find Previous Find Previous
<span <span
className="nav__keyboard-shortcut" class="nav__keyboard-shortcut"
> >
⇧+⌃+G
+
+G
</span> </span>
</button> </button>
</li> </li>
</ul> </ul>
</li> </li>
<li <li
className="nav__item" class="nav__item"
>
<button
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOver={[Function]}
> >
<button>
<span <span
className="nav__item-header" class="nav__item-header"
> >
Sketch Sketch
</span> </span>
<test-file-stub <test-file-stub
aria-hidden="true" aria-hidden="true"
className="nav__item-header-triangle" classname="nav__item-header-triangle"
focusable="false" focusable="false"
/> />
</button> </button>
<ul <ul
className="nav__dropdown" class="nav__dropdown"
> >
<li <li
className="nav__dropdown-item" class="nav__dropdown-item"
>
<button
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
> >
<button>
Add File Add File
</button> </button>
</li> </li>
<li <li
className="nav__dropdown-item" class="nav__dropdown-item"
>
<button
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
> >
<button>
Add Folder Add Folder
</button> </button>
</li> </li>
<li <li
className="nav__dropdown-item" class="nav__dropdown-item"
>
<button
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
> >
<button>
Run Run
<span <span
className="nav__keyboard-shortcut" class="nav__keyboard-shortcut"
> >
⌃+Enter
+Enter
</span> </span>
</button> </button>
</li> </li>
<li <li
className="nav__dropdown-item" class="nav__dropdown-item"
>
<button
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
> >
<button>
Stop Stop
<span <span
className="nav__keyboard-shortcut" class="nav__keyboard-shortcut"
> >
⇧+⌃+Enter
+
+Enter
</span> </span>
</button> </button>
</li> </li>
</ul> </ul>
</li> </li>
<li <li
className="nav__item" class="nav__item"
>
<button
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOver={[Function]}
> >
<button>
<span <span
className="nav__item-header" class="nav__item-header"
> >
Help Help
</span> </span>
<test-file-stub <test-file-stub
aria-hidden="true" aria-hidden="true"
className="nav__item-header-triangle" classname="nav__item-header-triangle"
focusable="false" focusable="false"
/> />
</button> </button>
<ul <ul
className="nav__dropdown" class="nav__dropdown"
> >
<li <li
className="nav__dropdown-item" class="nav__dropdown-item"
>
<button
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
> >
<button>
Keyboard Shortcuts Keyboard Shortcuts
</button> </button>
</li> </li>
<li <li
className="nav__dropdown-item" class="nav__dropdown-item"
> >
<a <a
href="https://p5js.org/reference/" href="https://p5js.org/reference/"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
rel="noopener noreferrer" rel="noopener noreferrer"
target="_blank" target="_blank"
> >
@ -327,14 +238,9 @@ exports[`Nav renders correctly 1`] = `
</a> </a>
</li> </li>
<li <li
className="nav__dropdown-item" class="nav__dropdown-item"
>
<a
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
style={Object {}}
> >
<a>
About About
</a> </a>
</li> </li>
@ -342,5 +248,6 @@ exports[`Nav renders correctly 1`] = `
</li> </li>
</ul> </ul>
</nav> </nav>
</header> </header>
</DocumentFragment>
`; `;

View file

@ -1,8 +1,5 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import { configure } from 'enzyme';
// eslint-disable-next-line import/no-extraneous-dependencies
import Adapter from 'enzyme-adapter-react-16';
import '@babel/polyfill'; import '@babel/polyfill';
configure({ adapter: new Adapter() }); // See: https://github.com/testing-library/jest-dom
// eslint-disable-next-line import/no-extraneous-dependencies
import '@testing-library/jest-dom';

View file

@ -206,12 +206,14 @@ export class FileNode extends React.Component {
</div> </div>
} }
<button <button
aria-label="Name"
className="sidebar__file-item-name" className="sidebar__file-item-name"
onClick={this.handleFileClick} onClick={this.handleFileClick}
> >
{this.state.updatedName} {this.state.updatedName}
</button> </button>
<input <input
data-testid="input"
type="text" type="text"
className="sidebar__file-item-input" className="sidebar__file-item-input"
value={this.state.updatedName} value={this.state.updatedName}

View file

@ -0,0 +1,31 @@
import React from 'react';
import { action } from '@storybook/addon-actions';
import { FileNode } from './FileNode';
export default {
title: 'IDE/FileNode',
component: FileNode
};
export const Show = () => (
<FileNode
id="nodeId"
parantId="parentId"
name="File name"
fileType="jpeg"
isSelectedFile
isFolderClosed={false}
setSelectedFile={action('setSelectedFile')}
deleteFile={action('deleteFile')}
updateFileName={action('updateFileName')}
resetSelectedFile={action('resetSelectedFile')}
newFile={action('newFile')}
newFolder={action('newFolder')}
showFolderChildren={action('showFolderChildren')}
hideFolderChildren={action('hideFolderChildren')}
openUploadFileModal={action('openUploadFileModal')}
canEdit
authenticated
/>
);

View file

@ -0,0 +1,127 @@
import React from 'react';
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react';
import { FileNode } from './FileNode';
describe('<FileNode />', () => {
const changeName = (newFileName) => {
const renameButton = screen.getByText(/Rename/i);
fireEvent.click(renameButton);
const input = screen.getByTestId('input');
fireEvent.change(input, { target: { value: newFileName } });
fireEvent.blur(input);
};
const expectFileNameToBe = async (expectedName) => {
const name = screen.getByLabelText(/Name/i);
await waitFor(() => within(name).queryByText(expectedName));
};
const renderFileNode = (fileType, extraProps = {}) => {
const props = {
...extraProps,
id: '0',
name: fileType === 'folder' ? 'afolder' : 'test.jsx',
fileType,
canEdit: true,
children: [],
authenticated: false,
setSelectedFile: jest.fn(),
deleteFile: jest.fn(),
updateFileName: jest.fn(),
resetSelectedFile: jest.fn(),
newFile: jest.fn(),
newFolder: jest.fn(),
showFolderChildren: jest.fn(),
hideFolderChildren: jest.fn(),
openUploadFileModal: jest.fn(),
setProjectName: jest.fn(),
};
render(<FileNode {...props} />);
return props;
};
describe('fileType: file', () => {
it('cannot change to an empty name', async () => {
const props = renderFileNode('file');
changeName('');
await waitFor(() => expect(props.updateFileName).not.toHaveBeenCalled());
await expectFileNameToBe(props.name);
});
it('can change to a valid filename', async () => {
const newName = 'newname.jsx';
const props = renderFileNode('file');
changeName(newName);
await waitFor(() => expect(props.updateFileName).toHaveBeenCalledWith(props.id, newName));
await expectFileNameToBe(newName);
});
it('must have an extension', async () => {
const newName = 'newname';
const props = renderFileNode('file');
changeName(newName);
await waitFor(() => expect(props.updateFileName).not.toHaveBeenCalled());
await expectFileNameToBe(props.name);
});
it('can change to a different extension', async () => {
const newName = 'newname.gif';
const props = renderFileNode('file');
changeName(newName);
await waitFor(() => expect(props.updateFileName).not.toHaveBeenCalled());
await expectFileNameToBe(props.name);
});
it('cannot be just an extension', async () => {
const newName = '.jsx';
const props = renderFileNode('file');
changeName(newName);
await waitFor(() => expect(props.updateFileName).not.toHaveBeenCalled());
await expectFileNameToBe(props.name);
});
});
describe('fileType: folder', () => {
it('cannot change to an empty name', async () => {
const props = renderFileNode('folder');
changeName('');
await waitFor(() => expect(props.updateFileName).not.toHaveBeenCalled());
await expectFileNameToBe(props.name);
});
it('can change to another name', async () => {
const newName = 'foldername';
const props = renderFileNode('folder');
changeName(newName);
await waitFor(() => expect(props.updateFileName).toHaveBeenCalledWith(props.id, newName));
await expectFileNameToBe(newName);
});
it('cannot have a file extension', async () => {
const newName = 'foldername.jsx';
const props = renderFileNode('folder');
changeName(newName);
await waitFor(() => expect(props.updateFileName).not.toHaveBeenCalled());
await expectFileNameToBe(props.name);
});
});
});

View file

@ -17,21 +17,36 @@ class Toolbar extends React.Component {
super(props); super(props);
this.handleKeyPress = this.handleKeyPress.bind(this); this.handleKeyPress = this.handleKeyPress.bind(this);
this.handleProjectNameChange = this.handleProjectNameChange.bind(this); this.handleProjectNameChange = this.handleProjectNameChange.bind(this);
this.handleProjectNameSave = this.handleProjectNameSave.bind(this);
this.state = {
projectNameInputValue: props.project.name,
};
} }
handleKeyPress(event) { handleKeyPress(event) {
if (event.key === 'Enter') { if (event.key === 'Enter') {
this.props.hideEditProjectName(); this.props.hideEditProjectName();
this.projectNameInput.blur();
} }
} }
handleProjectNameChange(event) { handleProjectNameChange(event) {
this.props.setProjectName(event.target.value); this.setState({ projectNameInputValue: event.target.value });
} }
validateProjectName() { handleProjectNameSave() {
if ((this.props.project.name.trim()).length === 0) { const newProjectName = this.state.projectNameInputValue.trim();
this.props.setProjectName(this.originalProjectName); if (newProjectName.length === 0) {
this.setState({
projectNameInputValue: this.props.project.name,
});
} else {
this.props.setProjectName(newProjectName);
this.props.hideEditProjectName();
if (this.props.project.id) {
this.props.saveProject();
}
} }
} }
@ -108,7 +123,6 @@ class Toolbar extends React.Component {
className="toolbar__project-name" className="toolbar__project-name"
onClick={() => { onClick={() => {
if (canEditProjectName) { if (canEditProjectName) {
this.originalProjectName = this.props.project.name;
this.props.showEditProjectName(); this.props.showEditProjectName();
setTimeout(() => this.projectNameInput.focus(), 0); setTimeout(() => this.projectNameInput.focus(), 0);
} }
@ -130,16 +144,11 @@ class Toolbar extends React.Component {
type="text" type="text"
maxLength="128" maxLength="128"
className="toolbar__project-name-input" className="toolbar__project-name-input"
value={this.props.project.name} aria-label="New sketch name"
value={this.state.projectNameInputValue}
onChange={this.handleProjectNameChange} onChange={this.handleProjectNameChange}
ref={(element) => { this.projectNameInput = element; }} ref={(element) => { this.projectNameInput = element; }}
onBlur={() => { onBlur={this.handleProjectNameSave}
this.validateProjectName();
this.props.hideEditProjectName();
if (this.props.project.id) {
this.props.saveProject();
}
}}
onKeyPress={this.handleKeyPress} onKeyPress={this.handleKeyPress}
/> />
{(() => { // eslint-disable-line {(() => { // eslint-disable-line

View file

@ -0,0 +1,84 @@
import React from 'react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import lodash from 'lodash';
import { ToolbarComponent } from './Toolbar';
const renderComponent = (extraProps = {}) => {
const props = lodash.merge({
isPlaying: false,
preferencesIsVisible: false,
stopSketch: jest.fn(),
setProjectName: jest.fn(),
openPreferences: jest.fn(),
showEditProjectName: jest.fn(),
hideEditProjectName: jest.fn(),
infiniteLoop: false,
autorefresh: false,
setAutorefresh: jest.fn(),
setTextOutput: jest.fn(),
setGridOutput: jest.fn(),
startSketch: jest.fn(),
startAccessibleSketch: jest.fn(),
saveProject: jest.fn(),
currentUser: 'me',
originalProjectName: 'testname',
owner: {
username: 'me'
},
project: {
name: 'testname',
isEditingName: false,
id: 'id',
},
}, extraProps);
render(<ToolbarComponent {...props} />);
return props;
};
describe('<ToolbarComponent />', () => {
it('sketch owner can switch to sketch name editing mode', async () => {
const props = renderComponent();
const sketchName = screen.getByLabelText('Edit sketch name');
fireEvent.click(sketchName);
await waitFor(() => expect(props.showEditProjectName).toHaveBeenCalled());
});
it('non-owner can\t switch to sketch editing mode', async () => {
const props = renderComponent({ currentUser: 'not-me' });
const sketchName = screen.getByLabelText('Edit sketch name');
fireEvent.click(sketchName);
expect(sketchName).toBeDisabled();
await waitFor(() => expect(props.showEditProjectName).not.toHaveBeenCalled());
});
it('sketch owner can change name', async () => {
const props = renderComponent({ project: { isEditingName: true } });
const sketchNameInput = screen.getByLabelText('New sketch name');
fireEvent.change(sketchNameInput, { target: { value: 'my new sketch name' } });
fireEvent.blur(sketchNameInput);
await waitFor(() => expect(props.setProjectName).toHaveBeenCalledWith('my new sketch name'));
await waitFor(() => expect(props.saveProject).toHaveBeenCalled());
});
it('sketch owner can\'t change to empty name', async () => {
const props = renderComponent({ project: { isEditingName: true } });
const sketchNameInput = screen.getByLabelText('New sketch name');
fireEvent.change(sketchNameInput, { target: { value: '' } });
fireEvent.blur(sketchNameInput);
await waitFor(() => expect(props.setProjectName).not.toHaveBeenCalled());
await waitFor(() => expect(props.saveProject).not.toHaveBeenCalled());
});
});

View file

@ -215,7 +215,7 @@ class IDEView extends React.Component {
warnIfUnsavedChanges={this.warnIfUnsavedChanges} warnIfUnsavedChanges={this.warnIfUnsavedChanges}
cmController={this.cmController} cmController={this.cmController}
/> />
<Toolbar /> <Toolbar key={this.props.project.id} />
{this.props.ide.preferencesIsVisible && {this.props.ide.preferencesIsVisible &&
<Overlay <Overlay
title="Settings" title="Settings"

944
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -99,14 +99,14 @@
"@storybook/addons": "^5.3.6", "@storybook/addons": "^5.3.6",
"@storybook/react": "^5.3.6", "@storybook/react": "^5.3.6",
"@svgr/webpack": "^5.4.0", "@svgr/webpack": "^5.4.0",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.2.1",
"babel-core": "^7.0.0-bridge.0", "babel-core": "^7.0.0-bridge.0",
"babel-eslint": "^9.0.0", "babel-eslint": "^9.0.0",
"babel-loader": "^8.0.0", "babel-loader": "^8.0.0",
"babel-plugin-transform-react-remove-prop-types": "^0.2.12", "babel-plugin-transform-react-remove-prop-types": "^0.2.12",
"css-loader": "^3.4.2", "css-loader": "^3.4.2",
"cssnano": "^4.1.10", "cssnano": "^4.1.10",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.2",
"eslint": "^4.19.1", "eslint": "^4.19.1",
"eslint-config-airbnb": "^16.1.0", "eslint-config-airbnb": "^16.1.0",
"eslint-plugin-import": "^2.20.2", "eslint-plugin-import": "^2.20.2",