Created
October 31, 2019 13:43
-
-
Save simonrelet/e1e6a244dbef5f654ac309ad692c6bff to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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