expo-popcore-app/hooks/use-sticky-tabs.test.ts

230 lines
6.0 KiB
TypeScript

/**
* Tests for useStickyTabs hook
*
* Note: Due to jest.setup.js configuration issues with react-native mocks,
* we test the core sticky tabs logic directly instead of using renderHook.
*/
// Re-implement the sticky tabs logic for testing
interface StickyTabsState {
isSticky: boolean
tabsHeight: number
tabsPositionRef: { current: number }
titleBarHeightRef: { current: number }
}
function createStickyTabsState(): StickyTabsState {
return {
isSticky: false,
tabsHeight: 0,
tabsPositionRef: { current: 0 },
titleBarHeightRef: { current: 0 },
}
}
function handleScroll(
state: StickyTabsState,
scrollY: number,
setIsSticky: (value: boolean) => void
): void {
if (scrollY > state.tabsPositionRef.current) {
if (!state.isSticky) {
setIsSticky(true)
}
} else {
if (state.isSticky) {
setIsSticky(false)
}
}
}
function handleTabsLayout(
state: StickyTabsState,
y: number,
height: number,
setTabsHeight: (value: number) => void
): void {
state.tabsPositionRef.current = y
setTabsHeight(height)
}
function handleTitleBarLayout(
state: StickyTabsState,
height: number
): void {
state.titleBarHeightRef.current = height
}
describe('useStickyTabs - sticky tabs logic', () => {
describe('initial state', () => {
it('should have isSticky as false initially', () => {
const state = createStickyTabsState()
expect(state.isSticky).toBe(false)
})
it('should have tabsHeight as 0 initially', () => {
const state = createStickyTabsState()
expect(state.tabsHeight).toBe(0)
})
it('should have tabsPositionRef.current as 0 initially', () => {
const state = createStickyTabsState()
expect(state.tabsPositionRef.current).toBe(0)
})
it('should have titleBarHeightRef.current as 0 initially', () => {
const state = createStickyTabsState()
expect(state.titleBarHeightRef.current).toBe(0)
})
})
describe('handleScroll', () => {
it('should set isSticky to true when scrollY exceeds tabsPosition', () => {
const state = createStickyTabsState()
state.tabsPositionRef.current = 100
let newIsSticky = state.isSticky
handleScroll(state, 150, (value) => {
newIsSticky = value
})
expect(newIsSticky).toBe(true)
})
it('should set isSticky to false when scrollY is below tabsPosition', () => {
const state = createStickyTabsState()
state.isSticky = true
state.tabsPositionRef.current = 100
let newIsSticky = state.isSticky
handleScroll(state, 50, (value) => {
newIsSticky = value
})
expect(newIsSticky).toBe(false)
})
it('should not call setIsSticky when already sticky and scrollY exceeds tabsPosition', () => {
const state = createStickyTabsState()
state.isSticky = true
state.tabsPositionRef.current = 100
let callCount = 0
handleScroll(state, 150, () => {
callCount++
})
expect(callCount).toBe(0)
})
it('should not call setIsSticky when not sticky and scrollY is below tabsPosition', () => {
const state = createStickyTabsState()
state.isSticky = false
state.tabsPositionRef.current = 100
let callCount = 0
handleScroll(state, 50, () => {
callCount++
})
expect(callCount).toBe(0)
})
it('should handle edge case when scrollY equals tabsPosition', () => {
const state = createStickyTabsState()
state.isSticky = true
state.tabsPositionRef.current = 100
let newIsSticky = state.isSticky
handleScroll(state, 100, (value) => {
newIsSticky = value
})
// scrollY (100) is not greater than tabsPosition (100), so should become false
expect(newIsSticky).toBe(false)
})
})
describe('handleTabsLayout', () => {
it('should update tabsPositionRef with y value', () => {
const state = createStickyTabsState()
handleTabsLayout(state, 200, 50, () => {})
expect(state.tabsPositionRef.current).toBe(200)
})
it('should call setTabsHeight with height value', () => {
const state = createStickyTabsState()
let newTabsHeight = 0
handleTabsLayout(state, 200, 50, (value) => {
newTabsHeight = value
})
expect(newTabsHeight).toBe(50)
})
it('should handle zero values', () => {
const state = createStickyTabsState()
let newTabsHeight = 100
handleTabsLayout(state, 0, 0, (value) => {
newTabsHeight = value
})
expect(state.tabsPositionRef.current).toBe(0)
expect(newTabsHeight).toBe(0)
})
})
describe('handleTitleBarLayout', () => {
it('should update titleBarHeightRef with height value', () => {
const state = createStickyTabsState()
handleTitleBarLayout(state, 64)
expect(state.titleBarHeightRef.current).toBe(64)
})
it('should handle zero height', () => {
const state = createStickyTabsState()
state.titleBarHeightRef.current = 100
handleTitleBarLayout(state, 0)
expect(state.titleBarHeightRef.current).toBe(0)
})
})
describe('multiple scroll events', () => {
it('should only trigger state change on actual transitions', () => {
const state = createStickyTabsState()
state.tabsPositionRef.current = 100
let callCount = 0
const setIsSticky = (value: boolean) => {
callCount++
state.isSticky = value
}
// Initial scroll past threshold - should trigger
handleScroll(state, 150, setIsSticky)
expect(callCount).toBe(1)
expect(state.isSticky).toBe(true)
// Another scroll past threshold - should NOT trigger
handleScroll(state, 200, setIsSticky)
expect(callCount).toBe(1)
// Scroll back below threshold - should trigger
handleScroll(state, 50, setIsSticky)
expect(callCount).toBe(2)
expect(state.isSticky).toBe(false)
// Another scroll below threshold - should NOT trigger
handleScroll(state, 30, setIsSticky)
expect(callCount).toBe(2)
})
})
})