From b7a09c30fa256a95040cb95c14ea0ccc1a8d7a5a Mon Sep 17 00:00:00 2001 From: Thomas Hudspith-Tatham Date: Sat, 15 Dec 2018 20:58:17 +0000 Subject: [PATCH 01/15] refactors from prop drilling to use context --- src/AddFolder/AddFolder.js | 1 + src/AddNote/AddNote.js | 8 +-- src/AddNote/AddNote.test.js | 6 +- src/ApiContext.js | 6 ++ src/App/App.js | 91 ++++++++----------------- src/NoteListMain/NoteListMain.js | 72 +++++++++++--------- src/NoteListMain/NoteListMain.test.js | 77 +++++++++++---------- src/NoteListNav/NoteListNav.js | 72 ++++++++++---------- src/NoteListNav/NoteListNav.test.js | 98 +++++++++++++-------------- src/NotePageMain/NotePageMain.js | 46 +++++++------ src/NotePageMain/NotePageMain.test.js | 69 +++++++++++++------ src/NotePageNav/NotePageNav.js | 60 +++++++++------- src/NotePageNav/NotePageNav.test.js | 25 ++++--- 13 files changed, 340 insertions(+), 291 deletions(-) create mode 100644 src/ApiContext.js diff --git a/src/AddFolder/AddFolder.js b/src/AddFolder/AddFolder.js index 0307acb27..634d6ec84 100644 --- a/src/AddFolder/AddFolder.js +++ b/src/AddFolder/AddFolder.js @@ -1,5 +1,6 @@ import React, { Component } from 'react' import NotefulForm from '../NotefulForm/NotefulForm' +// import ApiContext from '../ApiContext' import './AddFolder.css' export default class AddFolder extends Component { diff --git a/src/AddNote/AddNote.js b/src/AddNote/AddNote.js index a1b17e422..2778a74c8 100644 --- a/src/AddNote/AddNote.js +++ b/src/AddNote/AddNote.js @@ -1,13 +1,13 @@ import React, { Component } from 'react' import NotefulForm from '../NotefulForm/NotefulForm' +import ApiContext from '../ApiContext' import './AddNote.css' export default class AddNote extends Component { - static defaultProps = { - folders: [], - } + static contextType = ApiContext; + render() { - const { folders } = this.props + const { folders=[] } = this.context return (

Create a note

