Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 32 additions & 32 deletions frontend/__tests__/unit/components/AnchorTitle.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ const setupAnimationFrameForHash = (cb: (time: number) => void) => {
describe('AnchorTitle Component', () => {
afterEach(() => {
jest.restoreAllMocks()
window.history.replaceState(null, '', window.location.pathname)
globalThis.history.replaceState(null, '', globalThis.location.pathname)
})

describe('Basic Rendering', () => {
Expand Down Expand Up @@ -157,10 +157,10 @@ describe('AnchorTitle Component', () => {
let mockRequestAnimationFrame: jest.SpyInstance

const setupMocks = () => {
mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation()
mockPushState = jest.spyOn(window.history, 'pushState').mockImplementation()
mockScrollTo = jest.spyOn(globalThis, 'scrollTo').mockImplementation()
mockPushState = jest.spyOn(globalThis.history, 'pushState').mockImplementation()
mockRequestAnimationFrame = jest
.spyOn(window, 'requestAnimationFrame')
.spyOn(globalThis, 'requestAnimationFrame')
.mockImplementation(setupMockAnimationFrame)

mockGetBoundingClientRect = jest.fn(createMockBoundingClientRect)
Expand Down Expand Up @@ -232,9 +232,9 @@ describe('AnchorTitle Component', () => {
let mockRequestAnimationFrame: jest.SpyInstance

const setupUseEffectMocks = () => {
mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation()
mockScrollTo = jest.spyOn(globalThis, 'scrollTo').mockImplementation()
mockRequestAnimationFrame = jest
.spyOn(window, 'requestAnimationFrame')
.spyOn(globalThis, 'requestAnimationFrame')
.mockImplementation(setupAnimationFrameForUseEffect)

const mockElement = createMockElementForUseEffect()
Expand All @@ -253,7 +253,7 @@ describe('AnchorTitle Component', () => {
afterEach(cleanupUseEffectMocks)

it('scrolls to element on mount when hash matches', async () => {
window.location.hash = '#test-scroll'
globalThis.location.hash = '#test-scroll'

render(<AnchorTitle title="Test Scroll" />)

Expand All @@ -267,7 +267,7 @@ describe('AnchorTitle Component', () => {
})

it('does not scroll when hash does not match', () => {
window.location.hash = '#different-hash'
globalThis.location.hash = '#different-hash'

render(<AnchorTitle title="Test Scroll" />)

Expand All @@ -276,10 +276,10 @@ describe('AnchorTitle Component', () => {
it('handles popstate events correctly', async () => {
render(<AnchorTitle title="Popstate Test" />)

window.location.hash = '#popstate-test'
globalThis.location.hash = '#popstate-test'

const popstateEvent = new PopStateEvent('popstate')
fireEvent(window, popstateEvent)
fireEvent(globalThis as unknown as Window, popstateEvent)

await waitFor(() => {
expect(mockRequestAnimationFrame).toHaveBeenCalled()
Expand All @@ -288,7 +288,7 @@ describe('AnchorTitle Component', () => {
})

it('removes popstate event listener on unmount', () => {
const mockRemoveEventListener = jest.spyOn(window, 'removeEventListener')
const mockRemoveEventListener = jest.spyOn(globalThis, 'removeEventListener')

const { unmount } = render(<AnchorTitle title="Cleanup Test" />)
unmount()
Expand Down Expand Up @@ -347,7 +347,7 @@ describe('AnchorTitle Component', () => {
const mockGetElementById = jest
.spyOn(document, 'getElementById')
.mockReturnValue(mockElement as unknown as HTMLElement)
const mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation()
const mockScrollTo = jest.spyOn(globalThis, 'scrollTo').mockImplementation()

render(<AnchorTitle title="No Anchor" />)
const link = screen.getByRole('link')
Expand Down Expand Up @@ -381,8 +381,8 @@ describe('AnchorTitle Component', () => {
})

it('maintains focus management on interaction', () => {
const mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation()
const mockPushState = jest.spyOn(window.history, 'pushState').mockImplementation()
const mockScrollTo = jest.spyOn(globalThis, 'scrollTo').mockImplementation()
const mockPushState = jest.spyOn(globalThis.history, 'pushState').mockImplementation()

render(<AnchorTitle title="Focus Test" />)

Expand Down Expand Up @@ -418,8 +418,8 @@ describe('AnchorTitle Component', () => {
})

it('handles rapid successive clicks', () => {
const mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation()
const mockPushState = jest.spyOn(window.history, 'pushState').mockImplementation()
const mockScrollTo = jest.spyOn(globalThis, 'scrollTo').mockImplementation()
const mockPushState = jest.spyOn(globalThis.history, 'pushState').mockImplementation()

render(<AnchorTitle title="Rapid Click" />)
const link = screen.getByRole('link')
Expand Down Expand Up @@ -450,10 +450,10 @@ describe('AnchorTitle Component', () => {

describe('Browser API Interactions', () => {
it('handles window.pageYOffset correctly', () => {
const mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation()
const mockScrollTo = jest.spyOn(globalThis, 'scrollTo').mockImplementation()
const originalPageYOffset = window.pageYOffset

Object.defineProperty(window, 'pageYOffset', {
Object.defineProperty(globalThis, 'pageYOffset', {
value: 500,
configurable: true,
})
Expand All @@ -473,7 +473,7 @@ describe('AnchorTitle Component', () => {
behavior: 'smooth',
})

Object.defineProperty(window, 'pageYOffset', {
Object.defineProperty(globalThis, 'pageYOffset', {
value: originalPageYOffset,
configurable: true,
})
Expand All @@ -483,17 +483,17 @@ describe('AnchorTitle Component', () => {
})

it('handles hash changes in the URL correctly', async () => {
const mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation()
const mockScrollTo = jest.spyOn(globalThis, 'scrollTo').mockImplementation()
const mockRequestAnimationFrame = jest
.spyOn(window, 'requestAnimationFrame')
.spyOn(globalThis, 'requestAnimationFrame')
.mockImplementation(setupAnimationFrameForHash)

window.location.hash = ''
globalThis.location.hash = ''
render(<AnchorTitle title="Hash Test" />)

window.location.hash = '#hash-test'
globalThis.location.hash = '#hash-test'
const popstateEvent = new PopStateEvent('popstate')
fireEvent(window, popstateEvent)
fireEvent(globalThis as unknown as Window, popstateEvent)

await waitFor(() => {
expect(mockScrollTo).toHaveBeenCalled()
Expand All @@ -506,7 +506,7 @@ describe('AnchorTitle Component', () => {

describe('Performance and Optimization', () => {
it('uses useCallback for scrollToElement function', () => {
const mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation()
const mockScrollTo = jest.spyOn(globalThis, 'scrollTo').mockImplementation()

const { rerender } = render(<AnchorTitle title="Callback Test" />)
const link1 = screen.getByRole('link')
Expand All @@ -523,8 +523,8 @@ describe('AnchorTitle Component', () => {
})

it('cleans up event listeners properly', () => {
const mockAddEventListener = jest.spyOn(window, 'addEventListener')
const mockRemoveEventListener = jest.spyOn(window, 'removeEventListener')
const mockAddEventListener = jest.spyOn(globalThis, 'addEventListener')
const mockRemoveEventListener = jest.spyOn(globalThis, 'removeEventListener')

const { unmount } = render(<AnchorTitle title="Cleanup Test" />)

Expand All @@ -541,7 +541,7 @@ describe('AnchorTitle Component', () => {

describe('State Changes and Internal Logic', () => {
it('recalculates scroll position when element dimensions change', () => {
const mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation()
const mockScrollTo = jest.spyOn(globalThis, 'scrollTo').mockImplementation()

const offsetHeights = { current: 30 }
const mockElement = {
Expand Down Expand Up @@ -570,8 +570,8 @@ describe('AnchorTitle Component', () => {
})

it('handles component rerender with different IDs', () => {
const mockAddEventListener = jest.spyOn(window, 'addEventListener')
const mockRemoveEventListener = jest.spyOn(window, 'removeEventListener')
const mockAddEventListener = jest.spyOn(globalThis, 'addEventListener')
const mockRemoveEventListener = jest.spyOn(globalThis, 'removeEventListener')

const { rerender } = render(<AnchorTitle title="Original" />)
const originalAddCalls = mockAddEventListener.mock.calls.length
Expand Down Expand Up @@ -657,8 +657,8 @@ describe('AnchorTitle Component', () => {
.replace(/(^-{1,10}|-{1,10}$)/g, '')
)

const mockScrollTo = jest.spyOn(window, 'scrollTo').mockImplementation()
const mockPushState = jest.spyOn(window.history, 'pushState').mockImplementation()
const mockScrollTo = jest.spyOn(globalThis, 'scrollTo').mockImplementation()
const mockPushState = jest.spyOn(globalThis.history, 'pushState').mockImplementation()

render(<AnchorTitle title="User Journey" />)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ describe('AnimatedCounter', () => {

describe('Animation behavior', () => {
it('calls requestAnimationFrame during animation', () => {
const requestAnimationFrameSpy = jest.spyOn(window, 'requestAnimationFrame')
const requestAnimationFrameSpy = jest.spyOn(globalThis, 'requestAnimationFrame')
render(<AnimatedCounter end={100} duration={1} />)

expect(requestAnimationFrameSpy).toHaveBeenCalled()
Expand Down
2 changes: 1 addition & 1 deletion frontend/__tests__/unit/components/MultiSearch.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jest.mock('@fortawesome/react-fontawesome', () => ({

// Mock window.open globally
const mockWindowOpen = jest.fn()
Object.defineProperty(window, 'open', {
Object.defineProperty(globalThis, 'open', {
value: mockWindowOpen,
writable: true,
})
Expand Down
18 changes: 9 additions & 9 deletions frontend/__tests__/unit/components/ScrollToTop.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import ScrollToTop from 'components/ScrollToTop'

describe('ScrollToTop component test', () => {
beforeEach(() => {
window.scrollTo = jest.fn()
Object.defineProperty(window, 'scrollY', { value: 0, writable: true })
Object.defineProperty(window, 'innerHeight', { value: 1000, writable: true })
globalThis.scrollTo = jest.fn()
Object.defineProperty(globalThis, 'scrollY', { value: 0, writable: true })
Object.defineProperty(globalThis, 'innerHeight', { value: 1000, writable: true })
})

afterEach(() => {
Expand All @@ -24,27 +24,27 @@ describe('ScrollToTop component test', () => {
const { getByLabelText } = render(<ScrollToTop />)
const button = getByLabelText(/scroll to top/i)

Object.defineProperty(window, 'scrollY', { value: 400, writable: true })
window.dispatchEvent(new Event('scroll'))
Object.defineProperty(globalThis, 'scrollY', { value: 400, writable: true })
globalThis.dispatchEvent(new Event('scroll'))

await waitFor(() => {
expect(button).toHaveClass('opacity-100')
expect(button).toHaveClass('pointer-events-auto')
})
})

test('Clicking the button should call window.scrollTo with smooth behavior', async () => {
test('Clicking the button should call globalThis.scrollTo with smooth behavior', async () => {
const { getByLabelText } = render(<ScrollToTop />)
const button = getByLabelText(/scroll to top/i)

Object.defineProperty(window, 'scrollY', { value: 400, writable: true })
window.dispatchEvent(new Event('scroll'))
Object.defineProperty(globalThis, 'scrollY', { value: 400, writable: true })
globalThis.dispatchEvent(new Event('scroll'))

await waitFor(() => {
expect(button).toHaveClass('opacity-100')
})

fireEvent.click(button)
expect(window.scrollTo).toHaveBeenCalledWith({ top: 0, behavior: 'smooth' })
expect(globalThis.scrollTo).toHaveBeenCalledWith({ top: 0, behavior: 'smooth' })
})
})
6 changes: 3 additions & 3 deletions frontend/__tests__/unit/components/Search.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ describe('SearchBar Component', () => {

expect(sendGTMEvent).toHaveBeenCalledWith({
event: 'search',
path: window.location.pathname,
path: globalThis.location.pathname,
value: 'test',
})
})
Expand All @@ -203,7 +203,7 @@ describe('SearchBar Component', () => {
expect(mockOnSearch).toHaveBeenCalledWith('new query')
expect(sendGTMEvent).toHaveBeenCalledWith({
event: 'search',
path: window.location.pathname,
path: globalThis.location.pathname,
value: 'new query',
})

Expand All @@ -214,7 +214,7 @@ describe('SearchBar Component', () => {
expect(mockOnSearch).toHaveBeenCalledWith('change query')
expect(sendGTMEvent).toHaveBeenCalledWith({
event: 'search',
path: window.location.pathname,
path: globalThis.location.pathname,
value: 'change query',
})
})
Expand Down
6 changes: 3 additions & 3 deletions frontend/__tests__/unit/pages/Header.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ describe('Header Component', () => {
beforeEach(() => {
mockUsePathname.mockReturnValue('/')
// Mock window.innerWidth
Object.defineProperty(window, 'innerWidth', {
Object.defineProperty(globalThis, 'innerWidth', {
writable: true,
configurable: true,
value: 1024,
Expand Down Expand Up @@ -487,7 +487,7 @@ describe('Header Component', () => {

// Simulate resize event
await act(async () => {
window.dispatchEvent(new Event('resize'))
globalThis.dispatchEvent(new Event('resize'))
})

// Test passes if no errors are thrown
Expand Down Expand Up @@ -685,7 +685,7 @@ describe('Header Component', () => {

it('shows mobile menu button for mobile screens', () => {
// Set window width to simulate mobile
Object.defineProperty(window, 'innerWidth', {
Object.defineProperty(globalThis, 'innerWidth', {
writable: true,
configurable: true,
value: 400,
Expand Down
26 changes: 13 additions & 13 deletions frontend/jest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import '@testing-library/jest-dom'
import React from 'react'
import 'core-js/actual/structured-clone'

global.React = React
globalThis.React = React

// Add fetch polyfill for jsdom test environment
// Node.js 18+ has native fetch, but jsdom doesn't include it
if (typeof global.fetch === 'undefined') {
if (typeof globalThis.fetch === 'undefined') {
// Use a simple mock fetch for testing
global.fetch = jest.fn().mockResolvedValue({
globalThis.fetch = jest.fn().mockResolvedValue({
ok: true,
status: 200,
statusText: 'OK',
Expand Down Expand Up @@ -50,24 +50,24 @@ jest.mock('next-auth/react', () => {
}
})

if (!global.structuredClone) {
global.structuredClone = (val) => JSON.parse(JSON.stringify(val))
if (!globalThis.structuredClone) {
globalThis.structuredClone = (val) => JSON.parse(JSON.stringify(val))
}

beforeAll(() => {
if (typeof window !== 'undefined') {
jest.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => {
if (typeof globalThis !== 'undefined') {
jest.spyOn(globalThis, 'requestAnimationFrame').mockImplementation((cb) => {
return setTimeout(cb, 0)
})

Object.defineProperty(window, 'runAnimationFrameCallbacks', {
Object.defineProperty(globalThis, 'runAnimationFrameCallbacks', {
value: () => {},
configurable: true,
writable: true,
})
}

global.ResizeObserver = class {
globalThis.ResizeObserver = class {
disconnect() {}
observe() {}
unobserve() {}
Expand All @@ -79,7 +79,7 @@ beforeEach(() => {
throw new Error(`Console error: ${args.join(' ')}`)
})

jest.spyOn(global.console, 'warn').mockImplementation((message) => {
jest.spyOn(globalThis.console, 'warn').mockImplementation((message) => {
if (
typeof message === 'string' &&
message.includes('[@zag-js/dismissable] node is `null` or `undefined`')
Expand All @@ -88,7 +88,7 @@ beforeEach(() => {
}
})

Object.defineProperty(window, 'matchMedia', {
Object.defineProperty(globalThis, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
Expand All @@ -102,6 +102,6 @@ beforeEach(() => {
})),
})

global.runAnimationFrameCallbacks = jest.fn()
global.removeAnimationFrameCallbacks = jest.fn()
globalThis.runAnimationFrameCallbacks = jest.fn()
globalThis.removeAnimationFrameCallbacks = jest.fn()
})
Loading