diff --git a/spec-inbox/stores/contact-store-spec.coffee b/spec-inbox/stores/contact-store-spec.coffee index 52552f4b0..4b8398a88 100644 --- a/spec-inbox/stores/contact-store-spec.coffee +++ b/spec-inbox/stores/contact-store-spec.coffee @@ -1,6 +1,111 @@ +_ = require 'underscore-plus' +proxyquire = require 'proxyquire' +Contact = require '../../src/flux/models/contact' ContactStore = require '../../src/flux/stores/contact-store' +DatabaseStore = require '../../src/flux/stores/database-store' +NamespaceStore = require '../../src/flux/stores/namespace-store' describe "ContactStore", -> - xit 'should return an empty array when there is no namespace', -> - r = ContactStore.searchContacts '' - expect(r.length).toBe 0 + beforeEach -> + ContactStore._contactCache = [] + ContactStore._fetchOffset = 0 + ContactStore._namespaceId = null + ContactStore._lastNamespaceId = null + NamespaceStore._current = + id: "nsid" + + it "initializes the cache from the DB", -> + spyOn(DatabaseStore, "findAll").andCallFake -> Promise.resolve([]) + ContactStore.init() + expect(ContactStore._contactCache.length).toBe 0 + expect(ContactStore._fetchOffset).toBe 0 + + describe "when the Namespace updates from null to valid", -> + beforeEach -> + spyOn(ContactStore, "_refreshDBFromAPI") + NamespaceStore.trigger() + + it "triggers a database fetch", -> + expect(ContactStore._refreshDBFromAPI.calls.length).toBe 1 + + it "starts at the current offset", -> + args = ContactStore._refreshDBFromAPI.calls[0].args + expect(args[0].limit).toBe ContactStore.BATCH_SIZE + expect(args[0].offset).toBe 0 + + describe "when the Namespace updates but the ID doesn't change", -> + it "does nothing", -> + spyOn(ContactStore, "_refreshDBFromAPI") + ContactStore._contactCache = [1,2,3] + ContactStore._fetchOffset = 3 + ContactStore._namespaceId = "nsid" + ContactStore._lastNamespaceId = "nsid" + NamespaceStore._current = + id: "nsid" + NamespaceStore.trigger() + expect(ContactStore._contactCache).toEqual [1,2,3] + expect(ContactStore._fetchOffset).toBe 3 + expect(ContactStore._refreshDBFromAPI).not.toHaveBeenCalled() + + describe "When fetching from the API", -> + it "makes a request for the first batch", -> + batches = [[4,5,6], []] + spyOn(atom.inbox, "getCollection").andCallFake (nsid, type, params, opts) -> + opts.success(batches.shift()) + waitsForPromise -> + ContactStore._refreshDBFromAPI().then -> + expect(atom.inbox.getCollection.calls.length).toBe 2 + + it "makes additional requests for future batches", -> + batches = [[1,2,3], [4,5,6], []] + spyOn(atom.inbox, "getCollection").andCallFake (nsid, type, params, opts) -> + opts.success(batches.shift()) + waitsForPromise -> + ContactStore._refreshDBFromAPI().then -> + expect(atom.inbox.getCollection.calls.length).toBe 3 + + describe "when searching for a contact", -> + beforeEach -> + @c1 = new Contact(name: "", email: "1test@nilas.com") + @c2 = new Contact(name: "First", email: "2test@nilas.com") + @c3 = new Contact(name: "First Last", email: "3test@nilas.com") + @c4 = new Contact(name: "Fit", email: "fit@nilas.com") + @c5 = new Contact(name: "Fins", email: "fins@nilas.com") + @c6 = new Contact(name: "Fill", email: "fill@nilas.com") + @c7 = new Contact(name: "Fin", email: "fin@nilas.com") + ContactStore._contactCache = [@c1,@c2,@c3,@c4,@c5,@c6,@c7] + + it "can find by first name", -> + results = ContactStore.searchContacts("First") + expect(results.length).toBe 2 + expect(results[0]).toBe @c2 + expect(results[1]).toBe @c3 + + it "can find by last name", -> + results = ContactStore.searchContacts("Last") + expect(results.length).toBe 1 + expect(results[0]).toBe @c3 + + it "can find by email", -> + results = ContactStore.searchContacts("1test") + expect(results.length).toBe 1 + expect(results[0]).toBe @c1 + + it "is case insensitive", -> + results = ContactStore.searchContacts("FIrsT") + expect(results.length).toBe 2 + expect(results[0]).toBe @c2 + expect(results[1]).toBe @c3 + + it "only returns the number requested", -> + results = ContactStore.searchContacts("FIrsT", limit: 1) + expect(results.length).toBe 1 + expect(results[0]).toBe @c2 + + it "returns no more than 5 by default", -> + results = ContactStore.searchContacts("fi") + expect(results.length).toBe 5 + + it "can return more than 5 if requested", -> + results = ContactStore.searchContacts("fi", limit: 6) + expect(results.length).toBe 6 diff --git a/src/flux/inbox-api.coffee b/src/flux/inbox-api.coffee index 3080adfe9..7e626f8db 100644 --- a/src/flux/inbox-api.coffee +++ b/src/flux/inbox-api.coffee @@ -185,7 +185,7 @@ class InboxAPI if objects.length > 0 DatabaseStore.persistModels(objects) resolve(objects) - + _shouldAcceptModel: (classname, model = null) -> return Promise.resolve() unless model @@ -238,9 +238,9 @@ class InboxAPI getCalendars: (namespaceId) -> @getCollection(namespaceId, 'calendars', {}) - getCollection: (namespaceId, collection, params={}) -> + getCollection: (namespaceId, collection, params={}, requestOptions={}) -> throw (new Error "getCollection requires namespaceId") unless namespaceId - @makeRequest + @makeRequest _.extend requestOptions, path: "/n/#{namespaceId}/#{collection}" qs: params returnsModel: true diff --git a/src/flux/stores/contact-store.coffee b/src/flux/stores/contact-store.coffee index 1c8685b30..afabc177f 100644 --- a/src/flux/stores/contact-store.coffee +++ b/src/flux/stores/contact-store.coffee @@ -6,26 +6,67 @@ NamespaceStore = require './namespace-store' _ = require 'underscore-plus' module.exports = ContactStore = Reflux.createStore + + BATCH_SIZE: 500 # Num contacts to pull at once + init: -> - @namespaceId = null - @listenTo NamespaceStore, @onNamespaceChanged - @listenTo DatabaseStore, @onDataChanged + @_contactCache = [] + @_lastNamespaceId = null + @_namespaceId = null + @listenTo DatabaseStore, @_onDBChanged + @listenTo NamespaceStore, @_onNamespaceChanged - onNamespaceChanged: -> - @onDataChanged() + @_refreshCacheFromDB() - onDataChanged: (change) -> - return if change && change.objectClass != Contact.name - DatabaseStore.findAll(Contact).then (contacts) => - @_all = contacts - @trigger(@) - - searchContacts: (search) -> + searchContacts: (search, {limit}={}) -> + limit ?= 5 + limit = Math.max(limit, 0) return [] if not search or search.length is 0 search = search.toLowerCase() - matches = _.filter @_all, (contact) -> - return true if contact.email?.toLowerCase().indexOf(search) == 0 - return true if contact.name?.toLowerCase().indexOf(search) == 0 + matches = _.filter @_contactCache, (contact) -> + return true if contact.email?.toLowerCase().indexOf(search) is 0 + name = contact.name?.toLowerCase() ? "" + for namePart in name.split(/\s/) + return true if namePart.indexOf(search) is 0 false - matches = matches[0..4] if matches.length > 5 + matches = matches[0..limit-1] if matches.length > limit matches + + _refreshCacheFromDB: -> + new Promise (resolve, reject) => + DatabaseStore.findAll(Contact) + .then (contacts=[]) => + @_contactCache = contacts + @trigger() + resolve() + .catch(reject) + + _refreshDBFromAPI: (params={}) -> + new Promise (resolve, reject) => + requestOptions = + success: (json) => + if json.length > 0 + @_refreshDBFromAPI + limit: @BATCH_SIZE + offset: params.offset + json.length + .then(resolve).catch(reject) + else resolve(json) + error: reject + atom.inbox.getCollection(@_namespaceId, "contacts", params, requestOptions) + + _onDBChanged: (change) -> + return unless change?.objectClass is Contact.name + @_refreshCacheFromDB() + + _resetCache: -> + @_contactCache = [] + @trigger(@) + + _onNamespaceChanged: -> + @_namespaceId = NamespaceStore.current()?.id + if @_namespaceId? + if @_namespaceId isnt @_lastNamespaceId + @_refreshDBFromAPI(limit: @BATCH_SIZE, offset: 0) + else + @_resetCache() + @_lastNamespaceId = @_namespaceId diff --git a/src/flux/stores/namespace-store.coffee b/src/flux/stores/namespace-store.coffee index 30977a997..0bddcd6bb 100644 --- a/src/flux/stores/namespace-store.coffee +++ b/src/flux/stores/namespace-store.coffee @@ -18,13 +18,10 @@ NamespaceStore = Reflux.createStore populateItems: -> DatabaseStore.findAll(Namespace).then (namespaces) => - @_items = namespaces - @_current = _.find @_items, (n) -> n.id == @_current?.id + @_namespaces = namespaces + @_current = _.find @_namespaces, (n) -> n.id == @_current?.id - unless @_current - @_current = @_items?[0] - if @_current - atom.inbox.getCollection(@_current.id, "contacts") + @_current = @_namespaces?[0] unless @_current @trigger(@) @@ -35,13 +32,13 @@ NamespaceStore = Reflux.createStore @populateItems() onSelectNamespaceId: (id) -> - @_current = _.find @_items, (n) -> n.id == @_current.id + @_current = _.find @_namespaces, (n) -> n.id == @_current.id @trigger(@) # Exposed Data items: -> - @_items + @_namespaces current: -> @_current