Skip to content

Commit

Permalink
Adds persist tests
Browse files Browse the repository at this point in the history
  • Loading branch information
JesuHrz committed May 17, 2023
1 parent 36e1dbe commit 454b90e
Show file tree
Hide file tree
Showing 8 changed files with 266 additions and 105 deletions.
35 changes: 27 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ npm install killa
To use directly vanilla minified version in the browser:

```html
<script src="https://unpkg.com/killa@1.4.1/dist/killa.min.js"></script>
<script src="https://unpkg.com/killa@1.5.0/dist/killa.min.js"></script>
```

Or from jsdelivr:

```html
<script src="https://cdn.jsdelivr.net/npm/killa@1.4.1/dist/killa.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/killa@1.5.0/dist/killa.min.js"></script>
```

### How to create your first store
Expand Down Expand Up @@ -159,26 +159,45 @@ const Counter = () => {
## Middlewares
### Persist

Killa Persist uses `localStorage` by default.

```js
import { persist } from 'killa/middleware'
import { persist } from 'killa/persist'

const store = killa(
{ counter: 0, filter: '' },
{
use: [
persist({
name: 'killa-persist',
storage
})
persist({ name: 'killa-persist' })
]
}
)

store.getState() // { counter: 0 }
```


If you wish to use other storage you can do so by using the `normalizeStorage` method to normalize the storage supported by Killa Persist.

```js
import { persist, normalizeStorage } from 'killa/persist'

const store = killa(
{ counter: 0, filter: '' },
{
use: [
persist({ name: 'killa-persist' }),
storage: normalizeStorage(() => sessionStorage)
]
}
)

store.getState() // { counter: 0 }
```

### Auto Revalidate

<img src="killa-revalidate.gif" width="600" />

## Support
React >= 16.8, Chrome 58, Firefox 57, IE 11, Edge 16, Safari 11, & Node.js 12.
React >= 16.8, Chrome 58, Firefox 57, IE 11, Edge 18, Safari 11, & Node.js 12.
90 changes: 90 additions & 0 deletions __tests__/init-revalidate-on-focus.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { EventEmitter } from 'events'

const FOCUS_EVENT = 'focus'
const VISIBILITYCHANGE_EVENT = 'visibilitychange'

declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace NodeJS {
interface EventEmitter {
addEventListener: typeof EventEmitter.prototype.on
removeEventListener: typeof EventEmitter.prototype.off
}
}
}

EventEmitter.prototype['addEventListener'] = EventEmitter.prototype.on
EventEmitter.prototype['removeEventListener'] = EventEmitter.prototype.off

const eventEmitter = new EventEmitter()

describe('Init Revalidate On Focus', () => {
type TWindow = typeof global.window
type TDocument = typeof global.document

const globalSpy = {
window: jest.spyOn(global, 'window', 'get'),
document: jest.spyOn(global, 'document', 'get')
}

beforeEach(() => {
globalSpy.window.mockImplementation(
() => eventEmitter as unknown as TWindow
)
globalSpy.document.mockImplementation(
() => eventEmitter as unknown as TDocument
)

jest.resetModules()
})

afterEach(() => {
globalSpy.window.mockClear()
globalSpy.document.mockClear()
})

it('Should trigger focus event', () => {
const mockFn = jest.fn()

// eslint-disable-next-line @typescript-eslint/no-var-requires
const { initRevalidateOnFocus } = require('../src/middleware/persist')

const revalidateOnFocus = initRevalidateOnFocus(mockFn)

// Trigger focus event
eventEmitter.emit(FOCUS_EVENT)

// Remove focus event from window
revalidateOnFocus()

eventEmitter.emit(FOCUS_EVENT)

expect(mockFn).toBeCalledTimes(1)
})

it('Should trigger visibilitychange event', () => {
const mockFn = jest.fn()

globalSpy.window.mockImplementation(
() => eventEmitter as unknown as TWindow
)
globalSpy.document.mockImplementation(
() => eventEmitter as unknown as TDocument
)

// eslint-disable-next-line @typescript-eslint/no-var-requires
const { initRevalidateOnFocus } = require('../src/middleware/persist')

const revalidateOnFocus = initRevalidateOnFocus(mockFn)

// Trigger visibilitychange event
eventEmitter.emit(VISIBILITYCHANGE_EVENT)

// Remove visibilitychange event from document
revalidateOnFocus()

eventEmitter.emit(VISIBILITYCHANGE_EVENT)

expect(mockFn).toBeCalledTimes(1)
})
})
140 changes: 76 additions & 64 deletions __tests__/persist.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { createStore, Store } from '../src'
import { SYMBOL_PERSIST } from '../src/utils/constants'
import { persist } from '../src/middleware/persist'

