forked from Shiloh/remnantchat
feat: persist userId
This commit is contained in:
parent
74f11dae2a
commit
b8f8bb5bfd
35
package-lock.json
generated
35
package-lock.json
generated
@ -21,6 +21,7 @@
|
||||
"@types/react": "^18.0.17",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"fast-memoize": "^2.5.2",
|
||||
"localforage": "^1.10.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-markdown": "^8.0.3",
|
||||
@ -15987,6 +15988,22 @@
|
||||
"node": ">=8.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/localforage": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz",
|
||||
"integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==",
|
||||
"dependencies": {
|
||||
"lie": "3.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/localforage/node_modules/lie": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
|
||||
"integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==",
|
||||
"dependencies": {
|
||||
"immediate": "~3.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||
@ -35404,6 +35421,24 @@
|
||||
"json5": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"localforage": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz",
|
||||
"integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==",
|
||||
"requires": {
|
||||
"lie": "3.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"lie": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
|
||||
"integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==",
|
||||
"requires": {
|
||||
"immediate": "~3.0.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"locate-path": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||
|
@ -17,6 +17,7 @@
|
||||
"@types/react": "^18.0.17",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"fast-memoize": "^2.5.2",
|
||||
"localforage": "^1.10.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-markdown": "^8.0.3",
|
||||
|
@ -1,15 +1,70 @@
|
||||
import React from 'react'
|
||||
import { render } from '@testing-library/react'
|
||||
import { act, render } from '@testing-library/react'
|
||||
import { MemoryRouter as Router } from 'react-router-dom'
|
||||
import localforage from 'localforage'
|
||||
|
||||
import Bootstrap from './Bootstrap'
|
||||
import { PersistedStorageKeys } from 'models/storage'
|
||||
|
||||
const StubBootstrap = () => (
|
||||
import Bootstrap, { BootstrapProps } from './Bootstrap'
|
||||
|
||||
const mockPersistedStorage =
|
||||
jest.createMockFromModule<jest.Mock<typeof localforage>>('localforage')
|
||||
|
||||
const mockGetUuid = jest.fn()
|
||||
|
||||
const mockGetItem = jest.fn()
|
||||
const mockSetItem = jest.fn()
|
||||
|
||||
beforeEach(() => {
|
||||
mockGetItem.mockImplementation(() => Promise.resolve(null))
|
||||
mockSetItem.mockImplementation((data: any) => Promise.resolve(data))
|
||||
})
|
||||
|
||||
const renderBootstrap = async (overrides: BootstrapProps = {}) => {
|
||||
Object.assign(mockPersistedStorage, {
|
||||
getItem: mockGetItem,
|
||||
setItem: mockSetItem,
|
||||
})
|
||||
|
||||
render(
|
||||
<Router>
|
||||
<Bootstrap />
|
||||
<Bootstrap
|
||||
persistedStorage={mockPersistedStorage as any as typeof localforage}
|
||||
{...overrides}
|
||||
/>
|
||||
</Router>
|
||||
)
|
||||
|
||||
test('renders', () => {
|
||||
render(<StubBootstrap />)
|
||||
// https://kentcdodds.com/blog/fix-the-not-wrapped-in-act-warning#an-alternative-waiting-for-the-mocked-promise
|
||||
await act(async () => {
|
||||
await Promise.resolve()
|
||||
})
|
||||
}
|
||||
|
||||
test('renders', async () => {
|
||||
await renderBootstrap()
|
||||
})
|
||||
|
||||
test('checks persistedStorage for user settings', async () => {
|
||||
await renderBootstrap()
|
||||
expect(mockGetItem).toHaveBeenCalledWith(PersistedStorageKeys.USER_SETTINGS)
|
||||
})
|
||||
|
||||
test('persists user settings if none were already persisted', async () => {
|
||||
await renderBootstrap({
|
||||
getUuid: mockGetUuid.mockImplementation(() => 'abc123'),
|
||||
})
|
||||
|
||||
expect(mockSetItem).toHaveBeenCalledWith(PersistedStorageKeys.USER_SETTINGS, {
|
||||
userId: 'abc123',
|
||||
})
|
||||
})
|
||||
|
||||
test('does not update user settings if they were already persisted', async () => {
|
||||
mockGetItem.mockImplementation(() => ({
|
||||
userId: 'abc123',
|
||||
}))
|
||||
|
||||
await renderBootstrap()
|
||||
|
||||
expect(mockSetItem).not.toHaveBeenCalled()
|
||||
})
|
||||
|
@ -1,15 +1,54 @@
|
||||
import { useState } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Routes, Route } from 'react-router-dom'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import localforage from 'localforage'
|
||||
|
||||
import { Home } from './pages/Home/'
|
||||
import { PublicRoom } from './pages/PublicRoom/'
|
||||
import { Home } from 'pages/Home/'
|
||||
import { PublicRoom } from 'pages/PublicRoom/'
|
||||
import { UserSettings } from 'models/settings'
|
||||
import { PersistedStorageKeys } from 'models/storage'
|
||||
|
||||
function Bootstrap() {
|
||||
const [userId] = useState(uuid())
|
||||
export interface BootstrapProps {
|
||||
persistedStorage?: typeof localforage
|
||||
getUuid?: typeof uuid
|
||||
}
|
||||
|
||||
function Bootstrap({
|
||||
persistedStorage = localforage.createInstance({
|
||||
name: 'chitchatter',
|
||||
description: 'Persisted settings data for chitchatter',
|
||||
}),
|
||||
getUuid = uuid,
|
||||
}: BootstrapProps) {
|
||||
const [hasLoadedSettings, setHasLoadedSettings] = useState(false)
|
||||
const [settings, setSettings] = useState({ userId: getUuid() })
|
||||
const { userId } = settings
|
||||
|
||||
useEffect(() => {
|
||||
;(async () => {
|
||||
if (hasLoadedSettings) return
|
||||
|
||||
const persistedUserSettings =
|
||||
await persistedStorage.getItem<UserSettings>(
|
||||
PersistedStorageKeys.USER_SETTINGS
|
||||
)
|
||||
|
||||
if (persistedUserSettings) {
|
||||
setSettings(persistedUserSettings)
|
||||
} else {
|
||||
await persistedStorage.setItem(
|
||||
PersistedStorageKeys.USER_SETTINGS,
|
||||
settings
|
||||
)
|
||||
}
|
||||
|
||||
setHasLoadedSettings(true)
|
||||
})()
|
||||
}, [hasLoadedSettings, persistedStorage, settings, userId])
|
||||
|
||||
return (
|
||||
<div className="Chitchatter">
|
||||
{hasLoadedSettings ? (
|
||||
<Routes>
|
||||
{['/', '/index.html'].map(path => (
|
||||
<Route key={path} path={path} element={<Home />} />
|
||||
@ -19,6 +58,7 @@ function Bootstrap() {
|
||||
element={<PublicRoom userId={userId} />}
|
||||
/>
|
||||
</Routes>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
3
src/models/settings.ts
Normal file
3
src/models/settings.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export interface UserSettings {
|
||||
userId: string
|
||||
}
|
3
src/models/storage.ts
Normal file
3
src/models/storage.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export enum PersistedStorageKeys {
|
||||
USER_SETTINGS = 'userSettings',
|
||||
}
|
Loading…
Reference in New Issue
Block a user