diff --git a/src/AddNote/AddNote.test.js b/src/AddNote/AddNote.test.js index 2412e7c2a..ed50d58db 100644 --- a/src/AddNote/AddNote.test.js +++ b/src/AddNote/AddNote.test.js @@ -24,8 +24,10 @@ describe(`AddNote component`, () => { expect(toJson(wrapper)).toMatchSnapshot() }) - it('renders the select options from folders', () => { - const select = shallow() + // enzyme doesn't support React.createContext + it.skip('renders the select options from folders', () => { + const context = { folders: stubFolders } + const select = shallow(, context) .find('#note-folder-select') expect(toJson(select)).toMatchSnapshot() }) diff --git a/src/ApiContext.js b/src/ApiContext.js new file mode 100644 index 000000000..18b52a861 --- /dev/null +++ b/src/ApiContext.js @@ -0,0 +1,6 @@ +import React from 'react' + +export default React.createContext({ + notes: [], + folders: [], +}) diff --git a/src/App/App.js b/src/App/App.js index adc3ba283..2940a50a8 100644 --- a/src/App/App.js +++ b/src/App/App.js @@ -7,8 +7,8 @@ import NoteListMain from '../NoteListMain/NoteListMain' import NotePageMain from '../NotePageMain/NotePageMain' import AddFolder from '../AddFolder/AddFolder' import AddNote from '../AddNote/AddNote' +import ApiContext from '../ApiContext' import dummyStore from '../dummy-store' -import { getNotesForFolder, findNote, findFolder } from '../notes-helpers' import './App.css' class App extends Component { @@ -23,7 +23,6 @@ class App extends Component { } renderNavRoutes() { - const { notes, folders } = this.state return ( <> {['/', '/folder/:folderId'].map(path => @@ -31,28 +30,12 @@ class App extends Component { exact key={path} path={path} - render={routeProps => - - } + component={NoteListNav} /> )} { - const { noteId } = routeProps.match.params - const note = findNote(notes, noteId) || {} - const folder = findFolder(folders, note.folderId) - return ( - - ) - }} + component={NotePageNav} /> {['/', '/folder/:folderId'].map(path => @@ -75,30 +57,12 @@ class App extends Component { exact key={path} path={path} - render={routeProps => { - const { folderId } = routeProps.match.params - const notesForFolder = getNotesForFolder(notes, folderId) - return ( - - ) - }} + component={NoteListMain} /> )} { - const { noteId } = routeProps.match.params - const note = findNote(notes, noteId) - return ( - - ) - }} + component={NotePageMain} /> { - return ( - - ) - }} + component={AddNote} /> ) } render() { + const value = { + notes: this.state.notes, + folders: this.state.folders, + } return ( -
- -
-

- Noteful - {' '} - -

-
-
- {this.renderMainRoutes()} -
-
+ +
+ +
+

+ Noteful + {' '} + +

+
+
+ {this.renderMainRoutes()} +
+
+
) } } diff --git a/src/NoteListMain/NoteListMain.js b/src/NoteListMain/NoteListMain.js index f0019206c..f20111dde 100644 --- a/src/NoteListMain/NoteListMain.js +++ b/src/NoteListMain/NoteListMain.js @@ -3,38 +3,48 @@ import { Link } from 'react-router-dom' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import Note from '../Note/Note' import CircleButton from '../CircleButton/CircleButton' +import ApiContext from '../ApiContext' +import { getNotesForFolder } from '../notes-helpers' import './NoteListMain.css' -export default function NoteListMain(props) { - return ( -
-
    - {props.notes.map(note => -
  • - -
  • - )} -
-
- - -
- Note -
-
-
- ) -} +export default class NoteListMain extends React.Component { + static defaultProps = { + match: { + params: {} + } + } + static contextType = ApiContext -NoteListMain.defaultProps = { - notes: [], + render() { + const { folderId } = this.props.match.params + const { notes=[] } = this.context + const notesForFolder = getNotesForFolder(notes, folderId) + return ( +
+
    + {notesForFolder.map(note => +
  • + +
  • + )} +
+
+ + +
+ Note +
+
+
+ ) + } } diff --git a/src/NoteListMain/NoteListMain.test.js b/src/NoteListMain/NoteListMain.test.js index b82066585..3ed1bd752 100644 --- a/src/NoteListMain/NoteListMain.test.js +++ b/src/NoteListMain/NoteListMain.test.js @@ -4,46 +4,53 @@ import toJson from 'enzyme-to-json' import NoteListMain from './NoteListMain' describe(`NoteListMain component`, () => { - const props = { - notes: [ - { - "id": "cbc787a0-ffaf-11e8-8eb2-f2801f1b9fd1", - "name": "Dogs", - "modified": "2019-01-03T00:00:00.000Z", - "folderId": "b0715efe-ffaf-11e8-8eb2-f2801f1b9fd1", - "content": "Corporis accusamus placeat.\n \rUnde." - }, - { - "id": "d26e0034-ffaf-11e8-8eb2-f2801f1b9fd1", - "name": "Cats", - "modified": "2018-08-15T23:00:00.000Z", - "folderId": "b07161a6-ffaf-11e8-8eb2-f2801f1b9fd1", - "content": "Eos\n \rlaudantium." - }, - { - "id": "d26e01a6-ffaf-11e8-8eb2-f2801f1b9fd1", - "name": "Pigs", - "modified": "2018-03-01T00:00:00.000Z", - "folderId": "b07161a6-ffaf-11e8-8eb2-f2801f1b9fd1", - "content": "Occaecati dignissimos\nvoluptatum nihil." - }, - { - "id": "d26e0570-ffaf-11e8-8eb2-f2801f1b9fd1", - "name": "Birds", - "modified": "2019-01-04T00:00:00.000Z", - "folderId": "b0715efe-ffaf-11e8-8eb2-f2801f1b9fd1", - "content": "Eum culpa odit." - }, - ] - } - it('renders a .NoteListMain by default', () => { const wrapper = shallow() expect(toJson(wrapper)).toMatchSnapshot() }) - it('renders a Note in ul for each notes in array', () => { - const ul = shallow() + // enzyme doesn't yet support React.createContext + it.skip('renders a Note in ul for each notes in array', () => { + const props = { + match: { + params: { + folderId: 'THIS_FOLDER_ID' + } + } + } + const context = { + notes: [ + { + "id": "cbc787a0-ffaf-11e8-8eb2-f2801f1b9fd1", + "name": "Dogs", + "modified": "2019-01-03T00:00:00.000Z", + "folderId": "b0715efe-ffaf-11e8-8eb2-f2801f1b9fd1", + "content": "Corporis accusamus placeat.\n \rUnde." + }, + { + "id": "d26e0034-ffaf-11e8-8eb2-f2801f1b9fd1", + "name": "Cats", + "modified": "2018-08-15T23:00:00.000Z", + "folderId": "THIS_FOLDER_ID", + "content": "Eos\n \rlaudantium." + }, + { + "id": "d26e01a6-ffaf-11e8-8eb2-f2801f1b9fd1", + "name": "Pigs", + "modified": "2018-03-01T00:00:00.000Z", + "folderId": "THIS_FOLDER_ID", + "content": "Occaecati dignissimos\nvoluptatum nihil." + }, + { + "id": "d26e0570-ffaf-11e8-8eb2-f2801f1b9fd1", + "name": "Birds", + "modified": "2019-01-04T00:00:00.000Z", + "folderId": "b0715efe-ffaf-11e8-8eb2-f2801f1b9fd1", + "content": "Eum culpa odit." + }, + ] + } + const ul = shallow(, context) .find('ul') expect(toJson(ul)).toMatchSnapshot() }) diff --git a/src/NoteListNav/NoteListNav.js b/src/NoteListNav/NoteListNav.js index 3d2ee8956..ca44c187b 100644 --- a/src/NoteListNav/NoteListNav.js +++ b/src/NoteListNav/NoteListNav.js @@ -2,43 +2,45 @@ import React from 'react' import { NavLink, Link } from 'react-router-dom' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import CircleButton from '../CircleButton/CircleButton' +import ApiContext from '../ApiContext' import { countNotesForFolder } from '../notes-helpers' import './NoteListNav.css' -export default function NoteListNav(props) { - return ( -
-
    - {props.folders.map(folder => -
  • - - - {countNotesForFolder(props.notes, folder.id)} - - {folder.name} - -
  • - )} -
-
- - -
- Folder -
-
-
- ) -} +export default class NoteListNav extends React.Component { + static contextType = ApiContext; -NoteListNav.defaultProps = { - folders: [] + render() { + const { folders=[], notes=[] } = this.context + return ( +
+
    + {folders.map(folder => +
  • + + + {countNotesForFolder(notes, folder.id)} + + {folder.name} + +
  • + )} +
+
+ + +
+ Folder +
+
+
+ ) + } } diff --git a/src/NoteListNav/NoteListNav.test.js b/src/NoteListNav/NoteListNav.test.js index 0d595a9a0..ad848112d 100644 --- a/src/NoteListNav/NoteListNav.test.js +++ b/src/NoteListNav/NoteListNav.test.js @@ -4,60 +4,60 @@ import toJson from 'enzyme-to-json' import NoteListNav from './NoteListNav' describe(`NoteListNav component`, () => { - const props = { - notes: [ - { - "id": "cbc787a0-ffaf-11e8-8eb2-f2801f1b9fd1", - "name": "Dogs", - "modified": "2019-01-03T00:00:00.000Z", - "folderId": "b0715efe-ffaf-11e8-8eb2-f2801f1b9fd1", - "content": "Corporis accusamus placeat.\n \rUnde." - }, - { - "id": "d26e0034-ffaf-11e8-8eb2-f2801f1b9fd1", - "name": "Cats", - "modified": "2018-08-15T23:00:00.000Z", - "folderId": "b07161a6-ffaf-11e8-8eb2-f2801f1b9fd1", - "content": "Eos\n \rlaudantium." - }, - { - "id": "d26e01a6-ffaf-11e8-8eb2-f2801f1b9fd1", - "name": "Pigs", - "modified": "2018-03-01T00:00:00.000Z", - "folderId": "b07161a6-ffaf-11e8-8eb2-f2801f1b9fd1", - "content": "Occaecati dignissimos\nvoluptatum nihil." - }, - { - "id": "d26e0570-ffaf-11e8-8eb2-f2801f1b9fd1", - "name": "Birds", - "modified": "2019-01-04T00:00:00.000Z", - "folderId": "b0715efe-ffaf-11e8-8eb2-f2801f1b9fd1", - "content": "Eum culpa odit." - }, - ], - folders: [ - { - "id": "b0715efe-ffaf-11e8-8eb2-f2801f1b9fd1", - "name": "Important" - }, - { - "id": "b07161a6-ffaf-11e8-8eb2-f2801f1b9fd1", - "name": "Super" - }, - { - "id": "b07162f0-ffaf-11e8-8eb2-f2801f1b9fd1", - "name": "Spangley" - } - ] - } - it('renders a .NoteListNav by default', () => { const wrapper = shallow() expect(toJson(wrapper)).toMatchSnapshot() }) - it('renders a link in ul for each folder in array', () => { - const ul = shallow() + // enzyme doesn't yet support React.createContext + it.skip('renders a link in ul for each folder in array', () => { + const context = { + notes: [ + { + "id": "cbc787a0-ffaf-11e8-8eb2-f2801f1b9fd1", + "name": "Dogs", + "modified": "2019-01-03T00:00:00.000Z", + "folderId": "b0715efe-ffaf-11e8-8eb2-f2801f1b9fd1", + "content": "Corporis accusamus placeat.\n \rUnde." + }, + { + "id": "d26e0034-ffaf-11e8-8eb2-f2801f1b9fd1", + "name": "Cats", + "modified": "2018-08-15T23:00:00.000Z", + "folderId": "b07161a6-ffaf-11e8-8eb2-f2801f1b9fd1", + "content": "Eos\n \rlaudantium." + }, + { + "id": "d26e01a6-ffaf-11e8-8eb2-f2801f1b9fd1", + "name": "Pigs", + "modified": "2018-03-01T00:00:00.000Z", + "folderId": "b07161a6-ffaf-11e8-8eb2-f2801f1b9fd1", + "content": "Occaecati dignissimos\nvoluptatum nihil." + }, + { + "id": "d26e0570-ffaf-11e8-8eb2-f2801f1b9fd1", + "name": "Birds", + "modified": "2019-01-04T00:00:00.000Z", + "folderId": "b0715efe-ffaf-11e8-8eb2-f2801f1b9fd1", + "content": "Eum culpa odit." + }, + ], + folders: [ + { + "id": "b0715efe-ffaf-11e8-8eb2-f2801f1b9fd1", + "name": "Important" + }, + { + "id": "b07161a6-ffaf-11e8-8eb2-f2801f1b9fd1", + "name": "Super" + }, + { + "id": "b07162f0-ffaf-11e8-8eb2-f2801f1b9fd1", + "name": "Spangley" + } + ] + } + const ul = shallow(, context) .find('ul') expect(toJson(ul)).toMatchSnapshot() }) diff --git a/src/NotePageMain/NotePageMain.js b/src/NotePageMain/NotePageMain.js index 85077ea82..e1f4b9788 100644 --- a/src/NotePageMain/NotePageMain.js +++ b/src/NotePageMain/NotePageMain.js @@ -1,26 +1,34 @@ import React from 'react' import Note from '../Note/Note' +import ApiContext from '../ApiContext' +import { findNote } from '../notes-helpers' import './NotePageMain.css' -export default function NotePageMain(props) { - return ( -
- -
- {props.note.content.split(/\n \r|\n/).map((para, i) => -

{para}

- )} -
-
- ) -} +export default class NotePageMain extends React.Component { + static defaultProps = { + match: { + params: {} + } + } + static contextType = ApiContext -NotePageMain.defaultProps = { - note: { - content: '', + render() { + const { notes=[] } = this.context + const { noteId } = this.props.match.params + const note = findNote(notes, noteId) || { content: '' } + return ( +
+ +
+ {note.content.split(/\n \r|\n/).map((para, i) => +

{para}

+ )} +
+
+ ) } } diff --git a/src/NotePageMain/NotePageMain.test.js b/src/NotePageMain/NotePageMain.test.js index 961db2bc7..eb7da1ad5 100644 --- a/src/NotePageMain/NotePageMain.test.js +++ b/src/NotePageMain/NotePageMain.test.js @@ -4,34 +4,65 @@ import toJson from 'enzyme-to-json' import NotePageMain from './NotePageMain' describe(`NotePageMain component`, () => { - const props = { - note: { - "id": "cbc787a0-ffaf-11e8-8eb2-f2801f1b9fd1", - "name": "Dogs", - "modified": "2019-01-03T00:00:00.000Z", - // "folderId": "b0715efe-ffaf-11e8-8eb2-f2801f1b9fd1", - "content": "Corporis accusamus placeat.\n \rUnde." - } - } - it('renders a .NotePageMain by default', () => { const wrapper = shallow() expect(toJson(wrapper)).toMatchSnapshot() }) - it('renders a Note with note prop', () => { - const note = shallow() + // enzyme doesn't yet support React.createContext + it.skip('renders a Note with note prop', () => { + const props = { + match: { + params: { + noteId: 'cbc787a0-ffaf-11e8-8eb2-f2801f1b9fd1' + } + } + } + const context = { + notes: [{ + id: `cbc787a0-ffaf-11e8-8eb2-f2801f1b9fd1`, + name: `Dogs`, + modified: `2019-01-03T00:00:00.000Z`, + // folderId: b0715efe-ffaf-11e8-8eb2-f2801f1b9fd1, + content: "Corporis accusamus placeat.\n \rUnde." + }] + } + const note = shallow(, context) .find('Note') expect(toJson(note)).toMatchSnapshot() }) - it(`splits the content by \\n or \\n\\r, with a p foreach`, () => { - [{ - note: { "content": "Content with n r.\n \rafter n r." } - }, { - note: { "content": "Content with n.\nafter." } - }].forEach(props => { - const content = shallow() + // enzyme doesn't yet support React.createContext + it.skip(`splits the content by \\n or \\n\\r, with a p foreach`, () => { + const props = { + match: { + params: { + noteId: 'cbc787a0-ffaf-11e8-8eb2-f2801f1b9fd1' + } + } + } + + const notesContextWithDifferentContent = [ + { + notes: [ + { + id: `cbc787a0-ffaf-11e8-8eb2-f2801f1b9fd1`, + content: "Content with n r.\n \rafter n r.", + } + ] + }, + { + notes: [ + { + id: `cbc787a0-ffaf-11e8-8eb2-f2801f1b9fd1`, + content: "Content with n.\nafter." + } + ] + } + ] + + notesContextWithDifferentContent.forEach(context => { + const content = shallow(, context) .find('NotePageMain__content') expect(toJson(content)).toMatchSnapshot() }) diff --git a/src/NotePageNav/NotePageNav.js b/src/NotePageNav/NotePageNav.js index 630f54ae5..ef33935eb 100644 --- a/src/NotePageNav/NotePageNav.js +++ b/src/NotePageNav/NotePageNav.js @@ -1,32 +1,44 @@ import React from 'react' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import CircleButton from '../CircleButton/CircleButton' +import ApiContext from '../ApiContext' +import { findNote, findFolder } from '../notes-helpers' import './NotePageNav.css' -export default function NotePageNav(props) { - return ( -
- props.history.goBack()} - className='NotePageNav__back-button' - > - -
- Back -
- {props.folder && ( -

- {props.folder.name} -

- )} -
- ) -} +export default class NotePageNav extends React.Component { + static defaultProps = { + history: { + goBack: () => { } + }, + match: { + params: {} + } + } + static contextType = ApiContext; -NotePageNav.defaultProps = { - history: { - goBack: () => {} + render() { + const { notes, folders, } = this.context + const { noteId } = this.props.match.params + const note = findNote(notes, noteId) || {} + const folder = findFolder(folders, note.folderId) + return ( +
+ this.props.history.goBack()} + className='NotePageNav__back-button' + > + +
+ Back +
+ {folder && ( +

+ {folder.name} +

+ )} +
+ ) } } diff --git a/src/NotePageNav/NotePageNav.test.js b/src/NotePageNav/NotePageNav.test.js index 9a28d67a6..b41510573 100644 --- a/src/NotePageNav/NotePageNav.test.js +++ b/src/NotePageNav/NotePageNav.test.js @@ -1,22 +1,29 @@ -import React from 'react'; +import React from 'react' import { shallow } from 'enzyme' import toJson from 'enzyme-to-json' import NotePageNav from './NotePageNav' describe(`NotePageNav component`, () => { - const props = { - folder: { - "name": "Important" - } - } - it('renders a .NotePageNav by default', () => { const wrapper = shallow() expect(toJson(wrapper)).toMatchSnapshot() }) - it('renders a h3 with folder name when in props', () => { - const h3 = shallow() + // enzyme doesn't support React.createContext + it.skip('renders a h3 with folder name when in props', () => { + const props = { + match: { + params: { + noteId: 'test-note-id' + } + } + } + const context = { + notes: [{ id: 'test-note-id', folderId: 'test-folder-id' }], + folders: [{ id: 'test-folder-id', name: 'Important' }] + } + + const h3 = shallow(, context) .find('.NotePageNav__folder-name') expect(toJson(h3)).toMatchSnapshot() }) From 12efa966856293893a625de5e1d0620f77a5831d Mon Sep 17 00:00:00 2001 From: Thomas Hudspith-Tatham Date: Sat, 15 Dec 2018 22:22:21 +0000 Subject: [PATCH 02/15] implements add note, add folder, delete note with context against json-server --- src/AddFolder/AddFolder.js | 40 ++++++++++- src/AddNote/AddNote.js | 43 ++++++++++-- src/ApiContext.js | 3 + src/App/App.js | 52 +++++++++++++- src/Note/Note.js | 81 +++++++++++++++------ src/NotePageMain/NotePageMain.js | 5 ++ src/config.js | 3 + src/dummy-store.js | 116 ------------------------------- 8 files changed, 196 insertions(+), 147 deletions(-) create mode 100644 src/config.js delete mode 100644 src/dummy-store.js diff --git a/src/AddFolder/AddFolder.js b/src/AddFolder/AddFolder.js index 634d6ec84..3c573b3eb 100644 --- a/src/AddFolder/AddFolder.js +++ b/src/AddFolder/AddFolder.js @@ -1,19 +1,53 @@ import React, { Component } from 'react' import NotefulForm from '../NotefulForm/NotefulForm' -// import ApiContext from '../ApiContext' +import ApiContext from '../ApiContext' +import config from '../config' import './AddFolder.css' export default class AddFolder extends Component { + static defaultProps = { + history: { + push: () => { } + }, + } + static contextType = ApiContext; + + handleSubmit = e => { + e.preventDefault() + const folder = { + name: e.target['folder-name'].value + } + fetch(`${config.API_ENDPOINT}/folders`, { + method: 'POST', + headers: { + 'content-type': 'application/json' + }, + body: JSON.stringify(folder), + }) + .then(res => { + if (!res.ok) + return res.json().then(e => Promise.reject(e)) + return res.json() + }) + .then(folder => { + this.context.addFolder(folder) + this.props.history.push(`/folder/${folder.id}`) + }) + .catch(error => { + console.error({ error }) + }) + } + render() { return (

Create a folder

- +
- +