[web] Add compilation indicator favicon (#25990)
* Import changes from Hackathon https://github.com/overleaf/internal/pull/24501 * Update compile status: allow errors * Update favicons. Use the ones from Figma * Optimize and reuse path from favicon.svg * Clear status favicon after 5s on active tab * Rename hook from useCompileNotification to useStatusFavicon * Add tests * Revert changes to favicon.svg * Query favicon on document.head GitOrigin-RevId: 3972b1981abaf6c80273e0ed5b1bc05eb51bd689
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
import { expect } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import { renderHook } from '@testing-library/react'
|
||||
import * as CompileContext from '@/shared/context/detach-compile-context'
|
||||
|
||||
import { useStatusFavicon } from '@/features/ide-react/hooks/use-status-favicon'
|
||||
|
||||
type Compilation = { uncompiled: boolean; compiling: boolean; error: boolean }
|
||||
|
||||
describe('useStatusFavicon', function () {
|
||||
let mockUseDetachCompileContext: sinon.SinonStub
|
||||
let clock: sinon.SinonFakeTimers
|
||||
let originalHidden: PropertyDescriptor | undefined
|
||||
|
||||
const setCompilation = (compileContext: Compilation) => {
|
||||
mockUseDetachCompileContext.returns(compileContext)
|
||||
}
|
||||
const setHidden = (hidden: boolean) => {
|
||||
Object.defineProperty(document, 'hidden', {
|
||||
writable: true,
|
||||
configurable: true,
|
||||
value: hidden,
|
||||
})
|
||||
document.dispatchEvent(new Event('visibilitychange'))
|
||||
}
|
||||
|
||||
const getFaviconElements = () =>
|
||||
document.querySelectorAll('link[data-compile-status="true"]')
|
||||
|
||||
const getCurrentFaviconHref = () => {
|
||||
const favicon = document.querySelector(
|
||||
'link[data-compile-status="true"]'
|
||||
) as HTMLLinkElement
|
||||
return favicon?.href || null
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockUseDetachCompileContext = sinon.stub(
|
||||
CompileContext,
|
||||
'useDetachCompileContext'
|
||||
)
|
||||
|
||||
// Mock timers for timeout testing
|
||||
clock = sinon.useFakeTimers()
|
||||
|
||||
// Store original document.hidden descriptor
|
||||
originalHidden = Object.getOwnPropertyDescriptor(
|
||||
Document.prototype,
|
||||
'hidden'
|
||||
)
|
||||
|
||||
// Clean up any existing favicon elements
|
||||
document
|
||||
.querySelectorAll('link[data-compile-status="true"]')
|
||||
.forEach(el => el.remove())
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
sinon.restore()
|
||||
clock.restore()
|
||||
|
||||
// Restore original document.hidden
|
||||
if (originalHidden) {
|
||||
Object.defineProperty(document, 'hidden', originalHidden)
|
||||
}
|
||||
|
||||
// Clean up favicon elements
|
||||
document
|
||||
.querySelectorAll('link[data-compile-status="true"]')
|
||||
.forEach(el => el.remove())
|
||||
})
|
||||
|
||||
it('updates favicon to reflect status: UNCOMPILED', function () {
|
||||
setCompilation({ uncompiled: true, compiling: false, error: false })
|
||||
renderHook(() => useStatusFavicon())
|
||||
expect(getCurrentFaviconHref()).to.include('/favicon.svg')
|
||||
})
|
||||
|
||||
it('updates favicon to reflect status: COMPILING', function () {
|
||||
setCompilation({ uncompiled: false, compiling: true, error: false })
|
||||
renderHook(() => useStatusFavicon())
|
||||
expect(getCurrentFaviconHref()).to.include('/favicon-compiling.svg')
|
||||
})
|
||||
|
||||
it('updates favicon to reflect status: COMPILED', function () {
|
||||
setCompilation({ uncompiled: false, compiling: false, error: false })
|
||||
renderHook(() => useStatusFavicon())
|
||||
expect(getCurrentFaviconHref()).to.include('/favicon-compiled.svg')
|
||||
})
|
||||
|
||||
it('updates favicon to reflect status: ERROR', function () {
|
||||
setCompilation({ uncompiled: false, compiling: false, error: true })
|
||||
renderHook(() => useStatusFavicon())
|
||||
expect(getCurrentFaviconHref()).to.include('/favicon-error.svg')
|
||||
})
|
||||
|
||||
it('keeps the COMPILED favicon for 5 seconds when the window is active', function () {
|
||||
setCompilation({ uncompiled: false, compiling: false, error: false })
|
||||
const { rerender } = renderHook(() => useStatusFavicon())
|
||||
setHidden(false)
|
||||
rerender()
|
||||
expect(getCurrentFaviconHref()).to.include('/favicon-compiled.svg')
|
||||
clock.tick(4500)
|
||||
expect(getCurrentFaviconHref()).to.include('/favicon-compiled.svg')
|
||||
clock.tick(1000)
|
||||
expect(getCurrentFaviconHref()).to.include('/favicon.svg')
|
||||
})
|
||||
|
||||
it('keeps the COMPILED favicon forever when the window is hidden', function () {
|
||||
setCompilation({ uncompiled: false, compiling: false, error: false })
|
||||
const { rerender } = renderHook(() => useStatusFavicon())
|
||||
setHidden(true)
|
||||
rerender()
|
||||
|
||||
expect(getCurrentFaviconHref()).to.include('/favicon-compiled.svg')
|
||||
clock.tick(90000)
|
||||
expect(getCurrentFaviconHref()).to.include('/favicon-compiled.svg')
|
||||
})
|
||||
|
||||
it('should only have one favicon element at a time', function () {
|
||||
setCompilation({ uncompiled: true, compiling: false, error: false })
|
||||
const { rerender } = renderHook(() => useStatusFavicon())
|
||||
expect(getFaviconElements()).to.have.length(1)
|
||||
expect(getCurrentFaviconHref()).to.include('/favicon.svg')
|
||||
|
||||
setCompilation({ uncompiled: false, compiling: true, error: false })
|
||||
rerender()
|
||||
expect(getFaviconElements()).to.have.length(1)
|
||||
expect(getCurrentFaviconHref()).to.include('/favicon-compiling.svg')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user