Fix adding long tier lists as user page widget

This commit is contained in:
Kalle
2026-09-14 17:07:44 +03:00
parent 6595b25566
commit 3b78e82830
21 changed files with 77 additions and 1 deletions

View File

@@ -7,6 +7,7 @@ import {
getNextNthForItem,
tierListItemId,
tierListMakerPathWithState,
tierListSearchParamsHaveItems,
} from "./tier-list-maker-utils";
function makeState(
@@ -153,3 +154,42 @@ describe("tierListItemId", () => {
);
});
});
describe("tierListSearchParamsHaveItems", () => {
function searchParamsFor(state: TierListState) {
return tierListMakerPathWithState({
state,
title: "Weapons ranked",
showTierHeaders: true,
}).split("?")[1];
}
test("accepts a tier list that has items", () => {
expect(
tierListSearchParamsHaveItems(
searchParamsFor(makeState({ "tier-a": [splattershot] })),
),
).toBe(true);
});
test("rejects a state param cut short, as a too long URL pasted into a maxLength input is", () => {
const params = new URLSearchParams(
searchParamsFor(makeState({ "tier-a": [splattershot] })),
);
const state = params.get(TIER_LIST_SEARCH_PARAM_NAMES.STATE)!;
params.set(
TIER_LIST_SEARCH_PARAM_NAMES.STATE,
state.slice(0, Math.floor(state.length / 2)),
);
expect(tierListSearchParamsHaveItems(params.toString())).toBe(false);
});
test.each([
{ why: "no state param at all", searchParams: "title=Weapons+ranked" },
{ why: "state param is not decodable", searchParams: "state=notATierList" },
{ why: "every tier is empty", searchParams: searchParamsFor(makeState()) },
])("rejects when $why", ({ searchParams }) => {
expect(tierListSearchParamsHaveItems(searchParams)).toBe(false);
});
});

View File

