mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-08-27 13:45:27 -05:00
Label underground bracket in the FactCard
This commit is contained in:
@@ -797,17 +797,26 @@ describe("tournamentNameParts", () => {
|
||||
});
|
||||
|
||||
describe("bracketProgressionLabel", () => {
|
||||
const bracket = (
|
||||
bracket: Partial<ParsedBracket> & Pick<ParsedBracket, "type">,
|
||||
): ParsedBracket => ({
|
||||
name: bracket.type,
|
||||
requiresCheckIn: false,
|
||||
settings: {},
|
||||
...bracket,
|
||||
});
|
||||
|
||||
it("returns the short code for a single stage", () => {
|
||||
expect(bracketProgressionLabel([{ type: "single_elimination" }])).toBe(
|
||||
"SE",
|
||||
);
|
||||
expect(
|
||||
bracketProgressionLabel([bracket({ type: "single_elimination" })]),
|
||||
).toBe("SE");
|
||||
});
|
||||
|
||||
it("joins stages with an arrow", () => {
|
||||
expect(
|
||||
bracketProgressionLabel([
|
||||
{ type: "round_robin" },
|
||||
{ type: "single_elimination" },
|
||||
bracket({ type: "round_robin" }),
|
||||
bracket({ type: "single_elimination" }),
|
||||
]),
|
||||
).toBe("RR → SE");
|
||||
});
|
||||
@@ -815,13 +824,45 @@ describe("bracketProgressionLabel", () => {
|
||||
it("collapses consecutive duplicate stages", () => {
|
||||
expect(
|
||||
bracketProgressionLabel([
|
||||
{ type: "single_elimination" },
|
||||
{ type: "single_elimination" },
|
||||
{ type: "double_elimination" },
|
||||
bracket({ type: "single_elimination" }),
|
||||
bracket({ type: "single_elimination" }),
|
||||
bracket({ type: "double_elimination" }),
|
||||
]),
|
||||
).toBe("SE → DE");
|
||||
});
|
||||
|
||||
it("appends an underground bracket of a new type with a plus and (UG) tag", () => {
|
||||
expect(
|
||||
bracketProgressionLabel([
|
||||
bracket({ type: "double_elimination" }),
|
||||
bracket({
|
||||
type: "single_elimination",
|
||||
sources: [{ bracketIdx: 0, placements: [-1] }],
|
||||
}),
|
||||
]),
|
||||
).toBe("DE + SE (UG)");
|
||||
});
|
||||
|
||||
it("collapses repeated underground brackets of an already-shown type", () => {
|
||||
expect(
|
||||
bracketProgressionLabel([
|
||||
bracket({ type: "round_robin" }),
|
||||
bracket({
|
||||
type: "single_elimination",
|
||||
sources: [{ bracketIdx: 0, placements: [1] }],
|
||||
}),
|
||||
bracket({
|
||||
type: "single_elimination",
|
||||
sources: [{ bracketIdx: 0, placements: [2] }],
|
||||
}),
|
||||
bracket({
|
||||
type: "single_elimination",
|
||||
sources: [{ bracketIdx: 0, placements: [3] }],
|
||||
}),
|
||||
]),
|
||||
).toBe("RR → SE (UG)");
|
||||
});
|
||||
|
||||
it("returns empty string for empty progression", () => {
|
||||
expect(bracketProgressionLabel([])).toBe("");
|
||||
});
|
||||
|
||||
@@ -500,30 +500,51 @@ const STAGE_TYPE_TO_SHORT_CODE: Record<
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds a compact arrow-separated label describing the bracket progression of a tournament,
|
||||
* Builds a compact label describing the bracket progression of a tournament,
|
||||
* derived from `settings.bracketProgression`.
|
||||
*
|
||||
* Each stage type is rendered as a short code (`RR`, `SE`, `DE`, `SW`) and consecutive duplicates
|
||||
* are collapsed so e.g. two single-elimination stages still render as a single `SE`.
|
||||
* Each stage type is rendered as a short code (`RR`, `SE`, `DE`, `SW`). Main progression stages are
|
||||
* arrow-separated with consecutive duplicates collapsed (e.g. two single-elimination stages still
|
||||
* render as a single `SE`).
|
||||
*
|
||||
* Underground brackets are not part of the main progression — they give early losers another chance
|
||||
* to play. When present the whole label is tagged `(UG)` once. An underground bracket type that does
|
||||
* not already appear in the main progression is also surfaced with `+ {code}`, while repeated
|
||||
* underground brackets of an already-shown type are collapsed away to keep the label compact.
|
||||
*
|
||||
* @example
|
||||
* // [{type: "round_robin"}, {type: "single_elimination"}]
|
||||
* bracketProgressionLabel(progression) // "RR → SE"
|
||||
* // [{type: "round_robin"}, {type: "single_elimination"}, ...underground SE brackets]
|
||||
* bracketProgressionLabel(progression) // "RR → SE (UG)"
|
||||
* // [{type: "double_elimination"}, {type: "single_elimination", sources: [...]}]
|
||||
* bracketProgressionLabel(progression) // "DE + SE (UG)"
|
||||
*/
|
||||
export function bracketProgressionLabel(
|
||||
progression: Pick<ParsedBracket, "type">[],
|
||||
): string {
|
||||
export function bracketProgressionLabel(progression: ParsedBracket[]): string {
|
||||
if (progression.length === 0) return "";
|
||||
|
||||
const codes: string[] = [];
|
||||
for (const bracket of progression) {
|
||||
const code = STAGE_TYPE_TO_SHORT_CODE[bracket.type];
|
||||
const mainCodes: string[] = [];
|
||||
const undergroundCodes: string[] = [];
|
||||
for (let i = 0; i < progression.length; i++) {
|
||||
const code = STAGE_TYPE_TO_SHORT_CODE[progression[i].type];
|
||||
const codes = Progression.isUnderground(i, progression)
|
||||
? undergroundCodes
|
||||
: mainCodes;
|
||||
if (codes.at(-1) !== code) {
|
||||
codes.push(code);
|
||||
}
|
||||
}
|
||||
|
||||
return codes.join(" → ");
|
||||
const mainLabel = mainCodes.join(" → ");
|
||||
|
||||
if (undergroundCodes.length === 0) return mainLabel;
|
||||
|
||||
const extraCodes = undergroundCodes.filter(
|
||||
(code) => !mainCodes.includes(code),
|
||||
);
|
||||
|
||||
const label =
|
||||
extraCodes.length > 0 ? [mainLabel, ...extraCodes].join(" + ") : mainLabel;
|
||||
|
||||
return `${label} (UG)`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user