use-persisted-state.test.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. import sinon from 'sinon'
  2. import { expect } from 'chai'
  3. import { useEffect } from 'react'
  4. import { render, screen } from '@testing-library/react'
  5. import usePersistedState from '../../../../frontend/js/shared/hooks/use-persisted-state'
  6. import localStorage from '@/infrastructure/local-storage'
  7. describe('usePersistedState', function () {
  8. beforeEach(function () {
  9. sinon.spy(window.Storage.prototype, 'getItem')
  10. sinon.spy(window.Storage.prototype, 'removeItem')
  11. sinon.spy(window.Storage.prototype, 'setItem')
  12. })
  13. afterEach(function () {
  14. sinon.restore()
  15. })
  16. it('reads the value from localStorage', function () {
  17. const key = 'test'
  18. localStorage.setItem(key, 'foo')
  19. expect(window.Storage.prototype.setItem).to.have.callCount(1)
  20. const Test = () => {
  21. const [value] = usePersistedState(key)
  22. return <div>{value}</div>
  23. }
  24. render(<Test />)
  25. screen.getByText('foo')
  26. expect(window.Storage.prototype.getItem).to.have.callCount(1)
  27. expect(window.Storage.prototype.removeItem).to.have.callCount(0)
  28. expect(window.Storage.prototype.setItem).to.have.callCount(1)
  29. expect(localStorage.getItem(key)).to.equal('foo')
  30. })
  31. it('uses the default value without storing anything', function () {
  32. const key = 'test:default'
  33. const Test = () => {
  34. const [value] = usePersistedState(key, 'foo')
  35. return <div>{value}</div>
  36. }
  37. render(<Test />)
  38. screen.getByText('foo')
  39. expect(window.Storage.prototype.getItem).to.have.callCount(1)
  40. expect(window.Storage.prototype.removeItem).to.have.callCount(0)
  41. expect(window.Storage.prototype.setItem).to.have.callCount(0)
  42. expect(localStorage.getItem(key)).to.be.null
  43. })
  44. it('stores the new value in localStorage', function () {
  45. const key = 'test:store'
  46. localStorage.setItem(key, 'foo')
  47. expect(window.Storage.prototype.setItem).to.have.callCount(1)
  48. const Test = () => {
  49. const [value, setValue] = usePersistedState(key, 'bar')
  50. useEffect(() => {
  51. setValue('baz')
  52. }, [setValue])
  53. return <div>{value}</div>
  54. }
  55. render(<Test />)
  56. screen.getByText('baz')
  57. expect(window.Storage.prototype.getItem).to.have.callCount(1)
  58. expect(window.Storage.prototype.removeItem).to.have.callCount(0)
  59. expect(window.Storage.prototype.setItem).to.have.callCount(2)
  60. expect(localStorage.getItem(key)).to.equal('baz')
  61. })
  62. it('removes the value from localStorage if it equals the default value', function () {
  63. const key = 'test:store-default'
  64. localStorage.setItem(key, 'foo')
  65. expect(window.Storage.prototype.setItem).to.have.callCount(1)
  66. const Test = () => {
  67. const [value, setValue] = usePersistedState(key, 'bar')
  68. useEffect(() => {
  69. // set a different value
  70. setValue('baz')
  71. expect(localStorage.getItem(key)).to.equal('baz')
  72. // set the default value again
  73. setValue('bar')
  74. }, [setValue])
  75. return <div>{value}</div>
  76. }
  77. render(<Test />)
  78. screen.getByText('bar')
  79. expect(window.Storage.prototype.getItem).to.have.callCount(2)
  80. expect(window.Storage.prototype.removeItem).to.have.callCount(1)
  81. expect(window.Storage.prototype.setItem).to.have.callCount(2)
  82. expect(localStorage.getItem(key)).to.be.null
  83. })
  84. it('handles function values', function () {
  85. const key = 'test:store'
  86. localStorage.setItem(key, 'foo')
  87. expect(window.Storage.prototype.setItem).to.have.callCount(1)
  88. const Test = () => {
  89. const [value, setValue] = usePersistedState<string>(key)
  90. useEffect(() => {
  91. setValue(value => value + 'bar')
  92. }, [setValue])
  93. return <div>{value}</div>
  94. }
  95. render(<Test />)
  96. screen.getByText('foobar')
  97. expect(window.Storage.prototype.getItem).to.have.callCount(1)
  98. expect(window.Storage.prototype.removeItem).to.have.callCount(0)
  99. expect(window.Storage.prototype.setItem).to.have.callCount(2)
  100. expect(localStorage.getItem(key)).to.equal('foobar')
  101. })
  102. it('handles syncing values via storage event', async function () {
  103. const key = 'test:sync'
  104. localStorage.setItem(key, 'foo')
  105. expect(window.Storage.prototype.setItem).to.have.callCount(1)
  106. // listen for storage events
  107. const storageEventListener = sinon.stub()
  108. window.addEventListener('storage', storageEventListener)
  109. const Test = () => {
  110. const [value, setValue] = usePersistedState(key, 'bar', true)
  111. useEffect(() => {
  112. setValue('baz')
  113. }, [setValue])
  114. return <div>{value}</div>
  115. }
  116. render(<Test />)
  117. screen.getByText('baz')
  118. expect(window.Storage.prototype.getItem).to.have.callCount(1)
  119. expect(window.Storage.prototype.removeItem).to.have.callCount(0)
  120. expect(window.Storage.prototype.setItem).to.have.callCount(2)
  121. expect(localStorage.getItem(key)).to.equal('baz')
  122. expect(storageEventListener).to.have.callCount(0)
  123. // set the new value in localStorage
  124. localStorage.setItem(key, 'cat')
  125. // dispatch a "storage" event and check that it's picked up by the hook
  126. window.dispatchEvent(new StorageEvent('storage', { key }))
  127. await screen.findByText('cat')
  128. expect(storageEventListener).to.have.callCount(1)
  129. })
  130. })