use-status-favicon.test.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import { expect } from 'chai'
  2. import sinon from 'sinon'
  3. import { renderHook } from '@testing-library/react'
  4. import * as CompileContext from '@/shared/context/detach-compile-context'
  5. import { useStatusFavicon } from '@/features/ide-react/hooks/use-status-favicon'
  6. type Compilation = { uncompiled: boolean; compiling: boolean; error: boolean }
  7. describe('useStatusFavicon', function () {
  8. let mockUseDetachCompileContext: sinon.SinonStub
  9. let clock: sinon.SinonFakeTimers
  10. let originalHidden: PropertyDescriptor | undefined
  11. const setCompilation = (compileContext: Compilation) => {
  12. mockUseDetachCompileContext.returns(compileContext)
  13. }
  14. const setHidden = (hidden: boolean) => {
  15. Object.defineProperty(document, 'hidden', {
  16. writable: true,
  17. configurable: true,
  18. value: hidden,
  19. })
  20. document.dispatchEvent(new Event('visibilitychange'))
  21. }
  22. const getFaviconElements = () =>
  23. document.querySelectorAll('link[data-compile-status="true"]')
  24. const getCurrentFaviconHref = () => {
  25. const favicon = document.querySelector(
  26. 'link[data-compile-status="true"]'
  27. ) as HTMLLinkElement
  28. return favicon?.href || null
  29. }
  30. beforeEach(function () {
  31. mockUseDetachCompileContext = sinon.stub(
  32. CompileContext,
  33. 'useDetachCompileContext'
  34. )
  35. // Mock timers for timeout testing
  36. clock = sinon.useFakeTimers()
  37. // Store original document.hidden descriptor
  38. originalHidden = Object.getOwnPropertyDescriptor(
  39. Document.prototype,
  40. 'hidden'
  41. )
  42. // Clean up any existing favicon elements
  43. document
  44. .querySelectorAll('link[data-compile-status="true"]')
  45. .forEach(el => el.remove())
  46. window.metaAttributesCache.set(
  47. 'ol-baseAssetPath',
  48. 'https://cdn.test-overleaf.com/'
  49. )
  50. })
  51. afterEach(function () {
  52. sinon.restore()
  53. clock.restore()
  54. // Restore original document.hidden
  55. if (originalHidden) {
  56. Object.defineProperty(document, 'hidden', originalHidden)
  57. }
  58. // Clean up favicon elements
  59. document
  60. .querySelectorAll('link[data-compile-status="true"]')
  61. .forEach(el => el.remove())
  62. })
  63. it('updates favicon to reflect status: UNCOMPILED', function () {
  64. setCompilation({ uncompiled: true, compiling: false, error: false })
  65. renderHook(() => useStatusFavicon())
  66. expect(getCurrentFaviconHref()).to.include('/favicon.svg')
  67. })
  68. it('updates favicon to reflect status: COMPILING', function () {
  69. setCompilation({ uncompiled: false, compiling: true, error: false })
  70. renderHook(() => useStatusFavicon())
  71. expect(getCurrentFaviconHref()).to.include('/favicon-compiling.svg')
  72. })
  73. it('updates favicon to reflect status: COMPILED', function () {
  74. setCompilation({ uncompiled: false, compiling: false, error: false })
  75. renderHook(() => useStatusFavicon())
  76. expect(getCurrentFaviconHref()).to.include('/favicon-compiled.svg')
  77. })
  78. it('updates favicon to reflect status: ERROR', function () {
  79. setCompilation({ uncompiled: false, compiling: false, error: true })
  80. renderHook(() => useStatusFavicon())
  81. expect(getCurrentFaviconHref()).to.include('/favicon-error.svg')
  82. })
  83. it('keeps the COMPILED favicon for 5 seconds when the window is active', function () {
  84. setCompilation({ uncompiled: false, compiling: false, error: false })
  85. const { rerender } = renderHook(() => useStatusFavicon())
  86. setHidden(false)
  87. rerender()
  88. expect(getCurrentFaviconHref()).to.include('/favicon-compiled.svg')
  89. clock.tick(4500)
  90. expect(getCurrentFaviconHref()).to.include('/favicon-compiled.svg')
  91. clock.tick(1000)
  92. expect(getCurrentFaviconHref()).to.include('/favicon.svg')
  93. })
  94. it('keeps the COMPILED favicon forever when the window is hidden', function () {
  95. setCompilation({ uncompiled: false, compiling: false, error: false })
  96. const { rerender } = renderHook(() => useStatusFavicon())
  97. setHidden(true)
  98. rerender()
  99. expect(getCurrentFaviconHref()).to.include('/favicon-compiled.svg')
  100. clock.tick(90000)
  101. expect(getCurrentFaviconHref()).to.include('/favicon-compiled.svg')
  102. })
  103. it('should only have one favicon element at a time', function () {
  104. setCompilation({ uncompiled: true, compiling: false, error: false })
  105. const { rerender } = renderHook(() => useStatusFavicon())
  106. expect(getFaviconElements()).to.have.length(1)
  107. expect(getCurrentFaviconHref()).to.include('/favicon.svg')
  108. setCompilation({ uncompiled: false, compiling: true, error: false })
  109. rerender()
  110. expect(getFaviconElements()).to.have.length(1)
  111. expect(getCurrentFaviconHref()).to.include('/favicon-compiling.svg')
  112. })
  113. })