From 6530f25c69723a53a142dceaa31c26b19f356113 Mon Sep 17 00:00:00 2001 From: courthive Date: Fri, 12 Apr 2024 15:46:22 -0400 Subject: [PATCH] test: :white_check_mark: `scaledTeamAssignments`: extend test coverage; GOLD_TEAM_CHALLENGE scenario --- .../generators/mocks/generateEventWithDraw.ts | 6 +- .../participants/scaledTeamAssignment.ts | 11 +- .../mutations/events/generateLineUps.test.ts | 12 +- .../team/scaledGenderedTeamAssignment.test.ts | 121 ++++++++++++++++++ .../{ => team}/scaledTeamAssignments.test.ts | 9 +- 5 files changed, 138 insertions(+), 21 deletions(-) create mode 100644 src/tests/mutations/participants/team/scaledGenderedTeamAssignment.test.ts rename src/tests/mutations/participants/{ => team}/scaledTeamAssignments.test.ts (99%) diff --git a/src/assemblies/generators/mocks/generateEventWithDraw.ts b/src/assemblies/generators/mocks/generateEventWithDraw.ts index c3330d3b3c..ae0fefa726 100644 --- a/src/assemblies/generators/mocks/generateEventWithDraw.ts +++ b/src/assemblies/generators/mocks/generateEventWithDraw.ts @@ -1,10 +1,10 @@ import { generateDrawDefinition } from '../drawDefinitions/generateDrawDefinition/generateDrawDefinition'; import { automatedPlayoffPositioning } from '@Mutate/drawDefinitions/automatedPlayoffPositioning'; +import { setParticipantScaleItem } from '@Mutate/participants/scaleItems/addScaleItems'; import { checkRequiredParameters } from '@Helpers/parameters/checkRequiredParameters'; import { completeDrawMatchUps, completeDrawMatchUp } from './completeDrawMatchUps'; import { addPlayoffStructures } from '@Mutate/drawDefinitions/addPlayoffStructures'; import { isMatchUpEventType } from '@Helpers/matchUpEventTypes/isMatchUpEventType'; -import { setParticipantScaleItem } from '@Mutate/participants/scaleItems/addScaleItems'; import { addDrawDefinition } from '@Mutate/drawDefinitions/addDrawDefinition'; import { addParticipants } from '@Mutate/participants/addParticipants'; import { allDrawMatchUps } from '@Query/matchUps/getAllDrawMatchUps'; @@ -76,6 +76,7 @@ export function generateEventWithDraw(params) { drawExtensions, completionGoal, tieFormatName, + buildTeams, seedsCount, timeItems, drawName, @@ -229,11 +230,12 @@ export function generateEventWithDraw(params) { mIndex += genders[MALE]; rIndex += mixedCount; + const individualParticipantIds = buildTeams !== false ? [...fPIDs, ...mPIDs, ...rIDs] : []; return { - individualParticipantIds: [...fPIDs, ...mPIDs, ...rIDs], participantOtherName: `TM${teamIndex + 1}`, participantName: `Team ${teamIndex + 1}`, participantRole: COMPETITOR, + individualParticipantIds, participantType: TEAM, participantId: UUID(), }; diff --git a/src/mutate/participants/scaledTeamAssignment.ts b/src/mutate/participants/scaledTeamAssignment.ts index fe8899e773..9a22200d4d 100644 --- a/src/mutate/participants/scaledTeamAssignment.ts +++ b/src/mutate/participants/scaledTeamAssignment.ts @@ -1,10 +1,12 @@ import { participantScaleItem } from '@Query/participant/participantScaleItem'; import { getParticipantId } from '@Functions/global/extractors'; import { getFlightProfile } from '@Query/event/getFlightProfile'; +import { addParticipants } from './addParticipants'; import { isConvertableInteger } from '@Tools/math'; import { generateRange } from '@Tools/arrays'; -import { addParticipants } from './addParticipants'; +// constants and types +import { INDIVIDUAL, TEAM_PARTICIPANT } from '@Constants/participantConstants'; import { Event, Participant, Tournament } from '@Types/tournamentTypes'; import { DIRECT_ACCEPTANCE } from '@Constants/entryStatusConstants'; import { COMPETITOR } from '@Constants/participantRoles'; @@ -20,7 +22,6 @@ import { PARTICIPANT_NOT_FOUND, TEAM_NOT_FOUND, } from '@Constants/errorConditionConstants'; -import { INDIVIDUAL, TEAM_PARTICIPANT } from '@Constants/participantConstants'; /* scaledParticipants are equivalent to scaledEntries @@ -36,13 +37,13 @@ scaleAttributes can include { accessor: 'attribute' } which will return scaleIte */ type ScaledTeamAssignmentArgs = { - clearExistingAssignments?: boolean; individualParticipantIds?: string[]; + clearExistingAssignments?: boolean; reverseAssignmentOrder?: boolean; - initialTeamIndex?: number; - scaledParticipants?: any[]; teamParticipantIds?: string[]; tournamentRecord: Tournament; + scaledParticipants?: any[]; + initialTeamIndex?: number; scaleAttributes?: any; teamNameBase?: string; teamsCount?: number; diff --git a/src/tests/mutations/events/generateLineUps.test.ts b/src/tests/mutations/events/generateLineUps.test.ts index ca90200905..171f0d6830 100644 --- a/src/tests/mutations/events/generateLineUps.test.ts +++ b/src/tests/mutations/events/generateLineUps.test.ts @@ -101,11 +101,11 @@ it('will assign TEAM positions based on ranking', () => { scaleAllParticipants: true, }; const { - tournamentRecord, drawIds: [drawId], } = mocksEngine.generateTournamentRecord({ tournamentName: 'Gold Team Challenge', participantsProfile, + setState: true, drawProfiles: [ { category: { ageCategoryCode: categoryName }, @@ -116,9 +116,6 @@ it('will assign TEAM positions based on ranking', () => { ], }); - const result = tournamentEngine.setState(tournamentRecord); - expect(result.success).toEqual(true); - const { participants } = tournamentEngine.getParticipants({ withIndividualParticipants: true, withScaleValues: true, @@ -202,12 +199,12 @@ it('can generate lineUps for TEAM events', () => { }; const { eventIds: [eventId], - tournamentRecord, drawIds, } = mocksEngine.generateTournamentRecord({ completeAllMatchUps: true, participantsProfile, policyDefinitions, + setState: true, drawProfiles: [ { category: { ageCategoryCode: categoryName }, @@ -218,9 +215,6 @@ it('can generate lineUps for TEAM events', () => { ], }); - let result = tournamentEngine.setState(tournamentRecord); - expect(result.success).toEqual(true); - const { matchUps } = tournamentEngine.allTournamentMatchUps(); // all matchUps cannot be complelted if all participants have not been assigned propertly expect(matchUps.every((m) => m.winningSide)).toEqual(true); @@ -233,7 +227,7 @@ it('can generate lineUps for TEAM events', () => { // !!!!!!!!!!!!!!!!!!!!!!!!! DELETE DRAW_DEFINITION !!!!!!!!!!!!!!!!!!!!!!!!!! // Now delete the draw and all of the existing pairParticipants - result = tournamentEngine.deleteDrawDefinitions({ drawIds }); + let result = tournamentEngine.deleteDrawDefinitions({ drawIds }); expect(result.error).toEqual(SCORES_PRESENT); result = tournamentEngine.deleteDrawDefinitions({ drawIds, force: true }); diff --git a/src/tests/mutations/participants/team/scaledGenderedTeamAssignment.test.ts b/src/tests/mutations/participants/team/scaledGenderedTeamAssignment.test.ts new file mode 100644 index 0000000000..edb9e5a809 --- /dev/null +++ b/src/tests/mutations/participants/team/scaledGenderedTeamAssignment.test.ts @@ -0,0 +1,121 @@ +import { participantScaleItem } from '@Query/participant/participantScaleItem'; +import tournamentEngine from '@Engines/syncEngine'; +import mocksEngine from '@Assemblies/engines/mock'; +import { expect, it } from 'vitest'; + +// constants +import { USTA_GOLD_TEAM_CHALLENGE } from '@Constants/tieFormatConstants'; +import { SINGLES_EVENT, TEAM_EVENT } from '@Constants/eventConstants'; +import { INDIVIDUAL } from '@Constants/participantConstants'; +import { FEMALE, MALE } from '@Constants/genderConstants'; +import { ScaleAttributes } from '@Types/factoryTypes'; +import { RANKING } from '@Constants/scaleConstants'; + +it('can generate balanced gendered teams for Mixed Gender events', () => { + const GOLD_TEAM_SIZE = 8; + const eventId = 'eid'; + const drawId = 'did'; + const drawSize = 8; + const C18U = '18U'; + + const mockProfile = { + participantsProfile: { category: { categoryName: '18U' }, scaleAllParticipants: true }, + tournamentName: 'Gold Team Challenge', + setState: true, + drawProfiles: [ + { + tieFormatName: USTA_GOLD_TEAM_CHALLENGE, + category: { ageCategoryCode: C18U }, + eventType: TEAM_EVENT, + buildTeams: false, + generate: false, + drawSize, + eventId, + drawId, + }, + ], + }; + + mocksEngine.generateTournamentRecord(mockProfile); + + const participants = tournamentEngine.getParticipants().participants; + + // expect that teams are generated but have no individualParticipantIds + const entries = tournamentEngine.getEvent({ eventId }).event.entries; + const teamParticipantIds = entries.map((entry) => entry.participantId); + const enteredTeams = participants.filter((p) => teamParticipantIds.includes(p.participantId)); + expect(enteredTeams.every((team) => !team.individualParticipantIds.length)).toBe(true); + + const individualParticipants = participants.filter((p) => p.participantType === INDIVIDUAL); + const individualParticipantsCount = drawSize * GOLD_TEAM_SIZE; + const femaleParticipants = individualParticipants + .filter((p) => p.person.sex === FEMALE) + .slice(0, individualParticipantsCount / 2); + const maleParticipants = individualParticipants + .filter((p) => p.person.sex === MALE) + .slice(0, individualParticipantsCount / 2); + + expect(femaleParticipants.length).toBe(maleParticipants.length); + expect(femaleParticipants.length).toBe(individualParticipantsCount / 2); + + const scaleAttributes: ScaleAttributes = { + eventType: SINGLES_EVENT, + scaleType: RANKING, + scaleName: C18U, + }; + + const scaledFemaleParticipants = femaleParticipants.map((participant) => ({ + scaleValue: participantScaleItem({ participant, scaleAttributes })?.scaleItem?.scaleValue, + participantId: participant.participantId, + })); + let result = tournamentEngine.scaledTeamAssignment({ + scaledParticipants: scaledFemaleParticipants, + teamParticipantIds, + }); + expect(result.success).toBe(true); + + const scaledMaleParticipants = maleParticipants.map((participant) => ({ + scaleValue: participantScaleItem({ participant, scaleAttributes })?.scaleItem?.scaleValue, + participantId: participant.participantId, + })); + result = tournamentEngine.scaledTeamAssignment({ + scaledParticipants: scaledMaleParticipants, + clearExistingAssignments: false, + teamParticipantIds, + }); + expect(result.success).toBe(true); + + const teamsWithIndividualParticipants = tournamentEngine.getParticipants({ + participantFilters: { participantIds: teamParticipantIds }, + withIndividualParticipants: true, + }).participants; + expect(teamsWithIndividualParticipants.every((team) => team.individualParticipantIds.length === GOLD_TEAM_SIZE)).toBe( + true, + ); + + const teamRankingProfiles = teamsWithIndividualParticipants.map((team) => { + return team.individualParticipants.map((participant) => { + const scaleItem = participantScaleItem({ participant, scaleAttributes })?.scaleItem; + return { gender: participant.person.sex, value: scaleItem?.scaleValue }; + }); + }); + + /** + * Team Males and Females have been separately waterfalled by ranking value + * This test confirms that the ranking values are in the expected order + */ + teamRankingProfiles.forEach((teamRankingProfile, i) => { + if (i) { + const previousTeamRankingProfile = teamRankingProfiles[i - 1]; + teamRankingProfile.forEach((rankingProfile, j) => { + const previousRankingProfile = previousTeamRankingProfile[j]; + const expectLess = j % 2; + if (expectLess) { + expect(rankingProfile.value).toBeLessThan(previousRankingProfile.value); + } else { + expect(rankingProfile.value).toBeGreaterThan(previousRankingProfile.value); + } + }); + } + }); +}); diff --git a/src/tests/mutations/participants/scaledTeamAssignments.test.ts b/src/tests/mutations/participants/team/scaledTeamAssignments.test.ts similarity index 99% rename from src/tests/mutations/participants/scaledTeamAssignments.test.ts rename to src/tests/mutations/participants/team/scaledTeamAssignments.test.ts index 12958ec5f1..95e338b369 100644 --- a/src/tests/mutations/participants/scaledTeamAssignments.test.ts +++ b/src/tests/mutations/participants/team/scaledTeamAssignments.test.ts @@ -5,10 +5,10 @@ import mocksEngine from '@Assemblies/engines/mock'; import { generateRange } from '@Tools/arrays'; import { expect, it } from 'vitest'; -// constants +// constants and types import { INVALID_PARTICIPANT_IDS, INVALID_VALUES, MISSING_VALUE } from '@Constants/errorConditionConstants'; -import { TEAM_PARTICIPANT } from '@Constants/participantConstants'; import { SINGLES_EVENT, TEAM_EVENT } from '@Constants/eventConstants'; +import { TEAM_PARTICIPANT } from '@Constants/participantConstants'; import { UNGROUPED } from '@Constants/entryStatusConstants'; import { COMPETITOR } from '@Constants/participantRoles'; import { ScaleAttributes } from '@Types/factoryTypes'; @@ -32,11 +32,10 @@ it('can automatically assign participants to teams using individualParticipantId eventIds: [eventId], } = mocksEngine.generateTournamentRecord({ participantsProfile, + setState: true, eventProfiles, }); - tournamentEngine.setState(tournamentRecord); - const individualParticipantIds = tournamentRecord.participants.map(getParticipantId); let result = tournamentEngine.addEventEntries({ @@ -174,8 +173,8 @@ it('can automatically assign participants to teams using scaledParticipants', () }; const scaledParticipants = individualParticipants.map((participant) => ({ - participantId: participant.participantId, scaleValue: participantScaleItem({ participant, scaleAttributes })?.scaleItem?.scaleValue, + participantId: participant.participantId, })); const teamParticipantIds = teamParticipants.map(getParticipantId);