diff --git a/specifyweb/frontend/js_src/lib/components/WbPlanView/__tests__/automapper.test.ts b/specifyweb/frontend/js_src/lib/components/WbPlanView/__tests__/automapper.test.ts index 0e6d9abab75..0e7101bd1b7 100644 --- a/specifyweb/frontend/js_src/lib/components/WbPlanView/__tests__/automapper.test.ts +++ b/specifyweb/frontend/js_src/lib/components/WbPlanView/__tests__/automapper.test.ts @@ -3,8 +3,9 @@ import { theories } from '../../../tests/utils'; import type { RA } from '../../../utils/types'; import type { AutoMapperResults } from '../autoMapper'; import { - type AutoMapperConstructorParameters, AutoMapper as AutoMapperConstructor, + type AutoMapperConstructorParameters, + circularTables, } from '../autoMapper'; requireContext(); @@ -198,3 +199,22 @@ theories( }, ] ); + +test('circular tables are calculated correctly', () => + expect(circularTables()).toMatchInlineSnapshot(` + [ + "[table Agent]", + "[table Container]", + "[table Geography]", + "[table GeographyTreeDefItem]", + "[table GeologicTimePeriod]", + "[table GeologicTimePeriodTreeDefItem]", + "[table LithoStrat]", + "[table LithoStratTreeDefItem]", + "[table ReferenceWork]", + "[table Storage]", + "[table StorageTreeDefItem]", + "[table Taxon]", + "[table TaxonTreeDefItem]", + ] + `)); diff --git a/specifyweb/frontend/js_src/lib/components/WbPlanView/autoMapper.ts b/specifyweb/frontend/js_src/lib/components/WbPlanView/autoMapper.ts index 6385a0130ea..d8261728aca 100644 --- a/specifyweb/frontend/js_src/lib/components/WbPlanView/autoMapper.ts +++ b/specifyweb/frontend/js_src/lib/components/WbPlanView/autoMapper.ts @@ -13,7 +13,7 @@ import type { IR, R, RA, Writable, WritableArray } from '../../utils/types'; import { filterArray } from '../../utils/types'; import { findArrayDivergencePoint } from '../../utils/utils'; import type { AnyTree } from '../DataModel/helperTypes'; -import { getModel, strictGetModel } from '../DataModel/schema'; +import { getModel, schema, strictGetModel } from '../DataModel/schema'; import type { Relationship } from '../DataModel/specifyField'; import type { SpecifyModel } from '../DataModel/specifyModel'; import type { Tables } from '../DataModel/types'; @@ -602,6 +602,10 @@ export class AutoMapper { if ( // Don't iterate over the same table again this.searchedTables.includes(tableName) || + // Don't allow circular mappings + (mappingPath.length > 0 && + tableName === this.baseTable.name && + !circularTables().includes(this.baseTable)) || // Don't go beyond the depth limit mappingPath.length > depthLimit ) @@ -989,3 +993,12 @@ export class AutoMapper { return !pathContainsToManyReferences && !this.allowMultipleMappings; } } + +/** + * Tables that have relationships to themself + */ +export const circularTables = f.store>(() => + Object.values(schema.models).filter(({ relationships, name }) => + relationships.some(({ relatedModel }) => relatedModel.name === name) + ) +);