@@ -27,6 +27,14 @@ export function tierListMakerPathWithState({
});
}
export function tierListSearchParamsHaveItems(searchParams: string) {
const { state } = tierListMakerSearchParams.parse(
new URLSearchParams(searchParams),
);
return Array.from(state.tierItems.values()).some((items) => items.length > 0);
}
/** State with the item appended to the tier; unchanged if the tier does not exist. */
export function addItemToTier(
state: TierListState,

View File

@@ -3,6 +3,7 @@ import * as v from "valibot";
import { ART_SOURCES } from "~/features/art/art-types";
import { BADGE } from "~/features/badges/badges-constants";
import { TIMEZONES } from "~/features/lfg/lfg-constants";
import { tierListSearchParamsHaveItems } from "~/features/tier-list-maker/tier-list-maker-utils";
import {
array,
badges,
@@ -152,8 +153,13 @@ export const tierListSchema = v.object({
searchParams: textField({
label: "labels.tierListUrl",
leftAddon: "/tier-list-maker?",
maxLength: 500,
maxLength: USER.TIER_LIST_WIDGET_MAX_LENGTH,
transformValue: pastedTierListUrlToSearchParams,
validate: {
func: (value) =>
tierListSearchParamsHaveItems(pastedTierListUrlToSearchParams(value)),
message: "forms:errors.tierListUrlIncomplete",
},
}),
});

View File

@@ -17,6 +17,7 @@ export const USER = {
WEAPON_POOL_WIDGET_MAX: 7,
COUNTDOWN_TITLE_MAX_LENGTH: 50,
MARKDOWN_WIDGET_MAX_LENGTH: 2000,
TIER_LIST_WIDGET_MAX_LENGTH: 4000,
PEAK_XP_MIN: 1000,
PEAK_XP_MAX: 6000,
};

View File

@@ -0,0 +1,5 @@
---
navItem: [u, tier-list-maker]
type: bug
---
Fixed long tier list URLs getting cut off when added as a tier list widget, which made the widget open an empty tier list

View File

@@ -61,6 +61,7 @@
"errors.mustBeWholeNumber": "",
"errors.matchNotFound": "",
"errors.invalidUrl": "",
"errors.tierListUrlIncomplete": "",
"errors.imageTooLarge": "",
"errors.invalidTime": "",
"errors.timeRangeIncomplete": "",

View File

@@ -61,6 +61,7 @@
"errors.mustBeWholeNumber": "",
"errors.matchNotFound": "",
"errors.invalidUrl": "",
"errors.tierListUrlIncomplete": "",
"errors.imageTooLarge": "",
"errors.invalidTime": "",
"errors.timeRangeIncomplete": "",

View File

@@ -61,6 +61,7 @@
"errors.mustBeWholeNumber": "Must be a whole number (no decimals)",
"errors.matchNotFound": "No SendouQ match found with this ID",
"errors.invalidUrl": "Must be a valid URL",
"errors.tierListUrlIncomplete": "Tier list URL is incomplete or the tier list is empty",
"errors.imageTooLarge": "Image is too large. Try one with a smaller file size.",
"errors.invalidTime": "Must be a valid time, e.g. 18:30",
"errors.timeRangeIncomplete": "Both a start and an end time are needed",

View File

@@ -61,6 +61,7 @@
"errors.mustBeWholeNumber": "Debe ser un número entero (sin decimales)",
"errors.matchNotFound": "No se ha encontrado ninguna partida de SendouQ con esta ID",
"errors.invalidUrl": "Debe ser una URL válida",
"errors.tierListUrlIncomplete": "",
"errors.imageTooLarge": "La imagen es demasiado grande. Prueba con un archivo más pequeño.",
"errors.invalidTime": "",
"errors.timeRangeIncomplete": "",

View File

@@ -61,6 +61,7 @@
"errors.mustBeWholeNumber": "Debe ser un número entero (sin decimales)",
"errors.matchNotFound": "No se ha encontrado ninguna partida de SendouQ con esta ID",
"errors.invalidUrl": "Debe ser una URL válida",
"errors.tierListUrlIncomplete": "",
"errors.imageTooLarge": "La imagen es demasiado grande. Prueba con un archivo más pequeño.",
"errors.invalidTime": "",
"errors.timeRangeIncomplete": "",

View File

@@ -61,6 +61,7 @@
"errors.mustBeWholeNumber": "",
"errors.matchNotFound": "",
"errors.invalidUrl": "",
"errors.tierListUrlIncomplete": "",
"errors.imageTooLarge": "",
"errors.invalidTime": "",
"errors.timeRangeIncomplete": "",

View File

@@ -61,6 +61,7 @@
"errors.mustBeWholeNumber": "",
"errors.matchNotFound": "",
"errors.invalidUrl": "",
"errors.tierListUrlIncomplete": "",
"errors.imageTooLarge": "",
"errors.invalidTime": "",
"errors.timeRangeIncomplete": "",

View File

@@ -61,6 +61,7 @@
"errors.mustBeWholeNumber": "",
"errors.matchNotFound": "",
"errors.invalidUrl": "",
"errors.tierListUrlIncomplete": "",
"errors.imageTooLarge": "",
"errors.invalidTime": "",
"errors.timeRangeIncomplete": "",

View File

@@ -61,6 +61,7 @@
"errors.mustBeWholeNumber": "",
"errors.matchNotFound": "",
"errors.invalidUrl": "",
"errors.tierListUrlIncomplete": "",
"errors.imageTooLarge": "",
"errors.invalidTime": "",
"errors.timeRangeIncomplete": "",

View File

@@ -61,6 +61,7 @@
"errors.mustBeWholeNumber": "",
"errors.matchNotFound": "",
"errors.invalidUrl": "",
"errors.tierListUrlIncomplete": "",
"errors.imageTooLarge": "",
"errors.invalidTime": "",
"errors.timeRangeIncomplete": "",

View File

@@ -61,6 +61,7 @@
"errors.mustBeWholeNumber": "",
"errors.matchNotFound": "",
"errors.invalidUrl": "",
"errors.tierListUrlIncomplete": "",
"errors.imageTooLarge": "",
"errors.invalidTime": "",
"errors.timeRangeIncomplete": "",

View File

@@ -61,6 +61,7 @@
"errors.mustBeWholeNumber": "",
"errors.matchNotFound": "",
"errors.invalidUrl": "",
"errors.tierListUrlIncomplete": "",
"errors.imageTooLarge": "",
"errors.invalidTime": "",
"errors.timeRangeIncomplete": "",

View File

@@ -61,6 +61,7 @@
"errors.mustBeWholeNumber": "",
"errors.matchNotFound": "",
"errors.invalidUrl": "",
"errors.tierListUrlIncomplete": "",
"errors.imageTooLarge": "",
"errors.invalidTime": "",
"errors.timeRangeIncomplete": "",

View File

@@ -61,6 +61,7 @@
"errors.mustBeWholeNumber": "",
"errors.matchNotFound": "",
"errors.invalidUrl": "",
"errors.tierListUrlIncomplete": "",
"errors.imageTooLarge": "",
"errors.invalidTime": "",
"errors.timeRangeIncomplete": "",

View File

@@ -61,6 +61,7 @@
"errors.mustBeWholeNumber": "",
"errors.matchNotFound": "",
"errors.invalidUrl": "",
"errors.tierListUrlIncomplete": "",
"errors.imageTooLarge": "",
"errors.invalidTime": "",
"errors.timeRangeIncomplete": "",

View File

@@ -61,6 +61,7 @@
"errors.mustBeWholeNumber": "",
"errors.matchNotFound": "",
"errors.invalidUrl": "请输入有效的 URL 地址",
"errors.tierListUrlIncomplete": "",
"errors.imageTooLarge": "",
"errors.invalidTime": "",
"errors.timeRangeIncomplete": "",