-
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 #11 from RaphaelEscrig/v1.1.0
Add simulations pages
- Loading branch information
Showing
41 changed files
with
2,383 additions
and
10 deletions.
There are no files selected for viewing
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 CitiesSimulationsPage from "@/modules/cities/react/pages/simulations/cities-simulations.page"; | ||
/** UTILS */ | ||
import { isValidSpecialty } from "@/modules/shared/utils/specialty.util"; | ||
|
||
type Props = { | ||
readonly searchParams?: { | ||
readonly stage?: string; | ||
readonly rank?: string; | ||
readonly specialty?: string; | ||
}; | ||
}; | ||
|
||
const NextCitiesSimulationsPage = ({ searchParams }: Props) => { | ||
const stage = searchParams?.stage ? parseInt(searchParams.stage) : undefined; | ||
const rank = searchParams?.rank ? parseInt(searchParams.rank) : undefined; | ||
const specialty = | ||
searchParams?.specialty && isValidSpecialty(searchParams.specialty) | ||
? (searchParams.specialty as SpecialtyCode) | ||
: undefined; | ||
|
||
return ( | ||
<CitiesSimulationsPage rank={rank} specialty={specialty} stage={stage} /> | ||
); | ||
}; | ||
|
||
export default NextCitiesSimulationsPage; |
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 SpecialtiesSimulationsPage from "@/modules/specialties/react/pages/simulations/specialties-simulations.page"; | ||
|
||
type Props = { | ||
readonly searchParams?: { | ||
readonly stage?: string; | ||
readonly rank?: string; | ||
}; | ||
}; | ||
|
||
export default async function NextSpecialtiesPage({ searchParams }: Props) { | ||
const stage = searchParams?.stage ? parseInt(searchParams.stage) : 1; | ||
const rank = searchParams?.rank ? parseInt(searchParams.rank) : undefined; | ||
|
||
return <SpecialtiesSimulationsPage rank={rank} stage={stage} />; | ||
} |
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
File renamed without changes.
23 changes: 23 additions & 0 deletions
23
src/modules/cities/core/domain/models/city-simulation.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 stage: string; | ||
readonly rank: string; | ||
readonly specialty: string; | ||
}; | ||
|
||
export type FormErrors = Record< | ||
keyof Pick<Form, "stage" | "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,2 +1,3 @@ | ||
/** MODELS */ | ||
export * as CityRank from "@/modules/cities/core/domain/models/city-rank"; | ||
export * as CityRank from "@/modules/cities/core/domain/models/city-rank.model"; | ||
export * as CitySimulation from "@/modules/cities/core/domain/models/city-simulation.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_SIMULATION_MAX_STAGE, | ||
SPECIALTIES_SIMULATION_MIN_STAGE, | ||
} from "@/modules/specialties/core/domain/constants"; | ||
import { SPECIALTIES } from "@/modules/shared/domain/constants"; | ||
/** IMMER */ | ||
import { produce } from "immer"; | ||
/** MODELS */ | ||
import type { CitySimulation } from "@/modules/cities/core/domain/models"; | ||
/** UTILS */ | ||
import { castStringNumberToNumber } from "@/modules/shared/utils/numbers.util"; | ||
/** ZOD */ | ||
import { z } from "zod"; | ||
|
||
export class CitiesSimulationForm { | ||
public update<T extends keyof CitySimulation.Form>( | ||
state: CitySimulation.Form, | ||
key: T, | ||
value: CitySimulation.Form[T] | ||
): CitySimulation.Form { | ||
return produce(state, (draft) => { | ||
draft[key] = value; | ||
}); | ||
} | ||
|
||
public validate( | ||
state: CitySimulation.Form | ||
): [boolean, CitySimulation.FormErrors] { | ||
const schema = z.object({ | ||
rank: z | ||
.string() | ||
.min(1) | ||
.refine((rank) => castStringNumberToNumber(rank) > 0), | ||
stage: z | ||
.string() | ||
.min(1) | ||
.refine( | ||
(stage) => | ||
castStringNumberToNumber(stage) >= | ||
SPECIALTIES_SIMULATION_MIN_STAGE && | ||
castStringNumberToNumber(stage) <= SPECIALTIES_SIMULATION_MAX_STAGE | ||
), | ||
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, | ||
stage: errors.stage ? "INVALID_STAGE" : null, | ||
specialty: errors.specialty ? "INVALID_SPECIALTY" : null, | ||
}, | ||
]; | ||
} | ||
|
||
return [ | ||
true, | ||
{ | ||
rank: null, | ||
stage: null, | ||
specialty: null, | ||
}, | ||
]; | ||
} | ||
} |
122 changes: 122 additions & 0 deletions
122
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
// /** FORMS */ | ||
import { CitiesSimulationForm } from "@/modules/cities/core/forms/cities-simulation.form"; | ||
// /** MODELS */ | ||
import type { CitySimulation } from "@/modules/cities/core/domain/models"; | ||
|
||
const emptyInitialState: CitySimulation.Form = { | ||
rank: "", | ||
stage: "", | ||
specialty: "", | ||
}; | ||
const completedState: CitySimulation.Form = { | ||
rank: "1 289", | ||
stage: "1", | ||
specialty: "CMF", | ||
}; | ||
|
||
describe("Cities rank form", () => { | ||
const form = new CitiesSimulationForm(); | ||
|
||
it.each([ | ||
{ | ||
key: "stage" as keyof CitySimulation.Form, | ||
value: "1", | ||
}, | ||
{ | ||
key: "rank" as keyof CitySimulation.Form, | ||
value: "1 289", | ||
}, | ||
{ | ||
key: "rank" as keyof CitySimulation.Form, | ||
value: "47", | ||
}, | ||
{ | ||
key: "specialty" as keyof CitySimulation.Form, | ||
value: "CMF", | ||
}, | ||
{ | ||
key: "specialty" as keyof CitySimulation.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 CitySimulation.Form, | ||
value: "", | ||
context: "is empty", | ||
expected: "INVALID_RANK", | ||
}, | ||
{ | ||
key: "rank" as keyof CitySimulation.Form, | ||
value: "not a number", | ||
context: "is not a number", | ||
expected: "INVALID_RANK", | ||
}, | ||
{ | ||
key: "stage" as keyof CitySimulation.Form, | ||
value: "99", | ||
context: "is not in the range", | ||
expected: "INVALID_STAGE", | ||
}, | ||
{ | ||
key: "stage" as keyof CitySimulation.Form, | ||
value: "0", | ||
context: "is not in the range", | ||
expected: "INVALID_STAGE", | ||
}, | ||
{ | ||
key: "specialty" as keyof CitySimulation.Form, | ||
value: "", | ||
context: "is empty", | ||
expected: "INVALID_SPECIALTY", | ||
}, | ||
{ | ||
key: "specialty" as keyof CitySimulation.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", | ||
stage: "1", | ||
specialty: "CMF", | ||
}, | ||
{ | ||
rank: "8 384", | ||
stage: "1", | ||
specialty: "CMF", | ||
}, | ||
])("should be valid", ({ rank, stage, specialty }) => { | ||
const [isValid, errors] = form.validate({ | ||
rank, | ||
stage, | ||
specialty, | ||
}); | ||
|
||
expect(isValid).toBeTruthy(); | ||
expect(errors).toEqual({ | ||
rank: null, | ||
stage: null, | ||
specialty: null, | ||
}); | ||
}); | ||
}); |
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.