Merge branch 'main' into change-cross-search-export

This commit is contained in:
Kari Lintulaakso
2025-06-02 11:04:54 +03:00
committed by GitHub
13 changed files with 327 additions and 38 deletions
+3 -1
View File
@@ -6,5 +6,7 @@
"eslint.workingDirectories": [
{ "directory": "./frontend", "changeProcessCWD": true },
{ "directory": "./backend", "changeProcessCWD": true }
]
],
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
}
+1 -1
View File
@@ -2,7 +2,7 @@ import { beforeAll, afterAll, describe, it, expect, beforeEach } from '@jest/glo
import { login, logout, resetDatabase, send, resetDatabaseTimeout } from '../utils'
import { pool } from '../../utils/db'
describe('Deleting a region works', () => {
describe('Getting region data works', () => {
beforeAll(async () => {
await resetDatabase()
}, resetDatabaseTimeout)
-2
View File
@@ -8,7 +8,6 @@ describe('Open each page, table view and detail view, and check at least some co
})
it('Locality works', () => {
cy.contains('Search').click()
cy.contains('Localities').click()
cy.contains('Dmanisi')
cy.get('[data-cy="detailview-button-21050"]').click()
@@ -17,7 +16,6 @@ describe('Open each page, table view and detail view, and check at least some co
})
it('Species works', () => {
cy.contains('Search').click()
cy.contains('Species').click()
cy.contains('Rodentia')
cy.get('[data-cy="detailview-button-21052"]').first().click()
+6 -6
View File
@@ -60,7 +60,7 @@ describe('Creating a new locality', () => {
cy.get('[id=dms_lat-textfield]').should('have.value', '48 40 48 N')
cy.contains('This field is required')
cy.get('[id=write-button]').should('be.disabled')
cy.contains('4 Invalid fields')
cy.contains('4 invalid fields')
})
it("and filling, then erasing needed data doesn't work", () => {
@@ -95,12 +95,12 @@ describe('Creating a new locality', () => {
cy.get('[id=min_age-textfield]').type('{backspace}{backspace}{backspace}{backspace}{backspace}')
cy.contains('This field is required')
cy.get('[id=write-button]').should('be.disabled')
cy.contains('1 Invalid fields')
cy.contains('1 invalid field')
// make sure errors in other tabs disable the write button
cy.get('[role=tablist]').contains('Locality').click()
cy.get('[id=write-button]').should('be.disabled')
cy.contains('1 Invalid fields')
cy.contains('1 invalid field')
})
it('write button is disabled if unvisited tab has validation errors', () => {
@@ -116,12 +116,12 @@ describe('Creating a new locality', () => {
// Age tab has no errors, but Locality tab has unfilled mandatory fields
cy.get('[id=write-button]').should('be.disabled')
cy.contains('6 Invalid fields')
cy.contains('6 invalid fields')
cy.get('[id=min_age-textfield-helper-text]').should('not.exist')
cy.get('[id=max_age-textfield-helper-text]').should('not.exist')
cy.get('[role=tablist]').contains('Locality').click()
cy.get('[id=write-button]').should('be.disabled')
cy.contains('6 Invalid fields')
cy.contains('6 invalid fields')
})
it('composite dating method works', () => {
@@ -233,7 +233,7 @@ describe('Editing a locality', () => {
cy.get('[id=max_age-textfield]').type('{backspace}{backspace}{backspace}{backspace}{backspace}9')
cy.contains('Min value cannot be higher than max')
cy.get('[id=write-button]').should('be.disabled')
cy.contains('4 Invalid fields')
cy.contains('4 invalid fields')
})
it('changing fractions and basis for age updates the age correctly', () => {
+175
View File
@@ -0,0 +1,175 @@
before('Reset database', () => {
cy.request(Cypress.env('databaseResetUrl'))
})
describe('Creating a journal', () => {
beforeEach('Login as admin', () => {
cy.login('testSu')
})
it('with valid data works', () => {
cy.visit('/reference/new')
cy.contains('Journal')
cy.contains('7 invalid fields')
cy.get('[id=title_primary-textfield]').type('New test reference')
cy.get('[data-cy=Select\\ author-button]').click()
cy.get('[data-cy=detailview-button-1]').click()
cy.contains('Close').click()
cy.get('[id=date_primary-textfield]').type('2025')
cy.get('[data-cy=Select\\ Journal-button]').click()
cy.get('[data-cy=detailview-button-100]').click()
cy.contains('Close').click()
cy.get('[id=write-button]').click()
cy.contains('Saved reference successfully.')
})
it('with missing required fields does not work', () => {
cy.visit('/reference/new')
cy.contains('Journal')
cy.contains('7 invalid fields')
cy.get('[id=title_primary-textfield]').type('New test reference 2')
cy.contains('3 invalid fields')
cy.get('[id=write-button]').should('be.disabled')
cy.get('[data-cy=Select\\ author-button]').click()
cy.get('[data-cy=detailview-button-1]').click()
cy.contains('Close').click()
cy.contains('2 invalid fields')
cy.get('[id=write-button]').should('be.disabled')
cy.get('[id=date_primary-textfield]').type('2025')
cy.contains('1 invalid field')
cy.get('[id=write-button]').should('be.disabled')
cy.get('[data-cy=Select\\ Journal-button]').click()
cy.get('[data-cy=detailview-button-100]').click()
cy.contains('Close').click()
cy.get('[id=write-button]').should('not.be.disabled')
cy.get('[data-testid=RemoveCircleOutlineIcon]').first().click() // removes the selected author
cy.get('[id=write-button]').should('be.disabled')
cy.get('[data-cy=Select\\ author-button]').click()
cy.get('[data-cy=detailview-button-1]').click()
cy.contains('Close').click()
cy.get('[id=write-button]').should('not.be.disabled')
})
it('combined check with different types of titles and gen notes works', () => {
cy.visit('/reference/new')
cy.contains('Journal')
cy.contains('7 invalid fields')
cy.get('[id=write-button]').should('be.disabled')
cy.get('[id=title_primary-textfield]').type('Title Primary')
cy.contains('3 invalid fields')
cy.get('[id=title_primary-textfield]').clear()
cy.contains('7 invalid fields')
cy.get('[id=write-button]').should('be.disabled')
cy.get('[id=title_secondary-textfield]').type('Title Secondary')
cy.contains('3 invalid fields')
cy.get('[id=title_secondary-textfield]').clear()
cy.contains('7 invalid fields')
cy.get('[id=write-button]').should('be.disabled')
cy.get('[id=title_series-textfield]').type('Title Series')
cy.contains('3 invalid fields')
cy.get('[id=title_series-textfield]').clear()
cy.contains('7 invalid fields')
cy.get('[id=write-button]').should('be.disabled')
cy.get('[id=gen_notes-textfield]').type('Gen Notes')
cy.contains('3 invalid fields')
cy.get('[id=gen_notes-textfield]').clear()
cy.contains('7 invalid fields')
cy.get('[id=write-button]').should('be.disabled')
})
})
// TODO: add more tests for different types of references
describe('Editing a journal', () => {
beforeEach('Login as admin', () => {
cy.login('testSu')
})
it('with valid data works', () => {
cy.visit('reference/10039')
cy.contains('A new geomagnetic polarity time scale for the Late Cretaceous and Cenozoic.')
cy.get('[id=edit-button]').click()
cy.get('[id=title_primary-textfield]').clear()
cy.get('[id=title_primary-textfield]').type('New primary title.')
cy.get('[data-cy=Select\\ author-button]').click()
cy.get('[data-cy=detailview-button-2]').click()
cy.contains('Close').click()
cy.get('[id=write-button]').click()
cy.contains('Saved reference successfully.')
cy.contains('New primary title.')
cy.contains('Cande') // Author 1
cy.contains('Ciochon') // Author 2
})
it('with missing required fields does not work', () => {
cy.visit('reference/10039')
cy.contains('New primary title.')
cy.get('[id=edit-button]').click()
cy.get('[id=title_primary-textfield]').clear()
cy.contains('4 invalid fields')
cy.get('[id=write-button]').should('be.disabled')
cy.get('[id=title_primary-textfield]').type('New primary title.')
cy.get('[data-testid=RemoveCircleOutlineIcon]').first().click()
cy.get('[data-testid=RemoveCircleOutlineIcon]').first().click() // removes the selected authors
cy.contains('1 invalid field')
cy.get('[id=write-button]').should('be.disabled')
cy.get('[data-cy=Select\\ author-button]').click()
cy.get('[data-cy=detailview-button-1]').click()
cy.contains('Close').click()
cy.get('[id=write-button]').should('not.be.disabled')
})
})
// TODO: add more tests for different types of references
describe('Deleting a reference', () => {
beforeEach('Login as admin', () => {
cy.login('testSu')
})
it('that is not linked to anything works and returns user to table view', () => {
cy.visit('/reference/25000')
cy.get('[id=delete-button]').should('exist').click()
cy.on('window:confirm', str => {
expect(str).to.eq('Are you sure you want to delete this item? This operation cannot be undone.')
// this automatically clicks the OK button and returns to table view
})
cy.contains('Deleted item successfully.')
cy.contains('Dated co-occurrence of Homo erectus and Gigantopithecus from Tham Khuyen Cave, Vietnam') // check that table view has been navigated to
cy.contains('A test reference that is not linked to any localities or species').should('not.exist')
cy.visit('/reference/25000')
cy.contains('Error loading data')
})
// Skipped because references that have linked localities or species cannot be deleted currently
it.skip('that is linked to a locality and a species works and returns user to table view', () => {
cy.visit('/reference/10039')
cy.get('[id=delete-button]').should('exist').click()
cy.on('window:confirm', str => {
expect(str).to.eq('Are you sure you want to delete this item? This operation cannot be undone.')
// this automatically clicks the OK button and returns to table view
})
cy.contains('Deleted item successfully.')
cy.contains('Dated co-occurrence of Homo erectus and Gigantopithecus from Tham Khuyen Cave, Vietnam') // check that table view has been navigated to
cy.contains('A new geomagnetic polarity time scale for the Late Cretaceous and Cenozoic').should('not.exist')
cy.visit('/reference/10039')
cy.contains('Error loading data')
})
})
+76 -1
View File
@@ -1,3 +1,7 @@
before('Reset database', () => {
cy.request(Cypress.env('databaseResetUrl'))
})
describe('Creating a species', () => {
beforeEach('Login as admin', () => {
cy.login('testSu')
@@ -24,7 +28,78 @@ describe('Creating a species', () => {
cy.visit('/species/new')
cy.contains('This field is required')
cy.get('[id=write-button]').should('be.disabled')
cy.contains('4 Invalid fields')
cy.contains('4 invalid fields')
})
})
describe('Editing a species', () => {
beforeEach('Login as admin', () => {
cy.login('testSu')
})
it('with valid data works', () => {
cy.visit('/species/21052/')
cy.contains('21052 Simplomys simplicidens')
cy.get('[id=edit-button]').click()
cy.get('[id=order_name-textfield]').clear()
cy.get('[id=order_name-textfield]').type('modifiedOrder')
cy.get('[id=family_name-textfield]').clear()
cy.get('[id=family_name-textfield]').type('modifiedFamily')
cy.get('[id=genus_name-textfield]').clear()
cy.get('[id=genus_name-textfield]').type('modifiedGenus')
cy.get('[id=species_name-textfield]').clear()
cy.get('[id=species_name-textfield]').type('modifiedSpecies')
cy.get('[id=subfamily_name-textfield]').type('heimo')
cy.get('[id=write-button]').should('not.be.disabled')
cy.get('[id=write-button]').click()
cy.contains('button', 'Add existing reference').click()
cy.get('button[data-cy^="detailview-button"]').first().click()
cy.contains('button', 'Close').click()
cy.get('[id=write-button]').should('not.be.disabled')
cy.get('[id=write-button]').click()
cy.contains('Saved species entry successfully.')
cy.contains('modifiedOrder')
cy.contains('Subfamily or Tribe heimo')
})
it('with missing required fields does not work', () => {
cy.visit('/species/21052/')
cy.contains('21052')
cy.get('[id=edit-button]').click()
cy.get('[id=order_name-textfield]').clear()
cy.contains('Order: This field is required')
cy.get('[id=write-button]').should('be.disabled')
cy.contains('1 invalid field')
cy.get('[id=family_name-textfield]').clear()
cy.contains('Family: This field is required')
cy.get('[id=write-button]').should('be.disabled')
cy.contains('2 invalid fields')
cy.get('[id=order_name-textfield]').type('newOrder')
cy.contains('Order: This field is required').should('not.exist')
cy.contains('Family: This field is required')
cy.get('[id=write-button]').should('be.disabled')
cy.contains('1 invalid field')
cy.get('[id=family_name-textfield]').type('newFamily')
cy.contains('Family: This field is required').should('not.exist')
cy.get('[id=write-button]').click()
cy.contains('button', 'Add existing reference').click()
cy.get('button[data-cy^="detailview-button"]').first().click()
cy.contains('button', 'Close').click()
cy.get('[id=write-button]').should('not.be.disabled')
cy.get('[id=write-button]').click()
cy.contains('Saved species entry successfully.')
cy.contains('newOrder')
cy.contains('newFamily')
cy.contains('modifiedGenus')
cy.contains('Subfamily or Tribe heimo')
})
})
+30 -4
View File
@@ -1,10 +1,35 @@
describe('Editing time bound', () => {
before('Reset database', () => {
cy.request(Cypress.env('databaseResetUrl'))
})
describe('Creating time bound', () => {
beforeEach('Login as admin', () => {
cy.login('testSu')
})
beforeEach('Reset database', () => {
cy.request(Cypress.env('databaseResetUrl'))
it('with correct values works', () => {
cy.visit(`/time-bound/new/`)
cy.contains('Creating new time-bound')
cy.contains('Name: This field is required')
cy.contains('Age (Ma): This field is required')
cy.get('[id=b_name-textfield]').first().type('test bound')
cy.contains('Name: This field is required').should('not.exist')
cy.get('[id=write-button]').should('be.disabled')
cy.get('[id=age-textfield]').first().type('20')
cy.contains('Age (Ma): This field is required').should('not.exist')
cy.get('[id=write-button]').click()
cy.contains('button', 'Add existing reference').click()
cy.get('button[data-cy^="detailview-button"]').first().click()
cy.contains('button', 'Close').click()
cy.get('[id=write-button]').should('not.be.disabled')
cy.get('[id=write-button]').click()
cy.contains('Edited item successfully.')
})
})
describe('Editing time bound', () => {
beforeEach('Login as admin', () => {
cy.login('testSu')
})
it('with correct values works', () => {
@@ -20,6 +45,7 @@ describe('Editing time bound', () => {
cy.contains('button', 'Close').click()
cy.get('[id=write-button]').should('not.be.disabled')
cy.get('[id=write-button]').click()
cy.contains('Edited item successfully.')
cy.contains('1.5')
})
@@ -30,7 +56,7 @@ describe('Editing time bound', () => {
cy.get('[id=age-textfield]').first().type('{backspace}{backspace}{backspace}{backspace}{backspace}')
cy.contains('This field is required')
cy.get('[id=write-button]').should('be.disabled')
cy.contains('1 Invalid fields')
cy.contains('1 invalid field')
})
})
-4
View File
@@ -14,7 +14,6 @@ describe('Button Tests', () => {
cy.contains('References').should('be.visible')
cy.contains('Time Units').should('be.visible')
cy.contains('Search').click()
cy.contains('Cross-Search').should('be.visible')
cy.contains('Localities').should('be.visible')
cy.contains('Species').should('be.visible')
@@ -22,7 +21,6 @@ describe('Button Tests', () => {
it('Localities button works', () => {
cy.visit('/')
cy.contains('Search').click()
cy.contains('Localities').click()
cy.url().should('include', '/locality')
cy.contains('Name').should('be.visible')
@@ -33,7 +31,6 @@ describe('Button Tests', () => {
it('Species button works', () => {
cy.visit('/')
cy.contains('Search').click()
cy.contains('Species').click()
cy.url().should('include', '/species')
cy.contains('Order').should('be.visible')
@@ -52,7 +49,6 @@ describe('Button Tests', () => {
it('Cross Search button works', () => {
cy.visit('/')
cy.contains('Search').click()
cy.contains('Cross-Search').click()
cy.url().should('include', '/crosssearch')
cy.contains('Locality name').should('be.visible')
@@ -67,7 +67,7 @@ export const SelectingTable = <T extends MRT_RowData, ParentType extends object>
if (isError) return <Box>Error fetching data for the selecting table.</Box>
if (!data) return <CircularProgress />
return (
<EditingModal buttonText={buttonText}>
<EditingModal dataCy={`${buttonText}-button`} buttonText={buttonText}>
{data ? (
<TableView<T>
data={filteredData}
@@ -113,6 +113,8 @@ export const ErrorBox = <T,>() => {
const { fieldsWithErrors } = useDetailContext<T>()
const fields = Object.keys(fieldsWithErrors)
const title = fields.length > 1 ? `${fields.length} invalid fields` : '1 invalid field'
return (
<Box
sx={{
@@ -126,7 +128,7 @@ export const ErrorBox = <T,>() => {
}}
>
<Typography color="text.secondary" gutterBottom>
{`${fields.length} Invalid fields`}
{title}
</Typography>
<List sx={{ maxHeight: '5em', maxWidth: '30em', padding: '0em 0.8em 0em 0.8em', overflow: 'auto' }}>
{fields.map(field => (
+25 -6
View File
@@ -45,7 +45,14 @@ export const LocalitiesMap = ({ localitiesQueryData, localitiesQueryIsFetching }
const markers: Layer = L.markerClusterGroup()
const localityIds = columnFilters.idList as unknown as number[]
const filteredLocalities = localitiesQueryData?.filter(locality => localityIds.includes(locality.lid))
const filterApplied = localityIds.length > 0 && localityIds.every(id => id === null)
const validIds = localityIds.filter(id => typeof id === 'number')
const filteredLocalities = !filterApplied
? localitiesQueryData?.filter(locality => validIds.includes(locality.lid))
: localitiesQueryData
filteredLocalities?.forEach(locality =>
markers.addLayer(
L.marker([locality.dec_lat, locality.dec_long], {
@@ -71,17 +78,29 @@ export const LocalitiesMap = ({ localitiesQueryData, localitiesQueryIsFetching }
noWrap: true,
})
const baseMaps = {
OpenTopoMap: topomap,
OpenStreetMap: osm,
}
// LAYERS ON TOP OF BASE MAPS
//// Add borders to the map
borders.forEach(poly => {
L.polygon(poly as LatLngExpression[], { color: 'gray', weight: 1 }).addTo(map)
})
// create a polygon layer for the country borders that is in layer control panel
const borderLayer = L.layerGroup()
borders.forEach(country_border => {
const polygon = L.polygon(country_border as LatLngExpression[], {
color: '#136f94',
fillOpacity: 0.3,
weight: 1,
})
borderLayer.addLayer(polygon)
})
const baseMaps = {
OpenTopoMap: topomap,
OpenStreetMap: osm,
Countries: borderLayer,
}
// Add a scale bar to the map
L.control.scale({ position: 'bottomright' }).addTo(map)
// Add a layer control to the map
+3 -9
View File
@@ -17,15 +17,9 @@ export const NavBar = () => {
const dispatch = useDispatch()
const notify = useNotify()
const pages: LinkDefinition[] = [
{
title: 'Search',
url: '',
children: [
{ title: 'Localities', url: '/locality' },
{ title: 'Species', url: '/species' },
{ title: 'Cross-Search', url: '/crosssearch' },
],
},
{ title: 'Localities', url: '/locality' },
{ title: 'Species', url: '/species' },
{ title: 'Cross-Search', url: '/crosssearch' },
{ title: 'References', url: '/reference' },
{ title: 'Time Units', url: '/time-unit' },
{ title: 'Time Bounds', url: '/time-bound', allowedRoles: [Role.Admin, Role.EditUnrestricted] },
+4 -2
View File
@@ -1709,7 +1709,8 @@ INSERT INTO `ref_authors` VALUES
(21368,2,1,'Ciochon','Russell'),
(24151,2,1,'Ogg','J.G.'),
(24187,2,1,'Kaakinen','A.'),
(24188,2,1,'Wang','X.');
(24188,2,1,'Wang','X.'),
(25000,2,1,'Doe','D.');
/*!40000 ALTER TABLE `ref_authors` ENABLE KEYS */;
UNLOCK TABLES;
@@ -2198,7 +2199,8 @@ INSERT INTO `ref_ref` VALUES
(21368,1,100,'Dated co-occurrence of Homo erectus and Gigantopithecus from Tham Khuyen Cave, Vietnam',1996,'93',NULL,3016,3020,NULL,NULL,NULL,NULL,NULL,NULL,'abstract','http://www.pnas.org/cgi/reprint/93/7/3016',NULL,NULL,NULL,'English',NULL,NULL,1,NULL),
(24151,2,NULL,'A Concise Geologic Time Scale: 2016',2016,NULL,NULL,NULL,240,'Elsevier',NULL,NULL,NULL,NULL,'9780444594686',NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,1,0),
(24187,13,NULL,'Helsinki Asian time update',2020,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'2020-01-29',NULL,1,NULL),
(24188,2,NULL,'Fossil Mammals of Asia',2013,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,1,NULL);
(24188,2,NULL,'Fossil Mammals of Asia',2013,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,1,NULL),
(25000,1,100,'A test reference that is not linked to any localities or species',2025,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
/*!40000 ALTER TABLE `ref_ref` ENABLE KEYS */;
UNLOCK TABLES;