|
1 |
| -import React, {render, cleanup, fireEvent} from '@testing-library/react' |
| 1 | +import React, {render, cleanup, fireEvent, act} from '@testing-library/react' |
2 | 2 | import '@testing-library/jest-dom'
|
3 | 3 | import {axe, toHaveNoViolations} from 'jest-axe'
|
4 | 4 |
|
@@ -40,17 +40,99 @@ const MockAnchorNavFixture = ({data = mockData, withSecondAction = false, ...res
|
40 | 40 | }
|
41 | 41 |
|
42 | 42 | describe('AnchorNav', () => {
|
43 |
| - afterEach(cleanup) |
| 43 | + let mockIntersectionObserver: jest.Mock |
| 44 | + let addEventListenerSpy: jest.SpyInstance |
| 45 | + let scrollListener: EventListener | null |
| 46 | + |
| 47 | + const setScrollPosition = (position: number) => { |
| 48 | + Object.defineProperty(window, 'pageYOffset', { |
| 49 | + writable: true, |
| 50 | + configurable: true, |
| 51 | + value: position, |
| 52 | + }) |
| 53 | + } |
| 54 | + |
| 55 | + const triggerScrollEvent = async () => { |
| 56 | + if (scrollListener) { |
| 57 | + scrollListener(new Event('scroll')) |
| 58 | + } |
| 59 | + } |
| 60 | + |
| 61 | + const simulateNavBecomingSticky = async (navYPosition: number, scrollToPosition: number) => { |
| 62 | + await act(async () => { |
| 63 | + triggerObserverByRootMargin('0px 0px -100%', { |
| 64 | + boundingClientRect: mockRect(navYPosition), |
| 65 | + isIntersecting: true, |
| 66 | + }) |
| 67 | + }) |
| 68 | + |
| 69 | + await act(async () => { |
| 70 | + setScrollPosition(scrollToPosition) |
| 71 | + await triggerScrollEvent() |
| 72 | + }) |
| 73 | + } |
| 74 | + |
| 75 | + const mockRect = (y = 100): DOMRectReadOnly => ({ |
| 76 | + y, |
| 77 | + top: y, |
| 78 | + bottom: y + 50, |
| 79 | + left: 0, |
| 80 | + right: 0, |
| 81 | + width: 0, |
| 82 | + height: 50, |
| 83 | + x: 0, |
| 84 | + toJSON: () => ({}), |
| 85 | + }) |
| 86 | + |
| 87 | + const triggerObserverByRootMargin = (rootMargin: string, entry: Partial<IntersectionObserverEntry>) => { |
| 88 | + const observerCall = mockIntersectionObserver.mock.calls.find(call => { |
| 89 | + const options = call[1] |
| 90 | + return options && options.rootMargin === rootMargin |
| 91 | + }) |
| 92 | + |
| 93 | + if (observerCall) { |
| 94 | + const [callback] = observerCall |
| 95 | + const mockEntry = { |
| 96 | + isIntersecting: true, |
| 97 | + boundingClientRect: mockRect(), |
| 98 | + ...entry, |
| 99 | + } |
| 100 | + callback([mockEntry]) |
| 101 | + } |
| 102 | + } |
44 | 103 |
|
45 | 104 | beforeEach(() => {
|
46 |
| - // IntersectionObserver isn't available in test environment |
47 |
| - const mockIntersectionObserver = jest.fn() |
| 105 | + scrollListener = null |
| 106 | + |
| 107 | + mockIntersectionObserver = jest.fn() |
48 | 108 | mockIntersectionObserver.mockReturnValue({
|
49 |
| - observe: () => null, |
50 |
| - unobserve: () => null, |
51 |
| - disconnect: () => null, |
| 109 | + observe: jest.fn(), |
| 110 | + unobserve: jest.fn(), |
| 111 | + disconnect: jest.fn(), |
52 | 112 | })
|
53 | 113 | window.IntersectionObserver = mockIntersectionObserver
|
| 114 | + |
| 115 | + Object.defineProperty(window, 'pageYOffset', { |
| 116 | + writable: true, |
| 117 | + configurable: true, |
| 118 | + value: 0, |
| 119 | + }) |
| 120 | + |
| 121 | + addEventListenerSpy = jest |
| 122 | + .spyOn(window, 'addEventListener') |
| 123 | + .mockImplementation((type: string, listener: EventListenerOrEventListenerObject) => { |
| 124 | + if (type === 'scroll') { |
| 125 | + scrollListener = typeof listener === 'function' ? listener : listener.handleEvent |
| 126 | + } |
| 127 | + }) |
| 128 | + }) |
| 129 | + |
| 130 | + afterEach(() => { |
| 131 | + cleanup() |
| 132 | + |
| 133 | + addEventListenerSpy.mockRestore() |
| 134 | + |
| 135 | + jest.clearAllMocks() |
54 | 136 | })
|
55 | 137 |
|
56 | 138 | it('renders the root element correctly into the document', () => {
|
@@ -108,15 +190,13 @@ describe('AnchorNav', () => {
|
108 | 190 | const {getByTestId} = render(<MockAnchorNavFixture />)
|
109 | 191 | const actionEl = getByTestId(AnchorNav.testIds.action)
|
110 | 192 | expect(actionEl).toBeInTheDocument() // renders
|
111 |
| - expect(actionEl).toBeInTheDocument() // renders as an anchor |
112 | 193 | expect(actionEl).toHaveAttribute('href', '#') // renders with correct href
|
113 | 194 | })
|
114 | 195 |
|
115 | 196 | it('renders an secondary correctly', () => {
|
116 | 197 | const {getByTestId} = render(<MockAnchorNavFixture withSecondAction />)
|
117 | 198 | const secondaryActionEl = getByTestId(AnchorNav.testIds.secondaryAction)
|
118 | 199 | expect(secondaryActionEl).toBeInTheDocument() // renders
|
119 |
| - expect(secondaryActionEl).toBeInTheDocument() // renders as an anchor |
120 | 200 | expect(secondaryActionEl).toHaveAttribute('href', '#') // renders with correct href
|
121 | 201 | })
|
122 | 202 |
|
@@ -158,4 +238,32 @@ describe('AnchorNav', () => {
|
158 | 238 |
|
159 | 239 | expect(results).toHaveNoViolations()
|
160 | 240 | })
|
| 241 | + |
| 242 | + it('shows an equivalent height spacer when nav is sticky and hides it when not', async () => { |
| 243 | + const MockPage = () => ( |
| 244 | + <div style={{height: '200vh'}}> |
| 245 | + <MockAnchorNavFixture /> |
| 246 | + <div style={{height: '100vh'}}>After nav</div> |
| 247 | + </div> |
| 248 | + ) |
| 249 | + |
| 250 | + const {getByTestId, queryByTestId} = render(<MockPage />) |
| 251 | + const rootEl = getByTestId(AnchorNav.testIds.root) |
| 252 | + const navPosition = 100 |
| 253 | + |
| 254 | + expect(rootEl).not.toHaveClass('AnchorNav--stuck') |
| 255 | + expect(queryByTestId(AnchorNav.testIds.navSpacer)).not.toBeInTheDocument() |
| 256 | + |
| 257 | + await simulateNavBecomingSticky(navPosition, navPosition + 50) |
| 258 | + expect(rootEl).toHaveClass('AnchorNav--stuck') |
| 259 | + expect(getByTestId(AnchorNav.testIds.navSpacer)).toBeInTheDocument() |
| 260 | + |
| 261 | + await act(async () => { |
| 262 | + setScrollPosition(navPosition - 50) |
| 263 | + await triggerScrollEvent() |
| 264 | + }) |
| 265 | + |
| 266 | + expect(rootEl).not.toHaveClass('AnchorNav--stuck') |
| 267 | + expect(queryByTestId(AnchorNav.testIds.navSpacer)).not.toBeInTheDocument() |
| 268 | + }) |
161 | 269 | })
|
0 commit comments