-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #19 from RaphaelEscrig/feat/blank-rounds
Add blank rounds
- Loading branch information
Showing
47 changed files
with
2,396 additions
and
44,364 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/** MODELS */ | ||
import type { SpecialtyCode } from "@/modules/shared/domain/models"; | ||
/** PAGES */ | ||
import CitiesBlankRoundsPage from "@/modules/cities/react/pages/blank-rounds/blank-rounds.page"; | ||
/** UTILS */ | ||
import { isValidSpecialty } from "@/modules/shared/utils/specialty.util"; | ||
|
||
type Props = { | ||
readonly searchParams?: { | ||
readonly round?: string; | ||
readonly rank?: string; | ||
readonly specialty?: string; | ||
}; | ||
}; | ||
|
||
const NextCitiesBlankRoundsPage = ({ searchParams }: Props) => { | ||
const round = searchParams?.round ? parseInt(searchParams.round) : undefined; | ||
const rank = searchParams?.rank ? parseInt(searchParams.rank) : undefined; | ||
const specialty = | ||
searchParams?.specialty && isValidSpecialty(searchParams.specialty) | ||
? (searchParams.specialty as SpecialtyCode) | ||
: undefined; | ||
|
||
return ( | ||
<CitiesBlankRoundsPage rank={rank} round={round} specialty={specialty} /> | ||
); | ||
}; | ||
|
||
export default NextCitiesBlankRoundsPage; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
/** PAGES */ | ||
import SpecialtiesBlankRoundsPage from "@/modules/specialties/react/pages/blank-rounds/specialties-blank-rounds.page"; | ||
|
||
type Props = { | ||
readonly searchParams?: { | ||
readonly round?: string; | ||
readonly rank?: string; | ||
}; | ||
}; | ||
|
||
export default async function NextWhiteRoundsPage({ searchParams }: Props) { | ||
const round = searchParams?.round ? parseInt(searchParams.round) : 1; | ||
const rank = searchParams?.rank ? parseInt(searchParams.rank) : undefined; | ||
|
||
return <SpecialtiesBlankRoundsPage rank={rank} round={round} />; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
23 changes: 23 additions & 0 deletions
23
src/modules/cities/core/domain/models/city-blank-round.model.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
export type Form = { | ||
readonly round: string; | ||
readonly rank: string; | ||
readonly specialty: string; | ||
}; | ||
|
||
export type FormErrors = Record< | ||
keyof Pick<Form, "round" | "rank" | "specialty">, | ||
string | null | ||
>; | ||
|
||
export type City = { | ||
readonly name: string; | ||
readonly bestRank: number | null; | ||
readonly worstRank: number | null; | ||
readonly places: number; | ||
readonly assignedPlaces: number; | ||
readonly remainingPlaces: number; | ||
}; | ||
|
||
export type CityWithRankResult = City & { | ||
readonly wouldHaveIt: boolean; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
/** MODELS */ | ||
export * as CityRank from "@/modules/cities/core/domain/models/city-rank.model"; | ||
export * as CitySimulation from "@/modules/cities/core/domain/models/city-simulation.model"; | ||
export * as CityBlankRound from "@/modules/cities/core/domain/models/city-blank-round.model"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
/** CONSTANTS */ | ||
import { | ||
SPECIALTIES_BLANK_ROUND_MAX_ROUND, | ||
SPECIALTIES_BLANK_ROUND_MIN_ROUND, | ||
} from "@/modules/specialties/core/domain/constants"; | ||
import { SPECIALTIES } from "@/modules/shared/domain/constants"; | ||
/** IMMER */ | ||
import { produce } from "immer"; | ||
/** MODELS */ | ||
import type { CityBlankRound } from "@/modules/cities/core/domain/models"; | ||
/** UTILS */ | ||
import { castStringNumberToNumber } from "@/modules/shared/utils/numbers.util"; | ||
/** ZOD */ | ||
import { z } from "zod"; | ||
|
||
export class CitiesBlankRoundsForm { | ||
public update<T extends keyof CityBlankRound.Form>( | ||
state: CityBlankRound.Form, | ||
key: T, | ||
value: CityBlankRound.Form[T] | ||
): CityBlankRound.Form { | ||
return produce(state, (draft) => { | ||
draft[key] = value; | ||
}); | ||
} | ||
|
||
public validate( | ||
state: CityBlankRound.Form | ||
): [boolean, CityBlankRound.FormErrors] { | ||
const schema = z.object({ | ||
rank: z | ||
.string() | ||
.min(1) | ||
.refine((rank) => castStringNumberToNumber(rank) > 0), | ||
round: z | ||
.string() | ||
.min(1) | ||
.refine( | ||
(round) => | ||
castStringNumberToNumber(round) >= | ||
SPECIALTIES_BLANK_ROUND_MIN_ROUND && | ||
castStringNumberToNumber(round) <= SPECIALTIES_BLANK_ROUND_MAX_ROUND | ||
), | ||
specialty: z.enum( | ||
Array.from(new Set(SPECIALTIES.values())) as [string, ...string[]] | ||
), | ||
}); | ||
|
||
const res = schema.safeParse(state); | ||
|
||
if (!res.success) { | ||
const errors = res.error.flatten().fieldErrors; | ||
|
||
return [ | ||
false, | ||
{ | ||
rank: errors.rank ? "INVALID_RANK" : null, | ||
round: errors.round ? "INVALID_ROUND" : null, | ||
specialty: errors.specialty ? "INVALID_SPECIALTY" : null, | ||
}, | ||
]; | ||
} | ||
|
||
return [ | ||
true, | ||
{ | ||
rank: null, | ||
round: null, | ||
specialty: null, | ||
}, | ||
]; | ||
} | ||
} |
122 changes: 122 additions & 0 deletions
122
src/modules/cities/core/forms/specs/cities-blank-rounds.form.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
/** FORMS */ | ||
import { CitiesBlankRoundsForm } from "../cities-blank-rounds.form"; | ||
/** MODELS */ | ||
import type { CityBlankRound } from "@/modules/cities/core/domain/models"; | ||
|
||
const emptyInitialState: CityBlankRound.Form = { | ||
rank: "", | ||
round: "", | ||
specialty: "", | ||
}; | ||
const completedState: CityBlankRound.Form = { | ||
rank: "1 289", | ||
round: "1", | ||
specialty: "CMF", | ||
}; | ||
|
||
describe("Cities rank form", () => { | ||
const form = new CitiesBlankRoundsForm(); | ||
|
||
it.each([ | ||
{ | ||
key: "round" as keyof CityBlankRound.Form, | ||
value: "1", | ||
}, | ||
{ | ||
key: "rank" as keyof CityBlankRound.Form, | ||
value: "1 289", | ||
}, | ||
{ | ||
key: "rank" as keyof CityBlankRound.Form, | ||
value: "47", | ||
}, | ||
{ | ||
key: "specialty" as keyof CityBlankRound.Form, | ||
value: "CMF", | ||
}, | ||
{ | ||
key: "specialty" as keyof CityBlankRound.Form, | ||
value: "", | ||
}, | ||
])("should change the form when $key is $value", ({ key, value }) => { | ||
const state = form.update(emptyInitialState, key, value); | ||
|
||
expect(state[key]).toBe(value); | ||
}); | ||
|
||
it.each([ | ||
{ | ||
key: "rank" as keyof CityBlankRound.Form, | ||
value: "", | ||
context: "is empty", | ||
expected: "INVALID_RANK", | ||
}, | ||
{ | ||
key: "rank" as keyof CityBlankRound.Form, | ||
value: "not a number", | ||
context: "is not a number", | ||
expected: "INVALID_RANK", | ||
}, | ||
{ | ||
key: "round" as keyof CityBlankRound.Form, | ||
value: "99", | ||
context: "is not in the range", | ||
expected: "INVALID_ROUND", | ||
}, | ||
{ | ||
key: "round" as keyof CityBlankRound.Form, | ||
value: "0", | ||
context: "is not in the range", | ||
expected: "INVALID_ROUND", | ||
}, | ||
{ | ||
key: "specialty" as keyof CityBlankRound.Form, | ||
value: "", | ||
context: "is empty", | ||
expected: "INVALID_SPECIALTY", | ||
}, | ||
{ | ||
key: "specialty" as keyof CityBlankRound.Form, | ||
value: "CMFIJ", | ||
context: "does not exist", | ||
expected: "INVALID_SPECIALTY", | ||
}, | ||
])( | ||
"should not be submittable when $key $context", | ||
({ key, value, context, expected }) => { | ||
const [isValid, errors] = form.validate({ | ||
...completedState, | ||
[key]: value, | ||
}); | ||
|
||
expect(isValid).toBeFalsy(); | ||
expect(errors[key]).toBe(expected); | ||
} | ||
); | ||
|
||
it.each([ | ||
{ | ||
rank: "100", | ||
round: "1", | ||
specialty: "CMF", | ||
}, | ||
{ | ||
rank: "8 384", | ||
round: "1", | ||
specialty: "CMF", | ||
}, | ||
])("should be valid", ({ rank, round, specialty }) => { | ||
const [isValid, errors] = form.validate({ | ||
rank, | ||
round, | ||
specialty, | ||
}); | ||
|
||
expect(isValid).toBeTruthy(); | ||
expect(errors).toEqual({ | ||
rank: null, | ||
round: null, | ||
specialty: null, | ||
}); | ||
}); | ||
}); |
4 changes: 2 additions & 2 deletions
4
src/modules/cities/core/forms/specs/cities-simulation.form.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.