diff --git a/OCCURRENCE_UI_RENAME_PLAN.md b/OCCURRENCE_UI_RENAME_PLAN.md new file mode 100644 index 00000000..3bb20af6 --- /dev/null +++ b/OCCURRENCE_UI_RENAME_PLAN.md @@ -0,0 +1,212 @@ +# Feature Plan: Rename “Locality-Species” UI Labels to “Occurrence(s)” + +## 1) Assumptions & Scope +- This is a **UI terminology update only**: replace visible labels such as “Locality-Species”, “Locality Species”, and “Locality-Species-Cross-Search” with “Occurrence” / “Occurrences”. +- **Database and backend persistence remain unchanged**: table names (including `now_ls`), Prisma model names, API route names, and payload contracts are not renamed. +- Expected impact is concentrated in frontend presentation layers (navigation labels, page/tab/table captions, breadcrumbs, toolbar text, empty states, and helper text), plus any shared constants feeding those labels. +- Backend impact is limited to optional API response metadata strings only if currently surfaced directly in UI; otherwise no backend changes. +- Role-based authorization behavior does not change. Existing coordinator/admin restrictions remain as-is. +- No migration should be required unless a persisted settings/config table stores user-visible caption text (unlikely; verify quickly before implementation). + +## 2) High-Level Plan +1. **Inventory all user-visible strings** + - Search frontend and shared UI config for “Locality-Species”, “Locality Species”, and “Cross-Search” variants. + - Classify each usage as singular (“Occurrence”) vs plural (“Occurrences”) based on context. + +2. **Centralize/normalize labels where needed** + - If labels are scattered literals, introduce/extend a UI label constants module for this domain to reduce future drift. + - Keep route paths/IDs stable unless they are explicitly user-facing display text. + +3. **Update UI surfaces** + - Navigation menus, page titles, tabs, table headings, card headers, action dialogs, and search forms. + - Ensure document titles and route shell wrappers reflect the new term. + +4. **Preserve API/data compatibility** + - Do not rename backend endpoints, Prisma models, DB tables, or query parameters unless purely display-oriented. + - Keep request/response schema compatibility for frontend consumers. + +5. **Auth/permissions verification** + - Confirm that label updates do not accidentally expose hidden actions/sections. + - Validate coordinator-only UI controls still gate correctly after refactors. + +6. **Testing and QA** + - Frontend unit/integration tests for rendered labels and key page headings. + - Optional E2E smoke checks for navigation text and cross-search page captions. + - Run lint/type-check; update snapshots where appropriate. + +7. **Docs and release notes** + - Add a concise changelog/readme note that terminology changed in UI only. + +## 3) Tasks (JSON) +```json +[ + { + "id": "T1", + "title": "Inventory all Locality-Species UI label usages", + "summary": "Locate every user-visible occurrence of Locality-Species terminology in frontend components, pages, navigation config, and tests.", + "app": "frontend", + "files_touched": [ + "frontend/src/components/**", + "frontend/src/pages/**", + "frontend/src/App.tsx", + "frontend/src/router/**", + "frontend/src/redux/**", + "frontend/tests/**" + ], + "migrations": false, + "permissions": [ + "No permission model changes", + "Verify restricted views retain existing role gates" + ], + "acceptance_criteria": [ + "A complete checklist of UI label locations is produced", + "Each location is tagged as singular (Occurrence) or plural (Occurrences)" + ], + "test_plan": [ + "Run repository search for all target label variants", + "Peer-review checklist against major UI routes" + ], + "estimate_hours": 1.0, + "priority": "high" + }, + { + "id": "T2", + "title": "Replace navigation and route-shell captions", + "summary": "Update menu entries, breadcrumb labels, and route-level page titles from Locality-Species wording to Occurrence(s) without changing route paths.", + "app": "frontend", + "files_touched": [ + "frontend/src/App.tsx", + "frontend/src/pages/**", + "frontend/src/components/Navigation/**", + "frontend/src/router/**" + ], + "migrations": false, + "permissions": [ + "Visible only where existing route access is allowed", + "Coordinator-only routes remain coordinator-only" + ], + "acceptance_criteria": [ + "Primary navigation no longer shows Locality-Species labels", + "Document titles and breadcrumbs use Occurrence/Occurrences consistently" + ], + "test_plan": [ + "Frontend render tests for nav labels", + "Manual smoke test through key routes" + ], + "estimate_hours": 2.0, + "priority": "high" + }, + { + "id": "T3", + "title": "Update table, tab, and cross-search captions", + "summary": "Rename captions in list/table headers, tabs, and cross-search UI text to Occurrence(s) while preserving data bindings to now_ls-backed APIs.", + "app": "frontend", + "files_touched": [ + "frontend/src/components/**", + "frontend/src/pages/**", + "frontend/src/hooks/**", + "frontend/src/redux/**" + ], + "migrations": false, + "permissions": [ + "No new permissions", + "Existing disabled/hidden controls remain unchanged" + ], + "acceptance_criteria": [ + "All table/tab captions display Occurrence terminology", + "Cross-search captions no longer contain Locality-Species wording", + "Data loading behavior is unchanged" + ], + "test_plan": [ + "Update/extend component tests asserting headings and tab labels", + "Run regression checks for list and cross-search screens" + ], + "estimate_hours": 2.5, + "priority": "high" + }, + { + "id": "T4", + "title": "Harden consistency via shared UI label constants", + "summary": "Introduce or update a shared constants file for Occurrence labels to prevent future terminology drift across components.", + "app": "frontend", + "files_touched": [ + "frontend/src/constants/**", + "frontend/src/components/**", + "frontend/src/pages/**" + ], + "migrations": false, + "permissions": [ + "No permission changes" + ], + "acceptance_criteria": [ + "Key labels are sourced from centralized constants", + "No duplicate legacy Locality-Species literals remain in UI code" + ], + "test_plan": [ + "Static search confirms no legacy literals in frontend src", + "Type-check passes after refactor" + ], + "estimate_hours": 1.5, + "priority": "medium" + }, + { + "id": "T5", + "title": "Validate non-functional requirements and documentation", + "summary": "Run lint/type-check/tests, capture visual verification, and add a documentation note clarifying UI-only terminology change with no DB/table rename.", + "app": "fullstack", + "files_touched": [ + "README.md", + "CHANGELOG.md", + "frontend/tests/**", + "cypress/**" + ], + "migrations": false, + "permissions": [ + "Verify UI access boundaries through smoke tests" + ], + "acceptance_criteria": [ + "Lint and type-check complete successfully", + "Relevant automated tests pass", + "Docs explicitly state now_ls table name is unchanged" + ], + "test_plan": [ + "npm run lint", + "npm run tsc", + "Frontend test suite for affected screens", + "Optional Cypress smoke for navigation/caption text" + ], + "estimate_hours": 2.0, + "priority": "medium" + } +] +``` + +## 4) Risks & Mitigations +- **Auth Failures**: UI refactors may bypass existing guard wrappers. + - *Mitigation*: regression-check protected routes/components with both coordinator and non-coordinator accounts. +- **Data Migration Risk**: accidental backend renaming could break persistence contracts. + - *Mitigation*: explicitly prohibit DB/Prisma/API renames; no migration files in this feature. +- **Performance**: negligible risk, but broad refactor could trigger unnecessary rerenders. + - *Mitigation*: keep changes text-only where possible; avoid state shape changes. +- **Error Handling Drift**: changed labels in error banners/toasts may become inconsistent. + - *Mitigation*: update shared message constants and validate key failure flows. +- **Security**: low direct risk, but route/config edits can accidentally expose links. + - *Mitigation*: preserve existing authorization checks and conditional rendering. +- **Rollback**: need safe revert if terminology causes confusion. + - *Mitigation*: isolate commits to label updates and optional constants; revertable without schema impact. + +## 5) Out of Scope +- Renaming DB tables, Prisma models, backend route paths, or API field names. +- Legacy data migration/backfill tasks. +- Visual redesign/theming changes beyond text replacement. +- Full i18n localization framework rollout. +- Unrelated performance optimizations. + +## 6) Definition of Done ✅ +- All approved UI surfaces use “Occurrence” / “Occurrences” appropriately. +- No database table/model/API contract renames were introduced. +- Unit/integration (and applicable E2E smoke) tests pass for affected areas. +- Linting and type checks pass. +- Role-based visibility and access behavior verified unchanged. +- Documentation/changelog note added for terminology-only update. +- CI pipeline succeeds. diff --git a/README.md b/README.md index 00cfc613..14d7845e 100644 --- a/README.md +++ b/README.md @@ -61,3 +61,11 @@ The new version is not deployed yet. - Switching methods restores previously entered age values (e.g., `min_age`, `max_age`, basis/fraction selections) instead of clearing them. - Locality age validation now evaluates required basis fields against the currently active dating method, while allowing preserved values from other methods to remain in draft state. - See [documentation/CHANGELOG.md](documentation/CHANGELOG.md) for release-level tracking. + + +### Reviewer notes: Occurrence terminology update + +- User-facing UI captions now use **Occurrence/Occurrences** in navigation, cross-search, and Species detail surfaces. +- This is a presentation-only rename: backend/database identifiers are unchanged, including the `now_ls` table and related API payload fields. +- See `frontend/src/constants/occurrenceLabels.ts` for centralized frontend label constants and `frontend/tests/locality-species-ui-label-inventory.md` for inventory coverage. + diff --git a/documentation/CHANGELOG.md b/documentation/CHANGELOG.md index 14c02d5e..243f2853 100644 --- a/documentation/CHANGELOG.md +++ b/documentation/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased ### Changed +- UI terminology now uses Occurrence/Occurrences instead of Locality-Species in user-facing captions; backend/database contracts remain unchanged (including `now_ls`). - Repositioned table row action icons to the right edge, tightened spacing, and added aria labels so Locality, Species, and related tables reclaim horizontal room while keeping tooltips and restriction markers accessible. - Species detail Locality-Species tab now renders MW Score as a computed, read-only display field based on mw_scale_min, mw_scale_max, and mw_value instead of a placeholder value. diff --git a/frontend/src/components/CrossSearch/CrossSearchTable.tsx b/frontend/src/components/CrossSearch/CrossSearchTable.tsx index 14a6969f..2326d735 100755 --- a/frontend/src/components/CrossSearch/CrossSearchTable.tsx +++ b/frontend/src/components/CrossSearch/CrossSearchTable.tsx @@ -6,6 +6,7 @@ import { useGetAllCrossSearchQuery, useGetAllCrossSearchLocalitiesQuery } from ' import { usePageContext } from '../Page' import { LocalitiesMap } from '../Map/LocalitiesMap' import { formatWithMaxThreeDecimals } from '@/util/numberFormatting' +import { occurrenceLabels } from '@/constants/occurrenceLabels' export const CrossSearchTable = ({ selectorFn }: { selectorFn?: (newObject: CrossSearch) => void }) => { const { sqlLimit, sqlOffset, sqlColumnFilters, sqlOrderBy } = usePageContext() @@ -817,7 +818,7 @@ export const CrossSearchTable = ({ selectorFn }: { selectorFn?: (newObject: Cros <> - title="Locality-Species-Cross-Search" + title={occurrenceLabels.crossSearchTitle} selectorFn={selectorFn} checkRowRestriction={checkRowRestriction} idFieldName="lid_now_loc" @@ -825,7 +826,7 @@ export const CrossSearchTable = ({ selectorFn }: { selectorFn?: (newObject: Cros isFetching={isFetching} visibleColumns={visibleColumns} data={crossSearchQueryData} - url="crosssearch" + url="occurrence" enableColumnFilterModes={true} serverSidePagination={true} isCrossSearchTable={true} diff --git a/frontend/src/components/FrontPage.tsx b/frontend/src/components/FrontPage.tsx index e104c3c6..58973d2f 100755 --- a/frontend/src/components/FrontPage.tsx +++ b/frontend/src/components/FrontPage.tsx @@ -9,6 +9,7 @@ import { useEffect, useRef } from 'react' import mapSvg from '../resource/map.svg' import logo from '../resource/nowlogo.jpg' import '../styles/FrontPage.css' +import { occurrenceLabels } from '@/constants/occurrenceLabels' export const FrontPage = () => { const mapContainerRef = useRef(null) @@ -72,7 +73,7 @@ export const FrontPage = () => { Species {statisticsQueryData && statisticsQueryData.speciesCount} - Locality-species {statisticsQueryData && statisticsQueryData.localitySpeciesCount} + {occurrenceLabels.plural} {statisticsQueryData && statisticsQueryData.localitySpeciesCount}
diff --git a/frontend/src/components/NavBar.test.tsx b/frontend/src/components/NavBar.test.tsx new file mode 100644 index 00000000..42087988 --- /dev/null +++ b/frontend/src/components/NavBar.test.tsx @@ -0,0 +1,51 @@ +import { describe, expect, it, jest } from '@jest/globals' +import { render, screen } from '@testing-library/react' +import { MemoryRouter } from 'react-router-dom' +import { NavBar } from './NavBar' +import { occurrenceLabels } from '@/constants/occurrenceLabels' +import { Role } from '@/shared/types' + +jest.mock('react-redux', () => ({ + useDispatch: () => jest.fn(), +})) + +jest.mock('../redux/userReducer', () => ({ + clearUser: () => ({ type: 'user/clearUser' }), +})) + +jest.mock('../redux/api', () => ({ + api: { + util: { + resetApiState: () => ({ type: 'api/resetApiState' }), + }, + }, +})) + +jest.mock('@/hooks/user', () => ({ + useUser: () => ({ + token: null, + username: null, + role: Role.ReadOnly, + initials: null, + localities: [], + isFirstLogin: undefined, + }), +})) + +jest.mock('@/hooks/notification', () => ({ + useNotify: () => ({ notify: jest.fn() }), +})) + +jest.mock('../resource/nowlogo.jpg', () => 'now-logo') + +describe('NavBar', () => { + it('shows Occurrences navigation label', () => { + render( + + + + ) + + expect(screen.getByText(occurrenceLabels.plural)).toBeTruthy() + }) +}) diff --git a/frontend/src/components/NavBar.tsx b/frontend/src/components/NavBar.tsx index c72f596e..e23a6e16 100755 --- a/frontend/src/components/NavBar.tsx +++ b/frontend/src/components/NavBar.tsx @@ -9,6 +9,7 @@ import { useNotify } from '@/hooks/notification' import PersonIcon from '@mui/icons-material/Person' import '../styles/NavBar.css' import { LinkDefinition, NavBarLink } from './NavBarLink' +import { occurrenceLabels } from '@/constants/occurrenceLabels' import logo from '../resource/nowlogo.jpg' @@ -20,7 +21,7 @@ export const NavBar = () => { const pages: LinkDefinition[] = [ { title: 'Localities', url: '/locality' }, { title: 'Species', url: '/species' }, - { title: 'Locality-Species', url: '/crosssearch' }, + { title: occurrenceLabels.plural, url: '/occurrence' }, { title: 'References', url: '/reference' }, { title: 'Time Units', url: '/time-unit' }, { title: 'Time Bounds', url: '/time-bound', allowedRoles: [Role.Admin, Role.EditUnrestricted] }, diff --git a/frontend/src/components/Species/SpeciesDetails.tsx b/frontend/src/components/Species/SpeciesDetails.tsx index c6ad2ba2..129f8f64 100755 --- a/frontend/src/components/Species/SpeciesDetails.tsx +++ b/frontend/src/components/Species/SpeciesDetails.tsx @@ -17,6 +17,7 @@ import { emptySpecies } from '../DetailView/common/defaultValues' import { useNotify } from '@/hooks/notification' import { useEffect, useState } from 'react' import { fixNullValuesInTaxonomyFields } from '@/util/taxonomyUtilities' +import { occurrenceLabels } from '@/constants/occurrenceLabels' export const SpeciesDetails = ({ wrapWithUnsavedChangesProvider = true, @@ -105,7 +106,7 @@ export const SpeciesDetails = ({ content: , }, { - title: 'Locality Species', + title: occurrenceLabels.plural, content: , }, { diff --git a/frontend/src/components/Species/Tabs/LocalitySpeciesTab.tsx b/frontend/src/components/Species/Tabs/LocalitySpeciesTab.tsx index 8ff729f3..a77d1c77 100755 --- a/frontend/src/components/Species/Tabs/LocalitySpeciesTab.tsx +++ b/frontend/src/components/Species/Tabs/LocalitySpeciesTab.tsx @@ -11,6 +11,7 @@ import { calculateNormalizedMesowearScore } from '@/shared/utils/mesowear' import { applyDefaultSpeciesOrdering, hasActiveSortingInSearch } from '@/components/DetailView/common/DetailTabTable' import { useLocation } from 'react-router-dom' import { useMemo } from 'react' +import { occurrenceLabels } from '@/constants/occurrenceLabels' const hasMesowearScoreInputs = (row: SpeciesLocality) => { return ( @@ -197,7 +198,7 @@ export const LocalitySpeciesTab = () => { } const editingModal = ( - + @@ -212,7 +213,7 @@ export const LocalitySpeciesTab = () => { ) return ( - + {!mode.read && editingModal} , SpeciesDetailsType> columns={columns} diff --git a/frontend/src/components/Species/Tabs/__tests__/LocalitySpeciesTab.test.tsx b/frontend/src/components/Species/Tabs/__tests__/LocalitySpeciesTab.test.tsx index f01f5d4c..ad36eede 100644 --- a/frontend/src/components/Species/Tabs/__tests__/LocalitySpeciesTab.test.tsx +++ b/frontend/src/components/Species/Tabs/__tests__/LocalitySpeciesTab.test.tsx @@ -5,6 +5,7 @@ import type { MRT_ColumnDef, MRT_Row } from 'material-react-table' import { LocalitySpeciesTab } from '@/components/Species/Tabs/LocalitySpeciesTab' import { modeOptionToMode, useDetailContext } from '@/components/DetailView/Context/DetailContext' import type { SpeciesLocality } from '@/shared/types' +import { occurrenceLabels } from '@/constants/occurrenceLabels' jest.mock('@/components/DetailView/Context/DetailContext', () => ({ useDetailContext: jest.fn(), @@ -31,11 +32,19 @@ jest.mock('@/components/DetailView/common/EditableTable', () => ({ })) jest.mock('@/components/DetailView/common/EditingModal', () => ({ - EditingModal: ({ children }: { children: ReactNode }) =>
{children}
, + EditingModal: ({ children, buttonText }: { children: ReactNode; buttonText: string }) => ( +
+ {children} +
+ ), })) jest.mock('@/components/DetailView/common/tabLayoutHelpers', () => ({ - Grouped: ({ children }: { children: ReactNode }) =>
{children}
, + Grouped: ({ children, title }: { children: ReactNode; title: string }) => ( +
+ {children} +
+ ), })) const mockUseDetailContext = useDetailContext as jest.MockedFunction @@ -65,6 +74,14 @@ describe('LocalitySpeciesTab MW Score rendering', () => { expect(editableTableProps?.enableAdvancedTableControls).toBe(true) }) + it('uses occurrence terminology in modal button and group heading', () => { + const grouped = document.querySelector('[data-testid="grouped"]') + const editingModal = document.querySelector('[data-testid="editing-modal"]') + + expect(grouped?.getAttribute('data-title')).toBe(occurrenceLabels.informationSectionTitle) + expect(editingModal?.getAttribute('data-button-text')).toBe(occurrenceLabels.addNewButton) + }) + it('renders normalized score with 2 decimals for valid inputs', () => { const cellRenderer = getMwScoreCellRenderer() expect(cellRenderer).toBeDefined() diff --git a/frontend/src/components/TableView/TableView.test.tsx b/frontend/src/components/TableView/TableView.test.tsx index c9052441..317e7298 100644 --- a/frontend/src/components/TableView/TableView.test.tsx +++ b/frontend/src/components/TableView/TableView.test.tsx @@ -110,7 +110,7 @@ describe('TableView table help integration', () => { columns={[{ header: 'Name', accessorKey: 'name' }]} visibleColumns={{ name: true }} data={[{ id: '1', name: 'Alpha', full_count: 1 }]} - url="crosssearch" + url="occurrence" isFetching={false} isCrossSearchTable serverSidePagination diff --git a/frontend/src/components/TableView/TableView.tsx b/frontend/src/components/TableView/TableView.tsx index fb5c0e9a..8300e663 100755 --- a/frontend/src/components/TableView/TableView.tsx +++ b/frontend/src/components/TableView/TableView.tsx @@ -421,7 +421,7 @@ export const TableView = ({ tableName={title} kmlExport={kmlExport} svgExport={svgExport} - showNewButton={editRights.new && !selectorFn && title != 'Locality-Species-Cross-Search'} + showNewButton={editRights.new && !selectorFn && !isCrossSearchTable} isCrossSearchTable={isCrossSearchTable} selectorFn={selectorFn} hideLeftButtons={false} diff --git a/frontend/src/components/pages.tsx b/frontend/src/components/pages.tsx index 6c4747fe..b24d94df 100755 --- a/frontend/src/components/pages.tsx +++ b/frontend/src/components/pages.tsx @@ -64,7 +64,7 @@ export const crossSearchPage = ( } detailView={} - viewName="crosssearch" + viewName="occurrence" idFieldName="lid" createTitle={(loc: LocalityDetailsType) => `${loc.lid} ${loc.loc_name}, ${loc.country}`} createSubtitle={(loc: LocalityDetailsType) => diff --git a/frontend/src/constants/occurrenceLabels.ts b/frontend/src/constants/occurrenceLabels.ts new file mode 100644 index 00000000..522f87dc --- /dev/null +++ b/frontend/src/constants/occurrenceLabels.ts @@ -0,0 +1,7 @@ +export const occurrenceLabels = { + singular: 'Occurrence', + plural: 'Occurrences', + crossSearchTitle: 'Occurrences', + informationSectionTitle: 'Occurrences', + addNewButton: 'Add new Occurrence', +} as const diff --git a/frontend/src/router/index.tsx b/frontend/src/router/index.tsx index ba628a89..55d756a4 100644 --- a/frontend/src/router/index.tsx +++ b/frontend/src/router/index.tsx @@ -24,6 +24,7 @@ const router = createBrowserRouter([ element: , children: [ { index: true, element: frontPage }, + { path: 'occurrence/:id?', element: crossSearchPage }, { path: 'crosssearch/:id?', element: crossSearchPage }, { path: 'locality/:id?', element: localityPage }, { path: 'species/:id?', element: speciesPage }, diff --git a/frontend/src/shared/types/data.ts b/frontend/src/shared/types/data.ts index a6d0e01b..a05a4c87 100755 --- a/frontend/src/shared/types/data.ts +++ b/frontend/src/shared/types/data.ts @@ -24,7 +24,7 @@ export type LocalitySpeciesDetailsType = FixBigInt & { com_specie export type SpeciesLocality = FixBigInt & { now_loc: Prisma.now_loc // Explicitly required in the Species locality payload because MW Score is - // calculated client-side from these values in the Locality-Species table. + // calculated client-side from these values in the occurrence table. mw_scale_min: number | null mw_scale_max: number | null mw_value: number | null diff --git a/frontend/src/tests/README.md b/frontend/src/tests/README.md index 6d3c0c7b..2a5e10f8 100644 --- a/frontend/src/tests/README.md +++ b/frontend/src/tests/README.md @@ -5,6 +5,6 @@ - Execute `npm run test -- --watch=false` from the `frontend/` directory to run all Jest specs in CI mode. ## Country or Continent filtering coverage -- The Localities and Locality-Species tables now accept either a country name or a continent keyword in the "Country or Continent" column filter. +- The Localities and Occurrence tables now accept either a country name or a continent keyword in the "Country or Continent" column filter. - Automated coverage for the helper logic lives in `src/util/__tests__/countryContinents.test.ts`. Re-run that file directly with `npx jest src/util/__tests__/countryContinents.test.ts` if you need quick feedback while adjusting the mapping. - When performing manual QA, confirm that entering a continent (for example, "Africa") narrows both tables to countries mapped to that continent and that standard country filters still work as expected. diff --git a/frontend/src/tests/components/LocalitySpeciesTab.test.tsx b/frontend/src/tests/components/LocalitySpeciesTab.test.tsx index 418d27f9..bd0404b6 100644 --- a/frontend/src/tests/components/LocalitySpeciesTab.test.tsx +++ b/frontend/src/tests/components/LocalitySpeciesTab.test.tsx @@ -5,6 +5,7 @@ import { LocalitySpeciesTab } from '@/components/Species/Tabs/LocalitySpeciesTab import { modeOptionToMode, useDetailContext } from '@/components/DetailView/Context/DetailContext' import type { SpeciesLocality } from '@/shared/types' import type { MRT_ColumnDef, MRT_Row } from 'material-react-table' +import { occurrenceLabels } from '@/constants/occurrenceLabels' jest.mock('@/components/DetailView/Context/DetailContext', () => ({ useDetailContext: jest.fn(), @@ -30,11 +31,19 @@ jest.mock('@/components/DetailView/common/EditableTable', () => ({ })) jest.mock('@/components/DetailView/common/EditingModal', () => ({ - EditingModal: ({ children }: { children: ReactNode }) =>
{children}
, + EditingModal: ({ children, buttonText }: { children: ReactNode; buttonText: string }) => ( +
+ {children} +
+ ), })) jest.mock('@/components/DetailView/common/tabLayoutHelpers', () => ({ - Grouped: ({ children }: { children: ReactNode }) =>
{children}
, + Grouped: ({ children, title }: { children: ReactNode; title: string }) => ( +
+ {children} +
+ ), })) const mockUseDetailContext = useDetailContext as jest.MockedFunction @@ -81,4 +90,14 @@ describe('LocalitySpeciesTab MW score prerequisites', () => { const cellResult = cellRenderer?.({ row }) expect(cellResult).toBeTruthy() }) + + it('uses occurrence terminology in grouped heading and modal button', () => { + render() + + const grouped = document.querySelector('[data-testid="grouped"]') + const editingModal = document.querySelector('[data-testid="editing-modal"]') + + expect(grouped?.getAttribute('data-title')).toBe(occurrenceLabels.informationSectionTitle) + expect(editingModal?.getAttribute('data-button-text')).toBe(occurrenceLabels.addNewButton) + }) }) diff --git a/frontend/tests/locality-species-ui-label-inventory.md b/frontend/tests/locality-species-ui-label-inventory.md new file mode 100644 index 00000000..604986ad --- /dev/null +++ b/frontend/tests/locality-species-ui-label-inventory.md @@ -0,0 +1,55 @@ +# Locality-Species UI Label Inventory (Task T1) + +This checklist inventories user-visible "Locality-Species" terminology in frontend UI surfaces and maps each item to the intended replacement term. + +## Legend +- **Singular target**: `Occurrence` +- **Plural/collection target**: `Occurrences` + +## Inventory Checklist + +- [x] `frontend/src/components/NavBar.tsx` (line 23) + - Current UI label: `Locality-Species` (navigation item) + - Context: navigation entry linking to `/crosssearch` + - Target label: **Occurrences** (plural, module/list entry) + +- [x] `frontend/src/components/CrossSearch/CrossSearchTable.tsx` (line 820) + - Current UI label: `Locality-Species-Cross-Search` (table/view title) + - Context: cross-search page caption/title + - Target label: **Occurrence Cross-Search** (collection search surface) + +- [x] `frontend/src/components/Species/SpeciesDetails.tsx` (line 108) + - Current UI label: `Locality Species` (tab title) + - Context: species details tab that lists linked rows + - Target label: **Occurrences** (plural tab content) + +- [x] `frontend/src/components/Species/Tabs/LocalitySpeciesTab.tsx` (line 200) + - Current UI label: `Add new Locality Species` (action button text) + - Context: action creates one new row + - Target label: **Add new Occurrence** (singular action target) + +- [x] `frontend/src/components/Species/Tabs/LocalitySpeciesTab.tsx` (line 215) + - Current UI label: `Locality-Species Information` (group section title) + - Context: grouped information panel describing row collection + - Target label: **Occurrence Information** (domain section heading) + +- [x] `frontend/src/components/FrontPage.tsx` (line 75) + - Current UI label: `Locality-species` (statistics card label) + - Context: count of all rows in collection + - Target label: **Occurrences** (plural count label) + +## Related Non-UI Mentions (Do Not Rename in this task) + +These are not user-facing captions and are intentionally excluded from UI wording replacement: + +- Type names and identifiers (e.g. `LocalitySpecies`, `LocalitySpeciesDetailsType`) in shared/frontend TypeScript. +- File names and test suite names that reference implementation details. +- Export filename slug in `frontend/src/components/CrossSearch/CrossSearchExportMenuItem.tsx` (`locality-species-...csv`) unless product decides file naming should also change. +- Internal comparisons/guards in `frontend/src/components/TableView/TableView.tsx` using the legacy title token. + +## Validation Notes + +- Database table and backend contracts remain unchanged (`now_ls` is not renamed in this feature). +- Search terms used: `Locality-Species`, `Locality Species`, `Locality-species`, `Locality-Species-Cross-Search`, `locality-species`. +- Coverage scope: `frontend/src/**` and `frontend/tests/**`. +- Result: all discovered user-visible captions are listed above and tagged with singular/plural replacement intent.