Skip to content

Commit

Permalink
Merge pull request #1 from InsidersByte/feature/image_drop
Browse files Browse the repository at this point in the history
Feature/image drop
  • Loading branch information
Jonathon Kelly committed Apr 9, 2016
2 parents fa30c39 + 52bddb7 commit 62a7747
Show file tree
Hide file tree
Showing 7 changed files with 410 additions and 50 deletions.
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

"rules": {
"indent": [2, 4, {"SwitchCase": 1}],
"max-len": [2, 120],
"react/jsx-indent-props": [2, 4]
}
}
59 changes: 39 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

[![NPM](https://nodei.co/npm/@insidersbyte/react-markdown-editor.png?downloads=true&downloadRank=true)](https://nodei.co/npm/@insidersbyte/react-markdown-editor/)

[React](http://facebook.github.io/react) Markdown editor, built with [react-markdown-renderer](https://github.com/insidersbyte/react-markdown-renderer).
[React](http://facebook.github.io/react) Markdown editor with preview and drag and drop image support (at the moment it always adds the image to the end, regardless of where you drop it), built with [react-markdown-renderer](https://github.com/insidersbyte/react-markdown-renderer).

## Demo
http://insidersbyte.github.io/react-markdown-editor
Expand All @@ -31,24 +31,42 @@ import '@insidersbyte/react-markdown-editor/dist/css/react-markdown-editor.css';

class App extends React.Component {
constructor() {
super();

this.state = {
markdown: '# This is a H1 \n## This is a H2 \n###### This is a H6',
};

this.updateMarkdown = this.updateMarkdown.bind(this);
}

updateMarkdown(event) {
this.setState({ markdown: event.target.value });
}

render() {
return (
<MarkdownEditor value={this.state.markdown} onChange={this.updateMarkdown} />
);
}
super();

this.state = {
markdown: '# This is a H1 \n## This is a H2 \n###### This is a H6',
};

this.updateMarkdown = this.updateMarkdown.bind(this);
}

onImageDrop(file) {
// This is where you would upload your files to whatever storage you are using
// You just need to return a promise with the original filename and the url of the uploaded file

return new Promise((resolve) => {
setTimeout(() => {
resolve({
filename: file.name,
url: 'http://images.freeimages.com/images/previews/b56/hands-2-ok-hand-1241594.jpg',
});
}, 3000);
});
}

updateMarkdown(event) {
this.setState({ markdown: event.target.value });
}

render() {
return (
<MarkdownEditor
value={this.state.markdown}
onChange={this.updateMarkdown}
onImageDrop={this.onImageDrop}
/>
);
}
}

ReactDOM.render(<App />, document.getElementById('app'));
Expand All @@ -57,7 +75,8 @@ ReactDOM.render(<App />, document.getElementById('app'));
## Props

* value (*string*) - the raw markdown that will be converted to html (**required**)
* onChange (*function*) - css classes to add to the component (**required**).
* onChange (*function*) - called when a change is made (**required**)
* onImageDrop (*function*) - called per image dropped on the textarea

## Contributing

Expand Down
249 changes: 244 additions & 5 deletions __tests__/src/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,257 @@ import ReactDOM from 'react-dom';
import TestUtils from 'react-addons-test-utils';
import MarkdownEditor from '../../src';

const value = '';
const onChangeMock = jest.genMockFunction();
const onImageDropMock = jest.genMockFunction();

const validFile = [{ type: 'image/jpeg', name: 'image' }];
const invalidFile = [{ type: 'text/markdown', name: 'markdown' }];
const uploadedImage = { url: 'url', filename: 'image' };

describe('MarkdownEditor', () => {
beforeEach(() => {
onChangeMock.mockClear();
onImageDropMock.mockClear();
});

it('renders', () => {
const markdownEditor = TestUtils.renderIntoDocument(
<div>
<MarkdownEditor />
</div>
<MarkdownEditor value={value} onChange={onChangeMock} />
);

const containerNode = ReactDOM.findDOMNode(markdownEditor);
const markdownEditorNode = containerNode.children[0];
const markdownEditorNode = ReactDOM.findDOMNode(markdownEditor);

expect(markdownEditorNode).toBeDefined();
expect(markdownEditorNode).not.toBe(null);

expect(markdownEditor.state.draggingOver).toBe(false);
});

describe('onDragEnter', () => {
it('sets draggingOver to true', () => {
const markdownEditor = TestUtils.renderIntoDocument(
<MarkdownEditor value={value} onChange={onChangeMock} />
);

expect(markdownEditor.state.draggingOver).toBe(false);

TestUtils.Simulate.dragEnter(
TestUtils.findRenderedDOMComponentWithTag(markdownEditor, 'TextArea')
);

expect(markdownEditor.state.draggingOver).toBe(true);
});
});

describe('onDragLeave', () => {
it('sets draggingOver to false', () => {
const markdownEditor = TestUtils.renderIntoDocument(
<MarkdownEditor value={value} onChange={onChangeMock} />
);

markdownEditor.setState({ draggingOver: true });

TestUtils.Simulate.dragLeave(
TestUtils.findRenderedDOMComponentWithTag(markdownEditor, 'TextArea')
);

expect(markdownEditor.state.draggingOver).toBe(false);
});
});

describe('onDrop', () => {
it('sets draggingOver to false', () => {
const markdownEditor = TestUtils.renderIntoDocument(
<MarkdownEditor value={value} onChange={onChangeMock} />
);

markdownEditor.setState({ draggingOver: true });

TestUtils.Simulate.drop(
TestUtils.findRenderedDOMComponentWithTag(markdownEditor, 'TextArea')
);

expect(markdownEditor.state.draggingOver).toBe(false);
});

describe('valid images, but no onImageDrop prop passed', () => {
it('does not call onChange', () => {
const markdownEditor = TestUtils.renderIntoDocument(
<MarkdownEditor value={value} onChange={onChangeMock} />
);

expect(onChangeMock.mock.calls.length).toBe(0);

TestUtils.Simulate.drop(
TestUtils.findRenderedDOMComponentWithTag(markdownEditor, 'TextArea'),
{ dataTransfer: { files: validFile } }
);

expect(onChangeMock.mock.calls.length).toBe(0);
});
});

describe('onImageDrop prop passed', () => {
describe('no files passed', () => {
it('does not call onChange or onImageDrop', () => {
const markdownEditor = TestUtils.renderIntoDocument(
<MarkdownEditor
value={value}
onChange={onChangeMock}
onImageDrop={onImageDropMock}
/>
);

expect(onChangeMock.mock.calls.length).toBe(0);
expect(onImageDropMock.mock.calls.length).toBe(0);

TestUtils.Simulate.drop(
TestUtils.findRenderedDOMComponentWithTag(markdownEditor, 'TextArea'),
{ dataTransfer: { files: [] } }
);

expect(onChangeMock.mock.calls.length).toBe(0);
expect(onImageDropMock.mock.calls.length).toBe(0);
});
});

describe('no images passed', () => {
it('does not call onChange or onImageDrop', () => {
const markdownEditor = TestUtils.renderIntoDocument(
<MarkdownEditor
value={value}
onChange={onChangeMock}
onImageDrop={onImageDropMock}
/>
);

expect(onChangeMock.mock.calls.length).toBe(0);
expect(onImageDropMock.mock.calls.length).toBe(0);

TestUtils.Simulate.drop(
TestUtils.findRenderedDOMComponentWithTag(markdownEditor, 'TextArea'),
{ dataTransfer: { files: invalidFile } }
);

expect(onChangeMock.mock.calls.length).toBe(0);
expect(onImageDropMock.mock.calls.length).toBe(0);
});
});

describe('invalid response from onImageDrop', () => {
describe('no filename', () => {
it('does not call onChange twice', () => {
const markdownEditor = TestUtils.renderIntoDocument(
<MarkdownEditor
value={value}
onChange={onChangeMock}
onImageDrop={onImageDropMock}
/>
);

expect(onChangeMock.mock.calls.length).toBe(0);
expect(onImageDropMock.mock.calls.length).toBe(0);

onImageDropMock.mockReturnValueOnce({ url: 'url' });

TestUtils.Simulate.drop(
TestUtils.findRenderedDOMComponentWithTag(markdownEditor, 'TextArea'),
{ dataTransfer: { files: validFile } }
);

expect(onChangeMock.mock.calls.length).toBe(1);
expect(onChangeMock).toBeCalledWith({ target: { value: '\n![uploading image...]()' } });

expect(onImageDropMock.mock.calls.length).toBe(1);
expect(onImageDropMock).toBeCalledWith(validFile[0]);
});
});

describe('no url', () => {
it('does not call onChange twice', () => {
const markdownEditor = TestUtils.renderIntoDocument(
<MarkdownEditor
value={value}
onChange={onChangeMock}
onImageDrop={onImageDropMock}
/>
);

expect(onChangeMock.mock.calls.length).toBe(0);
expect(onImageDropMock.mock.calls.length).toBe(0);

onImageDropMock.mockReturnValueOnce({ filename: 'filename' });

TestUtils.Simulate.drop(
TestUtils.findRenderedDOMComponentWithTag(markdownEditor, 'TextArea'),
{ dataTransfer: { files: validFile } }
);

expect(onChangeMock.mock.calls.length).toBe(1);
expect(onChangeMock).toBeCalledWith({ target: { value: '\n![uploading image...]()' } });

expect(onImageDropMock.mock.calls.length).toBe(1);
expect(onImageDropMock).toBeCalledWith(validFile[0]);
});
});
});

describe('one image passed', () => {
it('calls onChange and onImageDrop', () => {
const markdownEditor = TestUtils.renderIntoDocument(
<MarkdownEditor
value={value}
onChange={onChangeMock}
onImageDrop={onImageDropMock}
/>
);

expect(onChangeMock.mock.calls.length).toBe(0);
expect(onImageDropMock.mock.calls.length).toBe(0);

onImageDropMock.mockReturnValueOnce(uploadedImage);

TestUtils.Simulate.drop(
TestUtils.findRenderedDOMComponentWithTag(markdownEditor, 'TextArea'),
{ dataTransfer: { files: validFile } }
);

expect(onChangeMock.mock.calls.length).toBe(1);
expect(onChangeMock).toBeCalledWith({ target: { value: '\n![uploading image...]()' } });

expect(onImageDropMock.mock.calls.length).toBe(1);
expect(onImageDropMock).toBeCalledWith(validFile[0]);
});
});

describe('one image passed with event.target.files', () => {
it('calls onChange and onImageDrop', () => {
const markdownEditor = TestUtils.renderIntoDocument(
<MarkdownEditor
value={value}
onChange={onChangeMock}
onImageDrop={onImageDropMock}
/>
);

expect(onChangeMock.mock.calls.length).toBe(0);
expect(onImageDropMock.mock.calls.length).toBe(0);

onImageDropMock.mockReturnValueOnce(uploadedImage);

TestUtils.Simulate.drop(
TestUtils.findRenderedDOMComponentWithTag(markdownEditor, 'TextArea'),
{ target: { files: validFile } }
);

expect(onChangeMock.mock.calls.length).toBe(1);
expect(onChangeMock).toBeCalledWith({ target: { value: '\n![uploading image...]()' } });

expect(onImageDropMock.mock.calls.length).toBe(1);
expect(onImageDropMock).toBeCalledWith(validFile[0]);
});
});
});
});
});
Loading

0 comments on commit 62a7747

Please sign in to comment.