Skip to content

Instantly share code, notes, and snippets.

@simonrelet
Created October 31, 2019 13:43
Show Gist options
  • Save simonrelet/e1e6a244dbef5f654ac309ad692c6bff to your computer and use it in GitHub Desktop.
Save simonrelet/e1e6a244dbef5f654ac309ad692c6bff to your computer and use it in GitHub Desktop.
import invariant from 'invariant'
import warning from 'warning'
function identity(state) {
return state
}
function parse(serializedJSON) {
if (serializedJSON) {
try {
return JSON.parse(serializedJSON)
} catch (e) {
warning(false, 'Could not parse stored item.')
}
}
return null
}
function load({ name, upgrader, version }) {
return () => {
const previousStorage = parse(localStorage.getItem(name))
if (previousStorage) {
return previousStorage.version < version
? upgrader(previousStorage.data, previousStorage.version)
: previousStorage.data
}
return null
}
}
function save({ name, version }) {
return data => {
localStorage.setItem(name, JSON.stringify({ version, data }))
}
}
function clear({ name }) {
return () => {
localStorage.removeItem(name)
}
}
function open(name, version, upgrader = identity) {
invariant(name, 'A storage name is required.')
invariant(version != null, 'A storage version is required.')
const context = { name, version, upgrader }
return {
load: load(context),
save: save(context),
clear: clear(context),
}
}
function clearAll() {
localStorage.clear()
}
export default { open, clearAll }
import Storages from './Storages'
const storageName = 'name'
const data = { a: 0, b: 1 }
const upgradedData = { c: 2, d: 3 }
function resetLocalStorage() {
localStorage.clear()
console.error.mockReset()
}
describe('Storages', () => {
beforeEach(resetLocalStorage)
afterAll(resetLocalStorage)
it('should return null when there are no stored data', () => {
expect(Storages.open(storageName, 1).load()).toBeNull()
})
it('should save data by name', () => {
Storages.open(storageName, 2).save(data)
expect(localStorage.getItem(storageName)).toBeDefined()
})
it('should load data by name', () => {
const storage = Storages.open(storageName, 1)
storage.save(data)
expect(storage.load()).toEqual(data)
})
it('should upgrade the data', () => {
Storages.open(storageName, 1).save(data)
const upgrader = jest.fn(() => upgradedData)
const loadedData = Storages.open(storageName, 3, upgrader).load()
expect(upgrader).toHaveBeenCalledWith(data, 1)
expect(loadedData).toEqual(upgradedData)
})
it('should not upgrade the data by default', () => {
Storages.open(storageName, 1).save(data)
const loadedData = Storages.open(storageName, 3).load()
expect(loadedData).toEqual(data)
})
it('should not invoke the upgrader for same versions', () => {
Storages.open(storageName, 1).save(data)
const upgrader = jest.fn()
const loadedData = Storages.open(storageName, 1, upgrader).load()
expect(upgrader).not.toHaveBeenCalled()
expect(loadedData).toEqual(data)
})
it('should clear a storage', () => {
const storage = Storages.open(storageName, 1)
storage.save(data)
storage.clear()
expect(storage.load()).toBeNull()
expect(localStorage.getItem(storageName)).toBeNull()
})
it('should support multiple storages at the same time', () => {
const storage1 = Storages.open(storageName, 1)
const storage2 = Storages.open('name-2', 1)
storage1.save(data)
storage2.save(upgradedData)
expect(storage2.load()).toEqual(upgradedData)
expect(storage1.load()).toEqual(data)
storage1.clear()
expect(storage2.load()).toEqual(upgradedData)
expect(storage1.load()).toBeNull()
})
it('should clear all storages', () => {
const storage1 = Storages.open(storageName, 1)
const storage2 = Storages.open('name-2', 1)
storage1.save(data)
storage2.save(upgradedData)
Storages.clearAll()
expect(storage1.load()).toBeNull()
expect(storage2.load()).toBeNull()
})
it('should return null for a corrupted storage', () => {
localStorage.setItem(storageName, "I'm not JSON")
expect(Storages.open(storageName, 1).load()).toBeNull()
expect(console.error).toHaveBeenCalledTimes(1)
})
it('should throw if a name is not specified', () => {
expect(() => Storages.open()).toThrow()
})
it('should throw if a version is not specified', () => {
expect(() => Storages.open(storageName)).toThrow()
})
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment