From 72fd39b494399e8a97024cc7d21b66a8eae84c55 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Tue, 27 Jun 2017 20:59:53 -0700 Subject: [PATCH 1/3] DocumentsStore WIP --- __mocks__/localStorage.js | 20 ++++++ frontend/models/Document.js | 8 +-- frontend/models/Document.test.js | 13 ++++ frontend/stores/CollectionsStore.js | 2 +- frontend/stores/CollectionsStore.test.js | 4 +- frontend/stores/DocumentsStore.js | 83 ++++++++++++++++++++++++ frontend/stores/UiStore.js | 2 +- frontend/utils/setupJest.js | 2 + 8 files changed, 125 insertions(+), 9 deletions(-) create mode 100644 __mocks__/localStorage.js create mode 100644 frontend/models/Document.test.js create mode 100644 frontend/stores/DocumentsStore.js diff --git a/__mocks__/localStorage.js b/__mocks__/localStorage.js new file mode 100644 index 000000000..3d8394b99 --- /dev/null +++ b/__mocks__/localStorage.js @@ -0,0 +1,20 @@ +const storage = {}; + +export default { + setItem: function(key, value) { + storage[key] = value || ''; + }, + getItem: function(key) { + return key in storage ? storage[key] : null; + }, + removeItem: function(key) { + delete storage[key]; + }, + get length() { + return Object.keys(storage).length; + }, + key: function(i) { + var keys = Object.keys(storage); + return keys[i] || null; + }, +}; diff --git a/frontend/models/Document.js b/frontend/models/Document.js index 6f98addd9..77dc34199 100644 --- a/frontend/models/Document.js +++ b/frontend/models/Document.js @@ -2,7 +2,7 @@ import { extendObservable, action, runInAction, computed } from 'mobx'; import invariant from 'invariant'; -import ApiClient, { client } from 'utils/ApiClient'; +import { client } from 'utils/ApiClient'; import stores from 'stores'; import ErrorsStore from 'stores/ErrorsStore'; @@ -17,14 +17,13 @@ class Document { html: string; id: string; private: boolean; + starred: boolean; team: string; text: string; title: string; updatedAt: string; updatedBy: User; url: string; - - client: ApiClient; errors: ErrorsStore; /* Computed */ @@ -56,7 +55,7 @@ class Document { @action update = async () => { try { - const res = await this.client.post('/documents.info', { id: this.id }); + const res = await client.post('/documents.info', { id: this.id }); invariant(res && res.data, 'Document API response should be available'); const { data } = res; runInAction('Document#update', () => { @@ -73,7 +72,6 @@ class Document { constructor(document: Document) { this.updateData(document); - this.client = client; this.errors = stores.errors; } } diff --git a/frontend/models/Document.test.js b/frontend/models/Document.test.js new file mode 100644 index 000000000..e7db05b7d --- /dev/null +++ b/frontend/models/Document.test.js @@ -0,0 +1,13 @@ +/* eslint-disable */ +import Document from './Document'; + +describe('Document model', () => { + test('should initialize with data', () => { + const document = new Document({ + id: 123, + title: 'Onboarding', + text: 'Some body text' + }); + expect(document.title).toBe('Onboarding'); + }); +}); diff --git a/frontend/stores/CollectionsStore.js b/frontend/stores/CollectionsStore.js index 2f9d2dafe..ae8e26446 100644 --- a/frontend/stores/CollectionsStore.js +++ b/frontend/stores/CollectionsStore.js @@ -22,7 +22,7 @@ class CollectionsStore { /* Actions */ - @action fetch = async (): Promise<*> => { + @action fetchAll = async (): Promise<*> => { try { const res = await this.client.post('/collections.list', { id: this.teamId, diff --git a/frontend/stores/CollectionsStore.test.js b/frontend/stores/CollectionsStore.test.js index 2694a9dc5..ef39ccbe0 100644 --- a/frontend/stores/CollectionsStore.test.js +++ b/frontend/stores/CollectionsStore.test.js @@ -27,7 +27,7 @@ describe('CollectionsStore', () => { })), }; - await store.fetch(); + await store.fetchAll(); expect(store.client.post).toHaveBeenCalledWith('/collections.list', { id: 123, @@ -44,7 +44,7 @@ describe('CollectionsStore', () => { add: jest.fn(), }; - await store.fetch(); + await store.fetchAll(); expect(store.errors.add).toHaveBeenCalledWith( 'Failed to load collections' diff --git a/frontend/stores/DocumentsStore.js b/frontend/stores/DocumentsStore.js new file mode 100644 index 000000000..250d107b9 --- /dev/null +++ b/frontend/stores/DocumentsStore.js @@ -0,0 +1,83 @@ +// @flow +import { observable, action, runInAction } from 'mobx'; +import { client } from 'utils/ApiClient'; +import _ from 'lodash'; +import invariant from 'invariant'; + +import stores from 'stores'; +import Document from 'models/Document'; +import ErrorsStore from 'stores/ErrorsStore'; + +type Options = { + teamId: string, +}; + +class DocumentsStore { + @observable data: Object = {}; + @observable isLoaded: boolean = false; + errors: ErrorsStore; + + /* Actions */ + + @action fetchAll = async (): Promise<*> => { + try { + const res = await client.post('/documents.list'); + invariant(res && res.data, 'Document list not available'); + const { data } = res; + runInAction('DocumentsStore#fetchAll', () => { + const loaded = _.keyBy( + data.map(document => new Document(document)), + 'id' + ); + this.data = { + ...this.data, + ...loaded, + }; + this.isLoaded = true; + }); + } catch (e) { + this.errors.add('Failed to load documents'); + } + }; + + @action fetch = async (id: string): Promise<*> => { + try { + const res = await client.post('/documents.info', { id }); + invariant(res && res.data, 'Document not available'); + const { data } = res; + runInAction('DocumentsStore#fetch', () => { + this.update(new Document(data)); + this.isLoaded = true; + }); + } catch (e) { + this.errors.add('Failed to load documents'); + } + }; + + @action add = (document: Document): void => { + this.data[document.id] = document; + }; + + @action remove = (id: string): void => { + delete this.data[id]; + }; + + @action update = (document: Document): void => { + const existing = this.data[document.id]; + + this.data[document.id] = { + ...existing, + document, + }; + }; + + getById = (id: string): Document => { + return _.find(this.data, { id }); + }; + + constructor(options: Options) { + this.errors = stores.errors; + } +} + +export default DocumentsStore; diff --git a/frontend/stores/UiStore.js b/frontend/stores/UiStore.js index 74068d0dd..48327a7bd 100644 --- a/frontend/stores/UiStore.js +++ b/frontend/stores/UiStore.js @@ -1,6 +1,6 @@ // @flow import { observable, action, computed } from 'mobx'; -import type { Document } from 'types'; +import Document from 'models/Document'; import Collection from 'models/Collection'; class UiStore { diff --git a/frontend/utils/setupJest.js b/frontend/utils/setupJest.js index dcd1f0750..61b717c2f 100644 --- a/frontend/utils/setupJest.js +++ b/frontend/utils/setupJest.js @@ -2,10 +2,12 @@ import React from 'react'; import { shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; +import localStorage from '../../__mocks__/localStorage'; const snap = children => { const wrapper = shallow(children); expect(toJson(wrapper)).toMatchSnapshot(); }; +global.localStorage = localStorage; global.snap = snap; From 4c3f4fea1669e3958318010e44136bbd58fade74 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Tue, 27 Jun 2017 21:20:09 -0700 Subject: [PATCH 2/3] recent --- frontend/index.js | 7 ++++++- frontend/stores/DocumentsStore.js | 32 +++++++++++++++---------------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/frontend/index.js b/frontend/index.js index c09fac025..102280336 100644 --- a/frontend/index.js +++ b/frontend/index.js @@ -11,6 +11,7 @@ import { import { Flex } from 'reflexbox'; import stores from 'stores'; +import DocumentsStore from 'stores/DocumentsStore'; import CollectionsStore from 'stores/CollectionsStore'; import 'normalize.css/normalize.css'; @@ -57,12 +58,14 @@ const Auth = ({ children }: AuthProps) => { const user = stores.auth.getUserStore(); authenticatedStores = { user, + documents: new DocumentsStore(), collections: new CollectionsStore({ teamId: user.team.id, }), }; - authenticatedStores.collections.fetch(); + authenticatedStores.documents.fetchAll(); + authenticatedStores.collections.fetchAll(); } return ( @@ -135,3 +138,5 @@ render( , document.getElementById('root') ); + +window.authenticatedStores = authenticatedStores; diff --git a/frontend/stores/DocumentsStore.js b/frontend/stores/DocumentsStore.js index 250d107b9..fa74e4aad 100644 --- a/frontend/stores/DocumentsStore.js +++ b/frontend/stores/DocumentsStore.js @@ -8,10 +8,6 @@ import stores from 'stores'; import Document from 'models/Document'; import ErrorsStore from 'stores/ErrorsStore'; -type Options = { - teamId: string, -}; - class DocumentsStore { @observable data: Object = {}; @observable isLoaded: boolean = false; @@ -19,9 +15,9 @@ class DocumentsStore { /* Actions */ - @action fetchAll = async (): Promise<*> => { + @action fetchAll = async (request?: string = 'list'): Promise<*> => { try { - const res = await client.post('/documents.list'); + const res = await client.post(`/documents.${request}`); invariant(res && res.data, 'Document list not available'); const { data } = res; runInAction('DocumentsStore#fetchAll', () => { @@ -35,18 +31,29 @@ class DocumentsStore { }; this.isLoaded = true; }); + return data; } catch (e) { this.errors.add('Failed to load documents'); } }; + @action fetchRecent = async (): Promise<*> => { + const data = await this.fetchAll('recent'); + console.log(data); + }; + + @action fetchViewed = async (): Promise<*> => { + const data = await this.fetchAll('viewed'); + console.log(data); + }; + @action fetch = async (id: string): Promise<*> => { try { const res = await client.post('/documents.info', { id }); invariant(res && res.data, 'Document not available'); const { data } = res; runInAction('DocumentsStore#fetch', () => { - this.update(new Document(data)); + this.add(new Document(data)); this.isLoaded = true; }); } catch (e) { @@ -62,20 +69,11 @@ class DocumentsStore { delete this.data[id]; }; - @action update = (document: Document): void => { - const existing = this.data[document.id]; - - this.data[document.id] = { - ...existing, - document, - }; - }; - getById = (id: string): Document => { return _.find(this.data, { id }); }; - constructor(options: Options) { + constructor() { this.errors = stores.errors; } } From 07c362cd5423bd21c42cc30bffe3ffe3ce4e3741 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Tue, 27 Jun 2017 21:52:47 -0700 Subject: [PATCH 3/3] Refactor to Map --- frontend/stores/DocumentsStore.js | 40 +++++++++++++++---------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/frontend/stores/DocumentsStore.js b/frontend/stores/DocumentsStore.js index fa74e4aad..05cfb02b5 100644 --- a/frontend/stores/DocumentsStore.js +++ b/frontend/stores/DocumentsStore.js @@ -1,5 +1,5 @@ // @flow -import { observable, action, runInAction } from 'mobx'; +import { observable, action, map, runInAction } from 'mobx'; import { client } from 'utils/ApiClient'; import _ from 'lodash'; import invariant from 'invariant'; @@ -9,26 +9,22 @@ import Document from 'models/Document'; import ErrorsStore from 'stores/ErrorsStore'; class DocumentsStore { - @observable data: Object = {}; + @observable recentlyViewedIds: Array = []; + @observable data: Map = map([]); @observable isLoaded: boolean = false; errors: ErrorsStore; /* Actions */ - @action fetchAll = async (request?: string = 'list'): Promise<*> => { + @action fetchAll = async (request: string = 'list'): Promise<*> => { try { const res = await client.post(`/documents.${request}`); invariant(res && res.data, 'Document list not available'); const { data } = res; runInAction('DocumentsStore#fetchAll', () => { - const loaded = _.keyBy( - data.map(document => new Document(document)), - 'id' - ); - this.data = { - ...this.data, - ...loaded, - }; + data.forEach(document => { + this.data.set(document.id, new Document(document)); + }); this.isLoaded = true; }); return data; @@ -37,14 +33,16 @@ class DocumentsStore { } }; - @action fetchRecent = async (): Promise<*> => { - const data = await this.fetchAll('recent'); - console.log(data); + @action fetchRecentlyEdited = async (): Promise<*> => { + await this.fetchAll('recent'); }; - @action fetchViewed = async (): Promise<*> => { + @action fetchRecentlyViewed = async (): Promise<*> => { const data = await this.fetchAll('viewed'); - console.log(data); + + runInAction('DocumentsStore#fetchRecentlyViewed', () => { + this.recentlyViewedIds = _.pick(data, 'id'); + }); }; @action fetch = async (id: string): Promise<*> => { @@ -53,7 +51,7 @@ class DocumentsStore { invariant(res && res.data, 'Document not available'); const { data } = res; runInAction('DocumentsStore#fetch', () => { - this.add(new Document(data)); + this.data.set(data.id, new Document(data)); this.isLoaded = true; }); } catch (e) { @@ -62,15 +60,15 @@ class DocumentsStore { }; @action add = (document: Document): void => { - this.data[document.id] = document; + this.data.set(document.id, document); }; @action remove = (id: string): void => { - delete this.data[id]; + this.data.delete(id); }; - getById = (id: string): Document => { - return _.find(this.data, { id }); + getById = (id: string): ?Document => { + return this.data.get(id); }; constructor() {