const createMockedStorage = (persistedState: Record<string, any> | null) => {
let state = persistedState
const createMockedStorage = (persistedState: { counter: number }) => {
type State = typeof persistedState
let state: State | null = persistedState

return {
getItem: jest.fn(() => {
if (!state) return null
return JSON.stringify(state)
return state
}),
setItem: jest.fn(),
removeItem: jest.fn(() => {
Expand All @@ -33,6 +35,16 @@ describe('Persist', () => {
expect(persist).toBeInstanceOf(Function)
})

it('Should init the middleware with the storage by default', () => {
persist({ name: storeName })(store)

expect(store.persist.name).toBe(storeName)
expect(store.persist.hydrated()).toEqual(true)
expect(store.persist.$$persist).toBe(SYMBOL_PERSIST)
expect(store.persist.destroy).toBeInstanceOf(Function)
expect(store.persist.rehydrate).toBeInstanceOf(Function)
})

it('Should init the middleware once it is loaded', () => {
persist({ name: storeName, storage })(store)

Expand All @@ -45,7 +57,8 @@ describe('Persist', () => {

it('Should init the middleware with a custom stogare', () => {
const customStorage = (() => {
let state = persistedState
type State = typeof persistedState
let state: State | null = persistedState
return {
getItem: jest.fn(() => state),
setItem: jest.fn(),
Expand All @@ -55,7 +68,7 @@ describe('Persist', () => {
}
})()

persist({ name: storeName, storage: () => customStorage })(store)
persist({ name: storeName, storage: customStorage })(store)

store.setState(() => ({ counter: 2 }))

Expand All @@ -69,22 +82,12 @@ describe('Persist', () => {
expect(store.getState()).toEqual({ counter: 2 })
})

it('Should init the middleware with the storage by default', () => {
persist({ name: storeName })(store)

expect(store.persist.name).toBe(storeName)
expect(store.persist.hydrated()).toEqual(true)
expect(store.persist.$$persist).toBe(SYMBOL_PERSIST)
expect(store.persist.destroy).toBeInstanceOf(Function)
expect(store.persist.rehydrate).toBeInstanceOf(Function)
})

it('Should hydrate the store with the persisted store', () => {
persist({ name: storeName, storage })(store)

expect(storage.getItem).toBeCalledTimes(1)
expect(storage.getItem).toHaveBeenCalledWith(storeName)
expect(storage.getItem).toHaveReturnedWith(JSON.stringify(persistedState))
expect(storage.getItem).toHaveReturnedWith(persistedState)
expect(store.getState()).toEqual(persistedState)
})

Expand All @@ -103,16 +106,12 @@ describe('Persist', () => {
store.setState(() => ({ counter: 2 }))

expect(storage.getItem).toBeCalledTimes(1)
expect(storage.setItem).toHaveBeenNthCalledWith(
1,
storeName,
JSON.stringify({ counter: 1 })
)
expect(storage.setItem).toHaveBeenNthCalledWith(
2,
storeName,
JSON.stringify({ counter: 2 })
)
expect(storage.setItem).toHaveBeenNthCalledWith(1, storeName, {
counter: 1
})
expect(storage.setItem).toHaveBeenNthCalledWith(2, storeName, {
counter: 2
})
expect(store.getState()).toEqual({ counter: 2 })
})

Expand All @@ -128,49 +127,62 @@ describe('Persist', () => {
expect(storage.getItem()).toBe(null)
})

it('Should throw an error when name store is empty', () => {
try {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
persist({ storage })(store)
throw new Error('Should fail because the name store is empty')
} catch (e: any) {
expect(e.message).toBe('Provide a name to persist your store.')
}
it('Should print an error when name store is empty', () => {
const logSpy = jest.spyOn(console, 'error')

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
persist()(store)

expect(logSpy).toHaveBeenCalledTimes(1)
expect(logSpy).toHaveBeenCalledWith(
'[Killa Persist] Provide a name to persist your store.'
)
logSpy.mockRestore()
})

it('Should throw an error when storage is empty', () => {
try {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
persist({ name: storeName, storage: () => null })(store)
throw new Error('Should fail because the storage is empty')
} catch (e: any) {
expect(e.message).toBe('Provide a storage to persist your store.')
}
it('Should print an error when name storage is empty', () => {
const logSpy = jest.spyOn(console, 'error')

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
persist({ name: storeName, storage: null })(store)

expect(logSpy).toHaveBeenCalledTimes(1)
expect(logSpy).toHaveBeenCalledWith(
'[Killa Persist] Provide a storage to persist your store.'
)

logSpy.mockRestore()
})

it('Should throw an error when killa store is invalid', () => {
try {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
persist({ name: storeName, storage })({})
throw new Error('Should fail because the valid killa store is invalid')
} catch (e: any) {
expect(e.message).toBe(
'Provide a valid killa store to persist your store.'
)
}
it('Should print an error when killa store is invalid', () => {
const logSpy = jest.spyOn(console, 'error')

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
persist({ name: storeName, storage })({})

expect(logSpy).toHaveBeenCalledTimes(1)
expect(logSpy).toHaveBeenCalledWith(
'[Killa Persist] Provide a valid killa store to persist your store.'
)

logSpy.mockRestore()
})

it('Shpuld throw an error when the custom storage is invalid', () => {
try {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
persist({ name: storeName, storage: () => AsyncStore })(store)
throw new Error('Should fail because the custom storage is invalid')
} catch (e: any) {
expect(e.message).toBe('Provide a storage to persist your store.')
}
it('Should print an error when the custom storage is invalid', () => {
const logSpy = jest.spyOn(console, 'error')

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
persist({ name: storeName, storage: () => AsyncStore })(store)

expect(logSpy).toHaveBeenCalledTimes(1)
expect(logSpy).toHaveBeenCalledWith(
'[Killa Persist] Provide a storage to persist your store.'
)

logSpy.mockRestore()
})
})
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 454b90e

Please sign in to comment.