-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add component tests #163
base: master
Are you sure you want to change the base?
Add component tests #163
Changes from 16 commits
02ba85b
fd4d868
1ed750c
c89d78e
f997d86
6386d28
8e6ed12
54ce926
0f883ce
1aeb9d5
30c6a25
09006ea
84243dd
dd70499
7812c21
2388915
7cd98ea
47c54aa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
const subjects = { | ||
get: (subjectId) => { | ||
return Promise.resolve(null) | ||
} | ||
} | ||
|
||
module.exports = { | ||
subjects | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# Jest Mocks | ||
|
||
This folder contains modules mocked for Jest tests. | ||
|
||
If one of our components has `import { banana } from 'fruitlist'`, and that fruitlist module is complex, server-reliant, or otherwise borking our tests, then expect to find a `fruitlist.js` file in here. | ||
|
||
Additional notes (accurate as of Jest 29.6.4): | ||
|
||
- The `__mock__` folder is _implicit_ to Jest: https://jestjs.io/docs/manual-mocks | ||
- The `__mock__` folder can handle scoped modules, e.g. `@zooniverse/panoptes-js`, by using subfolders. | ||
- However, if the `import` command is pulling from _subfolders,_ then the `__mock__` folder doesn't seem to function as well. See `App.spec.js` for how it handles `import auth from 'panoptes-client/lib/auth'` using `jest.mock('panoptes-client/lib/auth', ...)` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
const config = { | ||
'moduleNameMapper': { | ||
// Maps all media files to a mock file. | ||
// Without this, test will crash with a "Jest encountered an unexpected token" due to Jest having no idea what to do with binary files. | ||
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/jest/mockMediaFile.js', | ||
|
||
// Maps the @src alias added in webpack. | ||
'^@src/(.*)$': '<rootDir>/src/$1' | ||
}, | ||
|
||
// Tells Jest to look for unit test files in the /src directory. | ||
'roots': [ | ||
'<rootDir>/src' | ||
], | ||
|
||
'setupFilesAfterEnv': [ | ||
'<rootDir>/jest/jest-setup.js' | ||
], | ||
|
||
// Without this, render() will result in "document is not defined" errors. | ||
'testEnvironment': 'jsdom', | ||
} | ||
|
||
// Jest doesn't like 'export' (i.e. ES6 modules), so we use module.exports (CommonJS modules). | ||
// Probably because it's running in Node.js. | ||
module.exports = config |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { render, screen } from '@testing-library/react' | ||
import { MemoryRouter, Route, Routes } from 'react-router-dom' | ||
import ProjectContainer from './ProjectContainer.js' | ||
|
||
import { AppContext } from '@src/store' | ||
|
||
describe('ProjectContainers', function () { | ||
let selectedProject | ||
const store = { | ||
project: { | ||
name: 'Placeholder Project', | ||
slug: 'foo/bar', | ||
id: '1234' | ||
}, | ||
user: null, | ||
showingSensitiveContent: false, | ||
setProject: (p) => { selectedProject = p }, | ||
setUser: () => {}, | ||
setShowingSensitiveContent: () => {}, | ||
} | ||
|
||
test('should find (and set) a project based on the route/path', async function () { | ||
selectedProject = null | ||
|
||
render( | ||
<AppContext.Provider value={store}> | ||
<MemoryRouter initialEntries={[ | ||
'/projects/darkeshard/community-catalog' /* Input: path to the intended project */ | ||
]}> | ||
<Routes> | ||
<Route | ||
path='/projects/:projectOwner/:projectName' | ||
element={<ProjectContainer />} /* Output: we expect ProjectContainer to call setProject() with the intended project */ | ||
/> | ||
</Routes> | ||
</MemoryRouter> | ||
</AppContext.Provider> | ||
) | ||
|
||
expect(selectedProject?.name).toEqual('Community Catalog (Stable Test Project)') | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import { act, render, screen } from '@testing-library/react' | ||
import SubjectImage from './SubjectImage.js' | ||
|
||
// Example data | ||
const exampleSubject_87892456 = { | ||
id: '87892456', | ||
locations: [ | ||
{ 'image/jpeg': 'https://panoptes-uploads.zooniverse.org/subject_location/66fd834d-b13a-40f0-b97d-6d364841d56c.jpeg' }, | ||
{ 'image/jpeg': 'https://panoptes-uploads.zooniverse.org/subject_location/39c77068-22eb-472d-b280-1af9e2f33437.jpeg' } | ||
] | ||
} | ||
|
||
// mock Panoptes module | ||
jest.mock('@zooniverse/panoptes-js', () => { | ||
return { | ||
__esModule: true, | ||
shaunanoordin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
subjects: { | ||
get: ({ id }) => { | ||
if (id === '87892456') { | ||
const mockResponse = { | ||
body: { | ||
subjects: [ exampleSubject_87892456 ] | ||
}, | ||
ok: true, | ||
status: 200, | ||
statusCode: 200, | ||
} | ||
|
||
return Promise.resolve(mockResponse) | ||
} | ||
return Promise.resolve(null) | ||
} | ||
} | ||
} | ||
}) | ||
|
||
describe.only('SubjectImage', function () { | ||
test('should render an image, when given a subject', function () { | ||
render( | ||
<SubjectImage | ||
subject={exampleSubject_87892456} | ||
/> | ||
) | ||
|
||
// For multi-image subjects, SubjectImage will render the FIRST image. | ||
expect(screen.getByRole('img')).toHaveProperty('alt', 'Preview image for Subject 87892456') | ||
expect(screen.getByRole('img')).toHaveProperty('src', 'https://panoptes-uploads.zooniverse.org/subject_location/66fd834d-b13a-40f0-b97d-6d364841d56c.jpeg') | ||
}) | ||
|
||
/* | ||
TODO: this isn't working, as the SubjectImage seems to continue updating outside the act() | ||
Perhaps we need to implement useSWR (https://github.com/zooniverse/community-catalog/pull/131) (to avoid using setState in useEffect), or look into other patterns that handle async calls. | ||
(@shaunanoordin 20230826) | ||
Comment on lines
+50
to
+52
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. React 18 has a new, concurrent data-fetching model. They recommend using a framework like Remix or NextJS, rather than implementing your own data-fetching model (Alice and FEM both have examples of how that can go wrong.) https://react.dev/blog/2022/03/29/react-v18#suspense-in-data-frameworks |
||
|
||
test('should render an image, when given a subject ID', function () { | ||
act(() => { | ||
render( | ||
<SubjectImage | ||
subjectId='87892456' | ||
/> | ||
) | ||
}) | ||
|
||
// For multi-image subjects, SubjectImage will render the FIRST image. | ||
expect(screen.getByRole('img')).toHaveProperty('alt', 'Preview image for Subject 87892456') | ||
expect(screen.getByRole('img')).toHaveProperty('src', 'https://panoptes-uploads.zooniverse.org/subject_location/66fd834d-b13a-40f0-b97d-6d364841d56c.jpeg') | ||
}) | ||
/**/ | ||
|
||
test('should render a placeholder image, when given no subject', function () { | ||
render( | ||
<SubjectImage /> | ||
) | ||
|
||
const svg = screen.getByLabelText('Placeholder for Subject image') | ||
expect(svg).toBeDefined() | ||
|
||
const path = svg.querySelector('path') | ||
expect(path).toHaveAttribute('d', 'M1 3h22v18H1V3zm5 6a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm17 6-5-6-6 7-3-3-8 8') | ||
expect(path).toHaveAttribute('fill', 'none') | ||
expect(path).toHaveAttribute('stroke', '#000') | ||
expect(path).toHaveAttribute('stroke-width', '2') | ||
}) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Node supports
export
but only for ESM, ie.type: 'module'
inpackage.json
or.mjs
files.https://nodejs.org/api/esm.html#enabling
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This repo's
package.json
doesn't declare the package type, so Node and Jest default to CommonJS.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks Jim, that's very helpful to know! I didn't realise Node.js could support ESM.
I took some time to explore converting this package to an ESM, but that's gonna be a work in progress.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pure ESM might be a good idea when starting a new project, but it's definitely a fair bit of work converting an existing package.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, #153 is failing the tests because
@zooniverse/react-components
now depends on d3, for SVG data subjects, and d3 is pure ESM.