-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Frontend Tests, hooks and utils (#437)
* install vitest * install jsdom * add test config object * add tests folder * errors - updated to use boilerplate vitest code * wrote test to test vitest functionality. is this recursive testing? * add testTimeout to test config * rename tests folder to __tests__ * remove extra 'tests' folder * basic tests for utils/errors.js * create 'util' folder * add hasValidationsErrors test * use vitest.spy for getErrorList - still fails test * attempt to render App inside a div container * add testing-library/react-hooks * add tests for useKeyPress * added testing-library/react-hooks * fixed getErrorList 'no error passed' test * use vi.fn() to mock functions, passing all current tests * add basic vitest skeleton, not functional * hook sets state with initial window size * reword assertion * render hook as result object - not functional yet * put render into act() for app.test - not functional * Fix App test * simulate clicks inside & outside element * remove comments * import render, waitFor from react testing lib * test mock userAgents * add test that more closely mocks implementation * reword assertion * extract isChrome to function, test Mozilla userAgent, vendor * add chrome param to setWindowProps * add iPhone safari test, rm userAgent only tests * update userAgent strings with MDN examples * update Chrome userAgent * basic test outline - wip * move setWindowProps into inner scope * attempt to set multiple window properties * WIP - initial take on rendering hook * check portal.id * add first successful debounce test * add test where callback not called * reword assertion * remove test for useBrowserWarning * wip - assertions for window properties only * mock initial window size with vi.stubGlobal * add resize event test * action not triggered after hook unmounted * action not triggered after hook unmounted * remove redundant assertion * advance timer, add assertion that checks if callack is called * reword test description * add _arg to vi.fn() * check that callback is called with specific arg * wip - using stubGlobal * WIP - clean comments, test only Chrome, Mozilla * Fix isChrome test * npm run format * move initial window sizing to beforeEach * use outsideElement on hook unmount test * remove act(), make final assertions more comprehensive * remove redundant assertion --------- Co-authored-by: Rob Gries <[email protected]>
- Loading branch information
1 parent
7097056
commit dae0d3c
Showing
15 changed files
with
2,540 additions
and
394 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import App from '../components/App'; | ||
import { render, waitFor } from '@testing-library/react'; | ||
import { test } from 'vitest'; | ||
import store from '../store'; | ||
import { Provider } from 'react-redux'; | ||
|
||
test('When the app starts it renders a log in button', async () => { | ||
const container = document.createElement('div'); | ||
container.setAttribute('id', 'test-root'); | ||
document.body.appendChild(container); | ||
|
||
const { getByText } = render( | ||
<Provider store={store}> | ||
<App /> | ||
</Provider>, | ||
container, | ||
); | ||
await waitFor(() => expect(getByText('Log In')).toBeInTheDocument()); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { vi } from 'vitest'; | ||
import { renderHook } from '@testing-library/react-hooks'; | ||
import useDebounce from '../../hooks/useDebounce'; | ||
|
||
describe('useDebounce', () => { | ||
beforeEach(() => { | ||
vi.useFakeTimers(); | ||
}); | ||
afterEach(() => { | ||
vi.restoreAllMocks(); | ||
}); | ||
|
||
it('should debounce the callback function', async () => { | ||
const callback = vi.fn((_arg) => console.log()); | ||
const { result } = renderHook(() => useDebounce(callback, { timeout: 500 })); | ||
|
||
// Call the debounced function multiple times within a short period | ||
result.current('call 1'); | ||
result.current('call 2'); | ||
result.current('call 3'); | ||
|
||
vi.runAllTimers(); | ||
|
||
// Ensure that the callback has been called only once with the last argument | ||
expect(callback).toHaveBeenCalledTimes(1); | ||
expect(callback).toHaveBeenCalledWith('call 3'); | ||
}); | ||
|
||
it('should not execute the function until timer exceeds timeout', () => { | ||
const callback = vi.fn((_arg) => console.log()); | ||
const { result } = renderHook(() => useDebounce(callback, { timeout: 500 })); | ||
|
||
result.current('call 1'); | ||
|
||
// advancing by 2ms won't trigger callback | ||
vi.advanceTimersByTime(2); | ||
expect(callback).not.toHaveBeenCalled(); | ||
// advancing by 500ms total will trigger callback | ||
vi.advanceTimersByTime(498); | ||
expect(callback).toHaveBeenCalledWith('call 1'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { vi } from 'vitest'; | ||
import { renderHook } from '@testing-library/react-hooks'; | ||
import useKeyPress from '../../hooks/useKeyPress'; | ||
|
||
describe('useKeyPress', () => { | ||
it('should call the action function on Enter key press', () => { | ||
const key = 'Enter'; | ||
const action = vi.fn(); | ||
|
||
renderHook(() => useKeyPress(key, action)); | ||
const event = new KeyboardEvent('keyup', { key }); | ||
window.dispatchEvent(event); | ||
|
||
expect(action).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('should not call the action function on Escape key press', () => { | ||
const key = 'Enter'; | ||
const action = vi.fn(); | ||
|
||
renderHook(() => useKeyPress(key, action)); | ||
const event = new KeyboardEvent('keyup', { key: 'Escape' }); | ||
window.dispatchEvent(event); | ||
|
||
expect(action).not.toHaveBeenCalled(); | ||
}); | ||
|
||
it('action is not triggered after hook is unmounted', () => { | ||
const key = 'Enter'; | ||
const action = vi.fn(); | ||
|
||
const { unmount } = renderHook(() => useKeyPress(key, action)); | ||
unmount(); | ||
const event = new KeyboardEvent('keyup', { key }); | ||
window.dispatchEvent(event); | ||
|
||
expect(action).not.toHaveBeenCalled(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { vi } from 'vitest'; | ||
import { renderHook } from '@testing-library/react-hooks'; | ||
import useOnClickOutside from '../../hooks/useOnClickOutside'; | ||
|
||
describe('useOnClickOutside', () => { | ||
it('should call the handler when clicking outside the ref', () => { | ||
const ref = { current: document.createElement('div') }; | ||
const handler = vi.fn(); | ||
|
||
renderHook(() => useOnClickOutside(ref, handler)); | ||
|
||
const outsideElement = document.createElement('div'); | ||
document.body.appendChild(outsideElement); | ||
|
||
const event = new MouseEvent('mousedown', { | ||
bubbles: true, | ||
cancelable: true, | ||
}); | ||
outsideElement.dispatchEvent(event); | ||
|
||
expect(handler).toHaveBeenCalled(); | ||
}); | ||
|
||
it('should not call the handler when clicking inside the ref', () => { | ||
const ref = { current: document.createElement('div') }; | ||
const handler = vi.fn(); | ||
|
||
renderHook(() => useOnClickOutside(ref, handler)); | ||
|
||
const insideElement = document.createElement('div'); | ||
ref.current.appendChild(insideElement); | ||
|
||
const event = new MouseEvent('mousedown', { | ||
bubbles: true, | ||
cancelable: true, | ||
}); | ||
insideElement.dispatchEvent(event); | ||
|
||
expect(handler).not.toHaveBeenCalled(); | ||
}); | ||
|
||
it('should not call the handler after hook is unmounted', () => { | ||
const ref = { current: document.createElement('div') }; | ||
const handler = vi.fn(); | ||
|
||
const { unmount } = renderHook(() => useOnClickOutside(ref, handler)); | ||
|
||
const outsideElement = document.createElement('div'); | ||
document.body.appendChild(outsideElement); | ||
|
||
const event = new MouseEvent('mousedown', { | ||
bubbles: true, | ||
cancelable: true, | ||
}); | ||
|
||
outsideElement.dispatchEvent(event); | ||
expect(handler).toHaveBeenCalledTimes(1); | ||
|
||
unmount(); | ||
|
||
outsideElement.dispatchEvent(event); | ||
expect(handler).toHaveBeenCalledTimes(1); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { renderHook } from '@testing-library/react-hooks'; | ||
import usePortal from '../../hooks/usePortal'; | ||
|
||
describe('usePortal', () => { | ||
it('inserts an empty div into DOM with correct ID', () => { | ||
const sibling = document.createElement('section'); | ||
document.body.insertAdjacentElement('beforeend', sibling); | ||
|
||
const { result } = renderHook(() => usePortal('usePortal-test')); | ||
const portal = result.current; | ||
const portalId = document.querySelector('#usePortal-test').id; | ||
|
||
expect(portal).toBeInTheDocument(); | ||
expect(portal).toBeEmptyDOMElement(); | ||
expect(portalId).toBe(`usePortal-test`); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import useWindowSize from '../../hooks/useWindowSize'; | ||
import { vi } from 'vitest'; | ||
import { renderHook } from '@testing-library/react-hooks'; | ||
|
||
describe('useWindowSize', () => { | ||
beforeEach(() => { | ||
vi.stubGlobal('innerWidth', 1920); | ||
vi.stubGlobal('innerHeight', 1080); | ||
}); | ||
|
||
it('sets state to an initial window size', () => { | ||
const { result } = renderHook(() => useWindowSize()); | ||
|
||
expect(result.current.width).toBe(1920); | ||
expect(result.current.height).toBe(1080); | ||
|
||
vi.unstubAllGlobals(); | ||
}); | ||
|
||
it('adjusts after window resize event', () => { | ||
const { result } = renderHook(() => useWindowSize()); | ||
|
||
expect(result.current.width).toBe(1920); | ||
expect(result.current.height).toBe(1080); | ||
|
||
window.innerWidth = 1280; | ||
window.innerHeight = 720; | ||
window.dispatchEvent(new Event('resize')); | ||
|
||
expect(result.current.width).toBe(1280); | ||
expect(result.current.height).toBe(720); | ||
|
||
vi.unstubAllGlobals(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { vi } from 'vitest'; | ||
// import { screen } from '@testing-library/react'; | ||
import { downloadPdf } from '../../util/downloadFile'; // Import your utility functions here | ||
|
||
describe('File Download Utility Functions', () => { | ||
it('should download a PDF file', () => { | ||
// Mock Blob and createObjectURL | ||
window.Blob = vi.fn(); | ||
window.URL.createObjectURL = vi.fn(() => 'test-object-url'); | ||
|
||
const pdfData = 'Mock PDF Data'; | ||
const pdfFilename = 'sample.pdf'; | ||
|
||
// Mock the createElement and click methods for the link element | ||
const createElementMock = vi.spyOn(document, 'createElement').mockImplementation(() => { | ||
return { | ||
href: '', | ||
download: '', | ||
click: vi.fn(), | ||
remove: vi.fn(), | ||
}; | ||
}); | ||
|
||
// const mockDownload = vi.fn().mockImplementation(downloadPdf); | ||
// const fakeDl = mockDownload(pdfData, pdfFilename); | ||
// console.log(fakeDl); | ||
downloadPdf(pdfData, pdfFilename); | ||
|
||
// Assertions | ||
expect(window.Blob).toHaveBeenCalledWith([pdfData], { type: 'application/pdf' }); | ||
expect(window.URL.createObjectURL).toHaveBeenCalledWith(new Blob([pdfData], { type: 'application/pdf' })); | ||
|
||
expect(createElementMock).toHaveBeenCalledWith('a'); | ||
// expect(createElementMock.download).toBe(pdfFilename); | ||
// expect(createElementMock.href).toBe('test-object-url'); | ||
|
||
createElementMock.mockRestore(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { vi } from 'vitest'; | ||
import { getErrorList, hasValidationsErrors } from '../../util/errors'; | ||
|
||
describe('Utils: errors.js', () => { | ||
afterEach(() => { | ||
vi.resetAllMocks(); | ||
}); | ||
|
||
it('hasValidationsErrors returns true if errors exist', () => { | ||
const hasValidationsErrorsSpy = vi.fn(hasValidationsErrors); | ||
const mockErrors = { | ||
errorOne: ['Error 1 message'], | ||
errorTwo: ['Error 2 message'], | ||
}; | ||
const hasErrors = hasValidationsErrorsSpy(mockErrors); | ||
expect(hasErrors).toBe(true); | ||
expect(hasValidationsErrorsSpy).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('hasValidationsErrors returns false if no errors exist', () => { | ||
const hasValidationsErrorsSpy = vi.fn(hasValidationsErrors); | ||
const noErrors = hasValidationsErrorsSpy({}); | ||
expect(noErrors).toBe(false); | ||
expect(hasValidationsErrorsSpy).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('getErrorList returns a list of passed errors', () => { | ||
const getErrorListSpy = vi.fn(getErrorList); | ||
const mockErrors = { | ||
errorOne: ['Error 1 message'], | ||
errorTwo: ['Error 2 message'], | ||
}; | ||
const expectedResult = [`ErrorOne: Error 1 message`, `ErrorTwo: Error 2 message`]; | ||
const result = getErrorListSpy(mockErrors); | ||
|
||
expect(result).toEqual(expectedResult); | ||
expect(getErrorListSpy).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('getErrorList returns [] if no errors passed', () => { | ||
const getErrorListSpy = vi.fn(getErrorList); | ||
const noErrors = getErrorListSpy({}); | ||
expect(noErrors).toStrictEqual([]); | ||
expect(getErrorListSpy).toHaveBeenCalledTimes(1); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { vi } from 'vitest'; | ||
import isChrome from '../../util/isChrome.js'; | ||
|
||
describe('isChrome test', () => { | ||
beforeEach(() => { | ||
vi.stubGlobal('chrome', null); | ||
vi.stubGlobal('navigator', { | ||
userAgent: '', | ||
vendor: '', | ||
}); | ||
}); | ||
|
||
afterEach(() => { | ||
vi.clearAllMocks(); | ||
vi.resetAllMocks(); | ||
}); | ||
|
||
it('browser is Chrome, isChrome=true', async () => { | ||
vi.stubGlobal('chrome', true); | ||
vi.stubGlobal('navigator', { | ||
userAgent: `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36`, | ||
vendor: 'Google Inc.', | ||
}); | ||
|
||
expect(isChrome()).toEqual(true); | ||
}); | ||
|
||
it('browser is Mozilla, isChrome=false', async () => { | ||
vi.stubGlobal('chrome', undefined); | ||
vi.stubGlobal('navigator', { | ||
userAgent: `Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0`, | ||
vendor: '', | ||
}); | ||
|
||
expect(isChrome()).toEqual(false); | ||
}); | ||
}); |
Oops, something went wrong.