Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AGB and a welcome dialog #267

Open
wants to merge 8 commits into
base: latest
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -31,7 +31,7 @@
"author": {
"name": "Christopher Astfalk",
"email": "[email protected]",
"url": "http://christopher-astfalk.de/"
"url": "https://christopher-astfalk.de/"
},
"license": "Apache-2.0",
"engines": {
51 changes: 51 additions & 0 deletions src/actions/viewerAccount.js
Original file line number Diff line number Diff line change
@@ -3,11 +3,15 @@
import { v4 as uuid } from 'uuid'

import {
currentToSVersion,

signInStatus,
signOut as accountDidSignOut,
unlocked,
displayResetKeys,
didUpdate as accountDidUpdate,
didLoadToSState,
didAgreeToToS,
avatarSaved,
avatarsLoaded,
savedAvatarUpdated,
@@ -19,6 +23,7 @@ import {

selectIsSignedIn,
selectIsUnlocked,
selectToS,
selectSavedAvatars,
selectSavedAvatarsAreLoaded,
selectSavedGrids,
@@ -168,6 +173,52 @@ export function isSignedIn () {
}
}

export function doGetToSAgreeState () {
return async (dispatch, getState, { hoodie }) => {
if (!selectToS(getState()).loading) return

const store = hoodie.store.withIdPrefix('terms_of_service')
store.on('change', (eventName, doc) => {
if (doc.version < currentToSVersion) {
doc.version = currentToSVersion
hoodie.store.update(doc)
}
})

try {
const doc = await hoodie.store.find('terms_of_service')
dispatch(didLoadToSState({
isNew: false,
doc
}))
} catch (err) {
if (err.status === 404) {
dispatch(didLoadToSState({ isNew: true, doc: null }))
} else {
throw err
}
}
}
}

export function doAgreeToToS () {
return async (dispatch, getState, { hoodie }) => {
if (selectToS(getState()).loading) return

try {
await hoodie.store.updateOrAdd({
_id: 'terms_of_service',
version: currentToSVersion
})
} catch (err) {
// only log, to remove the dialog, but display it the next time.
console.error(err)
}

dispatch(didAgreeToToS())
}
}

function listenToAccountChanges (account, dispatch) {
const handler = changes => {
dispatch(accountDidUpdate(changes))
282 changes: 281 additions & 1 deletion src/actions/viewerAccount.test.js
Original file line number Diff line number Diff line change
@@ -2,17 +2,20 @@ import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import { v4 } from 'uuid'

import { signInStatus } from '../bundles/account'
import { signInStatus, selectShowToS, signOut } from '../bundles/account'
import {
saveAvatar,
loadSavedAvatars,
saveGrid,
loadSavedGrids,
isSignedIn,
doGetToSAgreeState,
doAgreeToToS,
unlock
} from './viewerAccount'

import AvatarName from '../avatarName'
import configureStore from '../store/configureStore'

jest.mock('uuid')
v4.mockReturnValue('b039f51f-41d9-41e7-a4b1-5490fbfd5eb9')
@@ -123,6 +126,283 @@ it('saveAvatar', async () => {
])
})

describe('Welcoming and Terms of Service state', () => {
// Please update this every time the ToS are changed!
const currentToSVersion = 1

it('loads terms of service state and subscribe to changes of it', async () => {
const find = jest.fn().mockResolvedValueOnce({
_id: 'terms_of_service',
version: currentToSVersion
})
const on = jest.fn()
const withIdPrefix = jest.fn(() => ({ on }))

window.hoodie = {
store: {
find,
withIdPrefix
}
}
const store = configureStore()

expect(selectShowToS(store.getState())).toBe(false)

await store.dispatch(doGetToSAgreeState())

expect(find).toHaveBeenLastCalledWith('terms_of_service')

expect(withIdPrefix).toHaveBeenLastCalledWith('terms_of_service')

expect(on).toHaveBeenCalled()
expect(on.mock.calls[0][0]).toBe('change')
expect(typeof on.mock.calls[0][1]).toBe('function')

expect(selectShowToS(store.getState())).toBe(false)
})

it('should display the ToS if the last version is older then current', async () => {
const find = jest.fn().mockResolvedValueOnce({
_id: 'terms_of_service',
version: currentToSVersion - 1
})
const on = jest.fn()
const withIdPrefix = jest.fn(() => ({ on }))

window.hoodie = {
store: {
find,
withIdPrefix
}
}
const store = configureStore()

expect(selectShowToS(store.getState())).toBe(false)

await store.dispatch(doGetToSAgreeState())

expect(find).toBeCalled()
expect(on).toBeCalled()

expect(selectShowToS(store.getState())).toBeTruthy()
})

it('should display the ToS if "terms_of_service" doc doesn\'t exist', async () => {
const find = jest.fn().mockImplementationOnce(id => {
const missing = new Error(`Object with id "${id}" is missing`)
missing.name = 'Not found'
missing.status = 404
return Promise.reject(missing)
})
const on = jest.fn()
const withIdPrefix = jest.fn(() => ({ on }))

window.hoodie = {
store: {
find,
withIdPrefix
}
}
const store = configureStore()

expect(selectShowToS(store.getState())).toBe(false)

await store.dispatch(doGetToSAgreeState())

expect(find).toBeCalled()
expect(on).toBeCalled()

expect(selectShowToS(store.getState())).toBeTruthy()
})

it('should close the Welcoming and ToS dialog and store a "terms_of_service" doc', async () => {
const find = jest.fn().mockImplementationOnce(id => {
const missing = new Error(`Object with id "${id}" is missing`)
missing.name = 'Not found'
missing.status = 404
return Promise.reject(missing)
})
const on = jest.fn()
const withIdPrefix = jest.fn(() => ({ on }))

const updateOrAdd = jest.fn().mockImplementation(obj => Promise.resolve(obj))

window.hoodie = {
store: {
find,
updateOrAdd,
withIdPrefix
}
}
const store = configureStore()

await store.dispatch(doGetToSAgreeState())

expect(selectShowToS(store.getState())).toBeTruthy()

await store.dispatch(doAgreeToToS())

expect(selectShowToS(store.getState())).toBeFalsy()

expect(updateOrAdd).toHaveBeenLastCalledWith({
_id: 'terms_of_service',
version: currentToSVersion
})
})

it('should not display the Welcome/ToS dialog after viewer-account sign out', async () => {
const find = jest.fn().mockResolvedValueOnce({
_id: 'terms_of_service',
version: currentToSVersion
})
const on = jest.fn()
const withIdPrefix = jest.fn(() => ({ on }))

window.hoodie = {
store: {
find,
withIdPrefix
}
}
const store = configureStore()

store.dispatch(signInStatus(true, true, '[email protected]'))

await store.dispatch(doGetToSAgreeState())

expect(selectShowToS(store.getState())).toBe(false)

store.dispatch(signOut())

expect(selectShowToS(store.getState())).toBe(false)
})

it('should not display the welcome/ToS if the viewer is not unlocked', async () => {
const find = jest.fn().mockResolvedValueOnce({
_id: 'terms_of_service',
version: currentToSVersion - 1
})
const on = jest.fn()
const withIdPrefix = jest.fn(() => ({ on }))

window.hoodie = {
account: {
one: () => {}
},
store: {
find,
withIdPrefix
},
cryptoStore: {
unlock: () => {},
withIdPrefix: () => ({
on: () => {},
findAll: () => []
})
}
}
const store = configureStore()

expect(selectShowToS(store.getState())).toBe(false)

await store.dispatch(doGetToSAgreeState())

store.dispatch(signInStatus(true, false, '[email protected]'))

expect(selectShowToS(store.getState())).toBe(false)

await store.dispatch(unlock())

expect(selectShowToS(store.getState())).toBe(true)
})

it('should update an outdated doc after sign in', async () => {
const find = jest.fn().mockImplementationOnce(id => {
const missing = new Error(`Object with id "${id}" is missing`)
missing.name = 'Not found'
missing.status = 404
return Promise.reject(missing)
})

const on = jest.fn()
const withIdPrefix = jest.fn(() => ({ on }))
const update = jest.fn(obj => obj)
const updateOrAdd = jest.fn().mockImplementation(obj => Promise.resolve(obj))

window.hoodie = {
store: {
find,
update,
updateOrAdd,
withIdPrefix
}
}
const store = configureStore()

await store.dispatch(doGetToSAgreeState())
await store.dispatch(doAgreeToToS())

store.dispatch(signInStatus(true, true, '[email protected]'))

const fn = on.mock.calls[0][1]
expect(typeof fn).toBe('function')

fn('update', {
_id: 'terms_of_service',
_rev: '1-12345678',
version: currentToSVersion - 1
})

expect(selectShowToS(store.getState())).toBeFalsy()
expect(update).toBeCalledWith({
_id: 'terms_of_service',
_rev: '1-12345678',
version: currentToSVersion
})
})

it('should ignore changes of not outdated tos doc', async () => {
const find = jest.fn().mockImplementationOnce(id => {
const missing = new Error(`Object with id "${id}" is missing`)
missing.name = 'Not found'
missing.status = 404
return Promise.reject(missing)
})

const on = jest.fn()
const withIdPrefix = jest.fn(() => ({ on }))
const update = jest.fn(obj => obj)
const updateOrAdd = jest.fn().mockImplementation(obj => Promise.resolve(obj))

window.hoodie = {
store: {
find,
update,
updateOrAdd,
withIdPrefix
}
}
const store = configureStore()

await store.dispatch(doGetToSAgreeState())
await store.dispatch(doAgreeToToS())

store.dispatch(signInStatus(true, true, '[email protected]'))

const fn = on.mock.calls[0][1]
expect(typeof fn).toBe('function')

fn('update', {
_id: 'terms_of_service',
_rev: '1-12345678',
version: currentToSVersion
})

expect(selectShowToS(store.getState())).toBeFalsy()
expect(update).not.toBeCalled()
})
})

it('loadSavedAvatars', async () => {
const findAll = jest.fn(() => Promise.resolve([
{
Loading