import sinon from 'sinon' import { expect } from 'chai' import { useEffect } from 'react' import { render, screen } from '@testing-library/react' import usePersistedState from '../../../../frontend/js/shared/hooks/use-persisted-state' import localStorage from '../../../../frontend/js/infrastructure/local-storage' describe('usePersistedState', function () { beforeEach(function () { sinon.spy(window.Storage.prototype, 'getItem') sinon.spy(window.Storage.prototype, 'removeItem') sinon.spy(window.Storage.prototype, 'setItem') }) afterEach(function () { sinon.restore() }) it('reads the value from localStorage', function () { const key = 'test' localStorage.setItem(key, 'foo') expect(window.Storage.prototype.setItem).to.have.callCount(1) const Test = () => { const [value] = usePersistedState(key) return
{value}
} render() screen.getByText('foo') expect(window.Storage.prototype.getItem).to.have.callCount(1) expect(window.Storage.prototype.removeItem).to.have.callCount(0) expect(window.Storage.prototype.setItem).to.have.callCount(1) expect(localStorage.getItem(key)).to.equal('foo') }) it('uses the default value without storing anything', function () { const key = 'test:default' const Test = () => { const [value] = usePersistedState(key, 'foo') return
{value}
} render() screen.getByText('foo') expect(window.Storage.prototype.getItem).to.have.callCount(1) expect(window.Storage.prototype.removeItem).to.have.callCount(0) expect(window.Storage.prototype.setItem).to.have.callCount(0) expect(localStorage.getItem(key)).to.be.null }) it('stores the new value in localStorage', function () { const key = 'test:store' localStorage.setItem(key, 'foo') expect(window.Storage.prototype.setItem).to.have.callCount(1) const Test = () => { const [value, setValue] = usePersistedState(key, 'bar') useEffect(() => { setValue('baz') }, [setValue]) return
{value}
} render() screen.getByText('baz') expect(window.Storage.prototype.getItem).to.have.callCount(1) expect(window.Storage.prototype.removeItem).to.have.callCount(0) expect(window.Storage.prototype.setItem).to.have.callCount(2) expect(localStorage.getItem(key)).to.equal('baz') }) it('removes the value from localStorage if it equals the default value', function () { const key = 'test:store-default' localStorage.setItem(key, 'foo') expect(window.Storage.prototype.setItem).to.have.callCount(1) const Test = () => { const [value, setValue] = usePersistedState(key, 'bar') useEffect(() => { // set a different value setValue('baz') expect(localStorage.getItem(key)).to.equal('baz') // set the default value again setValue('bar') }, [setValue]) return
{value}
} render() screen.getByText('bar') expect(window.Storage.prototype.getItem).to.have.callCount(2) expect(window.Storage.prototype.removeItem).to.have.callCount(1) expect(window.Storage.prototype.setItem).to.have.callCount(2) expect(localStorage.getItem(key)).to.be.null }) it('handles function values', function () { const key = 'test:store' localStorage.setItem(key, 'foo') expect(window.Storage.prototype.setItem).to.have.callCount(1) const Test = () => { const [value, setValue] = usePersistedState(key) useEffect(() => { setValue(value => value + 'bar') }, [setValue]) return
{value}
} render() screen.getByText('foobar') expect(window.Storage.prototype.getItem).to.have.callCount(1) expect(window.Storage.prototype.removeItem).to.have.callCount(0) expect(window.Storage.prototype.setItem).to.have.callCount(2) expect(localStorage.getItem(key)).to.equal('foobar') }) it('handles syncing values via storage event', async function () { const key = 'test:sync' localStorage.setItem(key, 'foo') expect(window.Storage.prototype.setItem).to.have.callCount(1) // listen for storage events const storageEventListener = sinon.stub() window.addEventListener('storage', storageEventListener) const Test = () => { const [value, setValue] = usePersistedState(key, 'bar', true) useEffect(() => { setValue('baz') }, [setValue]) return
{value}
} render() screen.getByText('baz') expect(window.Storage.prototype.getItem).to.have.callCount(1) expect(window.Storage.prototype.removeItem).to.have.callCount(0) expect(window.Storage.prototype.setItem).to.have.callCount(2) expect(localStorage.getItem(key)).to.equal('baz') expect(storageEventListener).to.have.callCount(0) // set the new value in localStorage localStorage.setItem(key, 'cat') // dispatch a "storage" event and check that it's picked up by the hook window.dispatchEvent(new StorageEvent('storage', { key })) await screen.findByText('cat') expect(storageEventListener).to.have.callCount(1) }) })