diff --git a/src/model/MutableClimbDataSource.ts b/src/model/MutableClimbDataSource.ts index f0ff343d..6fa12827 100644 --- a/src/model/MutableClimbDataSource.ts +++ b/src/model/MutableClimbDataSource.ts @@ -107,6 +107,28 @@ export default class MutableClimbDataSource extends ClimbDataSource { const newDocs: ClimbChangeDocType[] = [] + // when adding new climbs, ensure they are added in expected sort order, such that: + // * multiple climbs keep the order they're input in. + // * all newly added climbs come after existing climbs. + const maxExisting = (await this.climbModel + .find({ _id: { $in: parent.climbs } }) + .select('metadata.left_right_index') + .sort({ 'metadata.left_right_index': -1 }) + .limit(1))[0]?.metadata.left_right_index + let newClimbLeftRightIndex = (maxExisting ?? 0) + 1 + + function resolveLeftRightIndex (i: number): { left_right_index: number } | null { + // user input is always prioritized + if (userInput[i].leftRightIndex != null) { + return { left_right_index: userInput[i].leftRightIndex ?? 0 } + } + // otherwise, auto-order new climbs + if (!idList[i].existed) { + return { left_right_index: newClimbLeftRightIndex++ } + } + return null + } + for (let i = 0; i < userInput.length; i++) { // when adding new climbs we require name and disciplines if (!idList[i].existed && userInput[i].name == null) { @@ -186,7 +208,7 @@ export default class MutableClimbDataSource extends ClimbDataSource { metadata: { areaRef: parent.metadata.area_id, lnglat: parent.metadata.lnglat, - ...userInput[i]?.leftRightIndex != null && { left_right_index: userInput[i].leftRightIndex } + ...resolveLeftRightIndex(i) }, ...!idList[i].existed && { createdBy: experimentalUserId ?? userId }, ...idList[i].existed && { updatedBy: userId }, diff --git a/src/model/__tests__/MutableClimbDataSource.ts b/src/model/__tests__/MutableClimbDataSource.ts index f78fbb5a..c126350a 100644 --- a/src/model/__tests__/MutableClimbDataSource.ts +++ b/src/model/__tests__/MutableClimbDataSource.ts @@ -178,22 +178,33 @@ describe('Climb CRUD', () => { const newIDs = await climbs.addOrUpdateClimbs( testUser, routesArea.metadata.area_id, - newClimbsToAdd) + newClimbsToAdd + ) expect(newIDs).toHaveLength(newClimbsToAdd.length) - const climb0 = await climbs.findOneClimbByMUUID(muid.from(newIDs[0])) - - // Validate new climb - expect(climb0).toMatchObject({ - name: newClimbsToAdd[0].name, - type: sanitizeDisciplines(newClimbsToAdd[0].disciplines), - content: { - description: newClimbsToAdd[0].description, - location: newClimbsToAdd[0].location, - protection: newClimbsToAdd[0].protection - } - }) + // Validate all climbs were added, and in the order we expect + for (const [i, climbIn] of newClimbsToAdd.entries()) { + const climbOut = await climbs.findOneClimbByMUUID(muid.from(newIDs[i])) + + // Validate new climb + expect(climbOut).toMatchObject({ + name: climbIn.name, + type: sanitizeDisciplines(climbIn.disciplines), + metadata: { + left_right_index: i + 1 + }, + ...climbIn.description === undefined + ? {} + : { + content: { + description: climbIn.description, + location: climbIn.location, + protection: climbIn.protection + } + } + }) + } // California contains subareas. Should fail. await expect( @@ -201,7 +212,13 @@ describe('Climb CRUD', () => { ).rejects.toThrowError(/You can only add climbs to a crag/) // Route-only area should accept new boulder problems - await climbs.addOrUpdateClimbs(testUser, routesArea.metadata.area_id, [newBoulderProblem1]) + const [newBoulderID] = await climbs.addOrUpdateClimbs(testUser, routesArea.metadata.area_id, [newBoulderProblem1]) + // Should come after existing climbs + expect(await climbs.findOneClimbByMUUID(muid.from(newBoulderID))).toMatchObject({ + metadata: { + left_right_index: newClimbsToAdd.length + 1 + } + }) }) it('can add new boulder problems', async () => {