From 5b1af026ebbbaff4c56f9b97afc3bba3152e1dee Mon Sep 17 00:00:00 2001 From: Gregor Date: Sat, 10 Dec 2016 09:44:08 -0800 Subject: [PATCH 1/2] test: use account.hook API --- tests/specs/init.js | 229 ++++++++++++++++++-------------------------- 1 file changed, 91 insertions(+), 138 deletions(-) diff --git a/tests/specs/init.js b/tests/specs/init.js index 5d7fab0..40aabe4 100644 --- a/tests/specs/init.js +++ b/tests/specs/init.js @@ -1,4 +1,3 @@ -var _ = require('lodash') var simple = require('simple-mock') var test = require('tape') @@ -6,14 +5,6 @@ var init = require('../../lib/init') var getApi = require('../../lib/get-api') var getState = require('../../lib/get-state') -function findEventHandler (calls, name) { - var call = _.find(calls, function (call) { - return call.args[0] === name - }) - - return _.last(call.args) -} - test('"reset" triggered on "signin"', function (t) { t.plan(8) @@ -23,7 +14,11 @@ test('"reset" triggered on "signin"', function (t) { account: { id: 'accountid1', on: simple.stub(), - isSignedIn: simple.stub() + isSignedIn: simple.stub(), + hook: { + before: simple.stub(), + after: simple.stub() + } }, store: { findAll: function () { @@ -34,13 +29,13 @@ test('"reset" triggered on "signin"', function (t) { return Promise.resolve(existingObjects) }, connect: function () { - t.pass('store.connect is called on "post:signin"') + t.pass('store.connect is called after signin') signInTestOrder.push('connect') }, reset: function (options) { t.isNot(typeof options, 'undefined', 'store.reset options are defined') t.isNot(typeof options.name, 'undefined', 'store.reset options has defined name') - t.pass('store.reset called on "post:signin"') + t.pass('store.reset called after signin') signInTestOrder.push('reset') return Promise.resolve() @@ -53,39 +48,34 @@ test('"reset" triggered on "signin"', function (t) { init(hoodie) - var preSignInHandler = findEventHandler(hoodie.account.on.calls, 'pre:signin') - var postSignInHandler = findEventHandler(hoodie.account.on.calls, 'post:signin') - - var preHooks = [] - preSignInHandler({hooks: preHooks}) + var beforeSignInCall = hoodie.account.hook.before.calls[0] + var afterSignInCall = hoodie.account.hook.after.calls[0] - var postHooks = [] - postSignInHandler({hooks: postHooks}) + t.is(beforeSignInCall.args[0], 'signin', 'before signin hook registered') + t.is(afterSignInCall.args[0], 'signin', 'after signin hook registered') - t.is(preHooks.length, 1, 'one pre:signin hook registered') - t.is(postHooks.length, 1, 'one post:signin hook registered') - preHooks[0]().then(function () { - // simulate new account.id - hoodie.account.id = 'accountid2' - }) - - .then(postHooks[0]) + var options = {} + beforeSignInCall.args[1](options) + options.beforeSignin.accountId = 'accountid2' + afterSignInCall.args[1]({}, options) .then(function () { t.deepEqual(signInTestOrder, ['reset', 'connect'], 'store.connect was called after store.reset') }) - - .catch(t.error) }) -test('is "reset" triggered on "post:signout"', function (t) { +test('"reset" triggered after signout', function (t) { t.plan(4) var hoodie = { account: { id: 0, on: simple.stub(), - isSignedIn: simple.stub() + isSignedIn: simple.stub(), + hook: { + before: simple.stub(), + after: simple.stub() + } }, store: { reset: function (options) { @@ -100,11 +90,10 @@ test('is "reset" triggered on "post:signout"', function (t) { } init(hoodie) - var signOutHandler = findEventHandler(hoodie.account.on.calls, 'post:signout') - var hooks = [] - signOutHandler({hooks: hooks}) - t.is(hooks.length, 1, 'one post:signout hook registered') - hooks[0]() + + var afterHooks = hoodie.account.hook.after.calls + t.is(afterHooks[1].args[0], 'signout', 'after signout hook registered') + afterHooks[1].args[1]() }) test('"hoodie.store.connect()" is called when "hoodie.account.isSignedIn()" returns "true" ', function (t) { @@ -114,7 +103,11 @@ test('"hoodie.store.connect()" is called when "hoodie.account.isSignedIn()" retu account: { id: 0, on: simple.stub(), - isSignedIn: simple.stub().returnWith(true) + isSignedIn: simple.stub().returnWith(true), + hook: { + before: simple.stub(), + after: simple.stub() + } }, store: { connect: simple.stub(), @@ -136,7 +129,11 @@ test('"hoodie.store.connect()" is *not* called when "hoodie.account.isSignedIn() account: { id: 0, on: simple.stub(), - isSignedIn: simple.stub().returnWith(false) + isSignedIn: simple.stub().returnWith(false), + hook: { + before: simple.stub(), + after: simple.stub() + } }, store: { connect: simple.stub(), @@ -182,14 +179,18 @@ test('hoodie.store gets initialized with options.PouchDB', function (t) { t.is(storeDefaults.PouchDB, PouchDB, 'sets options.PouchDB') }) -test('"hoodie.store.push" is called on "pre:signout"', function (t) { +test('"hoodie.store.push" is called before signout', function (t) { t.plan(2) var hoodie = { account: { id: 0, on: simple.stub(), - isSignedIn: simple.stub() + isSignedIn: simple.stub(), + hook: { + before: simple.stub(), + after: simple.stub() + } }, store: { push: function () { @@ -203,11 +204,10 @@ test('"hoodie.store.push" is called on "pre:signout"', function (t) { } init(hoodie) - var signOutHandler = findEventHandler(hoodie.account.on.calls, 'pre:signout') - var hooks = [] - signOutHandler({hooks: hooks}) - t.is(hooks.length, 1, 'one pre:signout hook registered') - hooks[0]() + + var beforeHooks = hoodie.account.hook.before.calls + t.is(beforeHooks[1].args[0], 'signout', 'before signout hook registered') + beforeHooks[1].args[1]() }) test('"hoodie.store.push" returns better error message if local changes cannot be synced', function (t) { @@ -220,7 +220,11 @@ test('"hoodie.store.push" returns better error message if local changes cannot b account: { id: 0, on: simple.stub(), - isSignedIn: simple.stub() + isSignedIn: simple.stub(), + hook: { + before: simple.stub(), + after: simple.stub() + } }, store: { push: simple.stub().rejectWith(UnauthorizedError) @@ -231,11 +235,10 @@ test('"hoodie.store.push" returns better error message if local changes cannot b } init(hoodie) - var signOutHandler = findEventHandler(hoodie.account.on.calls, 'pre:signout') - var hooks = [] - signOutHandler({hooks: hooks}) - t.is(hooks.length, 1, 'one pre:signout hook registered') - hooks[0]() + + var beforeHooks = hoodie.account.hook.before.calls + t.is(beforeHooks[1].args[0], 'signout', 'before signout hook registered') + beforeHooks[1].args[1]() .catch(function (error) { t.is(error.message, 'Local changes could not be synced, sign in first') }) @@ -248,7 +251,11 @@ test('"hoodie.store.*" is *not* called when "hoodie.account.isSignedIn()" return account: { id: 0, on: simple.stub(), - isSignedIn: simple.stub().returnWith(false) + isSignedIn: simple.stub().returnWith(false), + hook: { + before: simple.stub(), + after: simple.stub() + } }, store: { disconnect: simple.stub(), @@ -273,7 +280,11 @@ test('"hoodie.store.*" is called on "disconnect" and "connect"', function (t) { account: { id: 0, on: simple.stub(), - isSignedIn: simple.stub().returnWith(true) + isSignedIn: simple.stub().returnWith(true), + hook: { + before: simple.stub(), + after: simple.stub() + } }, store: { disconnect: function (options) { @@ -294,85 +305,6 @@ test('"hoodie.store.*" is called on "disconnect" and "connect"', function (t) { init(hoodie) }) -test('"dataFromAccountBeforeSignin" nulled on "signin"', function (t) { - t.plan(3) - - var existingObjects = [{id: 'foo', createdBy: 'accountid1'}] - var hoodie = { - account: { - id: 'accountid1', - on: simple.stub(), - isSignedIn: simple.stub() - }, - store: { - findAll: function () { - return Promise.resolve(existingObjects) - }, - connect: function () { - t.pass('store.connect called on "signin"') - } - }, - connectionStatus: { - on: simple.stub() - } - } - - init(hoodie) - var preSignInHandler = findEventHandler(hoodie.account.on.calls, 'pre:signin') - var postSignInHandler = findEventHandler(hoodie.account.on.calls, 'post:signin') - - var preHooks = [] - preSignInHandler({hooks: preHooks}) - - var postHooks = [] - postSignInHandler({hooks: postHooks}) - - t.is(preHooks.length, 1, 'one pre:signin hook registered') - t.is(postHooks.length, 1, 'one post:signin hook registered') - preHooks[0]() - .then(postHooks[0]) -}) - -test('"signin" with Error', function (t) { - t.plan(2) - - var existingObjects = [{id: 'foo', createdBy: 'accountid1'}] - var hoodie = { - account: { - id: 'accountid1', - on: simple.stub(), - isSignedIn: simple.stub() - }, - store: { - findAll: function () { - return Promise.resolve(existingObjects) - }, - reset: simple.stub().rejectWith(new Error('Ooops')) - }, - connectionStatus: { - on: simple.stub() - } - } - - init(hoodie) - var preSignInHandler = findEventHandler(hoodie.account.on.calls, 'pre:signin') - var postSignInHandler = findEventHandler(hoodie.account.on.calls, 'post:signin') - - var preHooks = [] - preSignInHandler({hooks: preHooks}) - - var postHooks = [] - postSignInHandler({hooks: postHooks}) - - t.is(preHooks.length, 1, 'one pre:signin hook registered') - t.is(postHooks.length, 1, 'one post:signin hook registered') - preHooks[0]().then(function () { - // simulate new account.id - hoodie.account.id = 'accountid2' - }) - .then(postHooks[0]) -}) - test('options.account passed into Account constructor', function (t) { t.plan(2) @@ -381,11 +313,14 @@ test('options.account passed into Account constructor', function (t) { account: { id: 123 }, - PouchDB: { - defaults: simple.stub() - } + PouchDB: simple.stub() } - getApi.internals.Account = simple.stub().returnWith(state.account) + simple.mock(state.PouchDB, 'defaults').returnWith(state.PouchDB) + simple.mock(state.PouchDB, 'plugin').returnWith(state.PouchDB) + simple.mock(getApi.internals, 'Account').returnWith(state.account) + simple.mock(getApi.internals, 'Store', { + defaults: simple.stub().returnWith(simple.stub()) + }) getApi(state) @@ -406,6 +341,9 @@ test('options.ConnectionStatus passed into ConnectionStatus constructor', functi var state = { url: 'http://example.com', + account: { + id: 123 + }, connectionStatus: { interval: 10 }, @@ -413,7 +351,13 @@ test('options.ConnectionStatus passed into ConnectionStatus constructor', functi defaults: simple.stub() } } - getApi.internals.ConnectionStatus = simple.stub() + simple.mock(state.PouchDB, 'defaults').returnWith(state.PouchDB) + simple.mock(state.PouchDB, 'plugin').returnWith(state.PouchDB) + simple.mock(getApi.internals, 'Account').returnWith(state.account) + simple.mock(getApi.internals, 'Store', { + defaults: simple.stub().returnWith(simple.stub()) + }) + simple.mock(getApi.internals, 'ConnectionStatus').returnWith(simple.stub()) getApi(state) @@ -434,6 +378,9 @@ test('options.Log passed into Log constructor', function (t) { var state = { url: 'http://example.com', + account: { + id: 123 + }, log: { styles: false }, @@ -441,7 +388,13 @@ test('options.Log passed into Log constructor', function (t) { defaults: simple.stub() } } - getApi.internals.Log = simple.stub() + simple.mock(state.PouchDB, 'defaults').returnWith(state.PouchDB) + simple.mock(state.PouchDB, 'plugin').returnWith(state.PouchDB) + simple.mock(getApi.internals, 'Account').returnWith(state.account) + simple.mock(getApi.internals, 'Store', { + defaults: simple.stub().returnWith(simple.stub()) + }) + simple.mock(getApi.internals, 'Log').returnWith(simple.stub()) getApi(state) From c9cfeaa994c7e6971233acca4bf971f650338977 Mon Sep 17 00:00:00 2001 From: Gregor Date: Sat, 10 Dec 2016 09:44:17 -0800 Subject: [PATCH 2/2] refactor: use account.hook API --- lib/init.js | 90 +++++++++++++++++++++-------------------------------- 1 file changed, 36 insertions(+), 54 deletions(-) diff --git a/lib/init.js b/lib/init.js index 49685ab..db11ec4 100644 --- a/lib/init.js +++ b/lib/init.js @@ -3,72 +3,54 @@ module.exports = init function init (hoodie) { // In order to prevent data loss, we want to move all data that has been // created without an account (e.g. while offline) to the user’s account - // on signin. So before the signin happens, we temporarily store it in - // a variable (dataFromAccountBeforeSignin) and add it to the store again - // in the post:signin hook below - var dataFromAccountBeforeSignin - var accountIdBeforeSignIn - hoodie.account.on('pre:signin', function (options) { - options.hooks.push(function () { - accountIdBeforeSignIn = hoodie.account.id - return hoodie.store.findAll().then(function (objects) { - dataFromAccountBeforeSignin = objects - }) + // on signin. So before the signin happens, we store the user account’s id + // and data and store it again after the signin + hoodie.account.hook.before('signin', function (options) { + options.beforeSignin = { + accountId: hoodie.account.id + } + return hoodie.store.findAll().then(function (docs) { + options.beforeSignin.docs = docs }) }) - hoodie.account.on('post:signin', function (options) { - options.hooks.push(function () { - // when signing in to a newly created account, the account.id - // does not change, so there is no need to clear the local - // store and to migrate data - if (accountIdBeforeSignIn === hoodie.account.id) { - dataFromAccountBeforeSignin = null - return hoodie.store.connect() - } - - return hoodie.store.reset({ name: 'user/' + hoodie.account.id }) + hoodie.account.hook.after('signin', function (session, options) { + // when signing in to a newly created account, the account.id does not + // change. The same is true when the user changed their username. In both + // cases there is no need to migrate local data + if (options.beforeSignin.accountId === hoodie.account.id) { + return hoodie.store.connect() + } - .catch(function (error) { - dataFromAccountBeforeSignin = null - throw error - }) + return hoodie.store.reset({ name: 'user/' + hoodie.account.id }) - .then(function () { - var migratedDataFromAccountBeforeSignIn = dataFromAccountBeforeSignin.map(function (object) { - object.createdBy = hoodie.account.id - delete object._rev - return object - }) - dataFromAccountBeforeSignin = null - return hoodie.store.add(migratedDataFromAccountBeforeSignIn) - }) + .then(function () { + function migrate (doc) { + doc.createdBy = hoodie.account.id + delete doc._rev + return doc + } + return hoodie.store.add(options.beforeSignin.docs.map(migrate)) + }) - .then(function () { - return hoodie.store.connect() - }) + .then(function () { + return hoodie.store.connect() }) }) - // see https://github.com/hoodiehq/hoodie-client-account/issues/65 - // for info on the internal pre:* & post:* events - hoodie.account.on('pre:signout', function (options) { - options.hooks.push(function () { - return hoodie.store.push() - .catch(function (error) { - if (error.status !== 401) { - throw error - } - - error.message = 'Local changes could not be synced, sign in first' + hoodie.account.hook.before('signout', function () { + return hoodie.store.push() + .catch(function (error) { + if (error.status !== 401) { throw error - }) + } + + error.message = 'Local changes could not be synced, sign in first' + throw error }) }) - hoodie.account.on('post:signout', function (options) { - options.hooks.push(function () { - return hoodie.store.reset({ name: 'user/' + hoodie.account.id }) - }) + hoodie.account.hook.after('signout', function (options) { + return hoodie.store.reset({ name: 'user/' + hoodie.account.id }) }) hoodie.account.on('unauthenticate', hoodie.store.disconnect)