-
Notifications
You must be signed in to change notification settings - Fork 0
add localstorage-writable #81
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
Conversation
Co-authored-by: Benjamin Strasser <55442329+benjaminstrasser@users.noreply.github.com>
894482f to
eb13488
Compare
benjaminstrasser
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wrote some unit tests, which also identified an issue, where localStorageWritable(key, defaultValue) adds the value to the store but does not insert the default value into localstorage.
import { localStorageWritable } from './localstorage-writable'
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { get } from 'svelte/store'
const mockedBrowser = vi.hoisted(() => {
return vi.fn()
})
vi.mock('$app/environment', () => {
return {
get browser() {
return mockedBrowser()
}
}
})
const localStorageMockFactory = () => {
let store: Record<string, string> = {}
return {
getItem: (key: string) => store[key] || null,
setItem: (key: string, value: string) => {
store[key] = value
},
removeItem: (key: string) => {
delete store[key]
}
}
}
describe('localStorageWritable', () => {
beforeEach(() => {
mockedBrowser.mockReturnValue(true)
vi.stubGlobal('localStorage', localStorageMockFactory())
})
afterEach(() => {
vi.resetAllMocks()
})
it('should initialize with default value if no value in localStorage', () => {
const defaultValue = 'default'
const store = localStorageWritable('testKey', defaultValue)
expect(get(store)).toBe(defaultValue)
expect(localStorage.getItem('testKey')).toBe(JSON.stringify(defaultValue))
})
it('should initialize with value from localStorage if present', () => {
localStorage.setItem('testKey', JSON.stringify('storedValue'))
const store = localStorageWritable('testKey')
expect(get(store)).toBe('storedValue')
})
it('should prioritize value from local storage over default', () => {
localStorage.setItem('testKey', JSON.stringify('storedValue'))
const store = localStorageWritable('testKey', 'defaultValue')
expect(get(store)).toBe('storedValue')
expect(localStorage.getItem('testKey')).toBe(JSON.stringify('storedValue'))
})
it('should store value in localStorage when set', () => {
const store = localStorageWritable('testKey')
store.set('newValue')
expect(localStorage.getItem('testKey')).toBe(JSON.stringify('newValue'))
expect(get(store)).toBe('newValue')
})
it('should remove value from localStorage when wipe is called', () => {
localStorage.setItem('testKey', JSON.stringify('storedValue'))
const store = localStorageWritable('testKey')
store.wipe()
expect(localStorage.getItem('testKey')).toBe(null)
expect(get(store)).toBe(null)
})
it('should update the value correctly using update method', () => {
const store = localStorageWritable<number>('testKey', 1)
store.update((n) => n! + 1)
expect(localStorage.getItem('testKey')).toBe(JSON.stringify(2))
expect(get(store)).toBe(2)
})
})
describe('localStorageWritable with no browser', () => {
beforeEach(() => {
mockedBrowser.mockReturnValue(false)
vi.stubGlobal('localStorage', undefined)
})
afterEach(() => {
vi.resetAllMocks()
})
it('should initialize with default value', () => {
const defaultValue = 'default'
const store = localStorageWritable('testKey', defaultValue)
expect(get(store)).toBe(defaultValue)
})
it('should initialize with null', () => {
const store = localStorageWritable('testKey')
expect(get(store)).toBe(null)
})
it('should set value', () => {
const store = localStorageWritable('testKey')
store.set('newValue')
expect(get(store)).toBe('newValue')
})
it('should remove value', () => {
const store = localStorageWritable('testKey')
store.set('newValue')
store.wipe()
expect(get(store)).toBe(null)
})
it('should update the value correctly using update method', () => {
const store = localStorageWritable<number>('testKey', 1)
store.update((n) => n! + 1)
expect(get(store)).toBe(2)
})
})|
For what kind of use cases are we going to utilize this? |
I think it is intended for #77 |
|
@benjaminstrasser Pls directly push the unit-tests, I'll look at the issue (but ofc feel free to add a fix directly, if you have one) |
Right, it's intended for the other PR, but it comes in handy pretty often. Might be some things and local configs like dark mode settings, preferences, undo history, recent search keywords, ... |
Signed-off-by: Benjamin Strasser <bp.strasser@gmail.com>
Signed-off-by: Benjamin Strasser <bp.strasser@gmail.com>
54a4f33 to
f418270
Compare
mledl
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice utility and good testing 🚀
| const storedValue = browser ? localStorage.getItem(localStorageKey) : null | ||
| const localStorageValue = (() => { | ||
| try { | ||
| return storedValue !== null ? JSON.parse(storedValue) : null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@benjaminstrasser the check here is redundant.
JSON.parse(null) === null
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes you are correct! I caught that too. Problem is JSON.parse is typed as parse(value: string) and not parse(value: string | null) even though parse(null) === null.
See here: #81 (comment)
Adds a fully compatible custom store that persists all values of the writable to local storage.
Caveat is of course that the value has to be serializable with JSON. An alternative might be to use devalue?