diff --git a/app/features/tier-list-maker/tier-list-maker-utils.test.ts b/app/features/tier-list-maker/tier-list-maker-utils.test.ts index b01bff6df..2f40e2b85 100644 --- a/app/features/tier-list-maker/tier-list-maker-utils.test.ts +++ b/app/features/tier-list-maker/tier-list-maker-utils.test.ts @@ -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); + }); +}); diff --git a/app/features/tier-list-maker/tier-list-maker-utils.ts b/app/features/tier-list-maker/tier-list-maker-utils.ts index e832c1d86..294ba8cf6 100644 --- a/app/features/tier-list-maker/tier-list-maker-utils.ts +++ b/app/features/tier-list-maker/tier-list-maker-utils.ts @@ -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, diff --git a/app/features/user-page/core/widgets/widget-form-schemas.ts b/app/features/user-page/core/widgets/widget-form-schemas.ts index e99acc23b..f0633996e 100644 --- a/app/features/user-page/core/widgets/widget-form-schemas.ts +++ b/app/features/user-page/core/widgets/widget-form-schemas.ts @@ -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", + }, }), }); diff --git a/app/features/user-page/user-page-constants.ts b/app/features/user-page/user-page-constants.ts index d7dbec5fb..b47b78cb1 100644 --- a/app/features/user-page/user-page-constants.ts +++ b/app/features/user-page/user-page-constants.ts @@ -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, }; diff --git a/changelog/2026-09-14-tier-list-widget-url-truncated.md b/changelog/2026-09-14-tier-list-widget-url-truncated.md new file mode 100644 index 000000000..58410ef74 --- /dev/null +++ b/changelog/2026-09-14-tier-list-widget-url-truncated.md @@ -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 diff --git a/locales/da/forms.json b/locales/da/forms.json index 214405c2f..deb492524 100644 --- a/locales/da/forms.json +++ b/locales/da/forms.json @@ -61,6 +61,7 @@ "errors.mustBeWholeNumber": "", "errors.matchNotFound": "", "errors.invalidUrl": "", + "errors.tierListUrlIncomplete": "", "errors.imageTooLarge": "", "errors.invalidTime": "", "errors.timeRangeIncomplete": "", diff --git a/locales/de/forms.json b/locales/de/forms.json index 3b40946e6..1d7f36064 100644 --- a/locales/de/forms.json +++ b/locales/de/forms.json @@ -61,6 +61,7 @@ "errors.mustBeWholeNumber": "", "errors.matchNotFound": "", "errors.invalidUrl": "", + "errors.tierListUrlIncomplete": "", "errors.imageTooLarge": "", "errors.invalidTime": "", "errors.timeRangeIncomplete": "", diff --git a/locales/en/forms.json b/locales/en/forms.json index 50f96c195..9bf38ed4e 100644 --- a/locales/en/forms.json +++ b/locales/en/forms.json @@ -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", diff --git a/locales/es-ES/forms.json b/locales/es-ES/forms.json index b1c07a268..614a86e66 100644 --- a/locales/es-ES/forms.json +++ b/locales/es-ES/forms.json @@ -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": "", diff --git a/locales/es-US/forms.json b/locales/es-US/forms.json index c19e51a3a..7fa0f494e 100644 --- a/locales/es-US/forms.json +++ b/locales/es-US/forms.json @@ -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": "", diff --git a/locales/fr-CA/forms.json b/locales/fr-CA/forms.json index 91b7889a8..2d453f0d8 100644 --- a/locales/fr-CA/forms.json +++ b/locales/fr-CA/forms.json @@ -61,6 +61,7 @@ "errors.mustBeWholeNumber": "", "errors.matchNotFound": "", "errors.invalidUrl": "", + "errors.tierListUrlIncomplete": "", "errors.imageTooLarge": "", "errors.invalidTime": "", "errors.timeRangeIncomplete": "", diff --git a/locales/fr-EU/forms.json b/locales/fr-EU/forms.json index 58a6da2e0..0aad25ab0 100644 --- a/locales/fr-EU/forms.json +++ b/locales/fr-EU/forms.json @@ -61,6 +61,7 @@ "errors.mustBeWholeNumber": "", "errors.matchNotFound": "", "errors.invalidUrl": "", + "errors.tierListUrlIncomplete": "", "errors.imageTooLarge": "", "errors.invalidTime": "", "errors.timeRangeIncomplete": "", diff --git a/locales/he/forms.json b/locales/he/forms.json index 41c358292..98da2c0fc 100644 --- a/locales/he/forms.json +++ b/locales/he/forms.json @@ -61,6 +61,7 @@ "errors.mustBeWholeNumber": "", "errors.matchNotFound": "", "errors.invalidUrl": "", + "errors.tierListUrlIncomplete": "", "errors.imageTooLarge": "", "errors.invalidTime": "", "errors.timeRangeIncomplete": "", diff --git a/locales/it/forms.json b/locales/it/forms.json index f869b6af4..aa95718cc 100644 --- a/locales/it/forms.json +++ b/locales/it/forms.json @@ -61,6 +61,7 @@ "errors.mustBeWholeNumber": "", "errors.matchNotFound": "", "errors.invalidUrl": "", + "errors.tierListUrlIncomplete": "", "errors.imageTooLarge": "", "errors.invalidTime": "", "errors.timeRangeIncomplete": "", diff --git a/locales/ja/forms.json b/locales/ja/forms.json index c61edc6ec..ffebf8a4f 100644 --- a/locales/ja/forms.json +++ b/locales/ja/forms.json @@ -61,6 +61,7 @@ "errors.mustBeWholeNumber": "", "errors.matchNotFound": "", "errors.invalidUrl": "", + "errors.tierListUrlIncomplete": "", "errors.imageTooLarge": "", "errors.invalidTime": "", "errors.timeRangeIncomplete": "", diff --git a/locales/ko/forms.json b/locales/ko/forms.json index db94c13cb..e70d94376 100644 --- a/locales/ko/forms.json +++ b/locales/ko/forms.json @@ -61,6 +61,7 @@ "errors.mustBeWholeNumber": "", "errors.matchNotFound": "", "errors.invalidUrl": "", + "errors.tierListUrlIncomplete": "", "errors.imageTooLarge": "", "errors.invalidTime": "", "errors.timeRangeIncomplete": "", diff --git a/locales/nl/forms.json b/locales/nl/forms.json index 76a06837b..3d61e19e8 100644 --- a/locales/nl/forms.json +++ b/locales/nl/forms.json @@ -61,6 +61,7 @@ "errors.mustBeWholeNumber": "", "errors.matchNotFound": "", "errors.invalidUrl": "", + "errors.tierListUrlIncomplete": "", "errors.imageTooLarge": "", "errors.invalidTime": "", "errors.timeRangeIncomplete": "", diff --git a/locales/pl/forms.json b/locales/pl/forms.json index 578defb89..aeb87d2e2 100644 --- a/locales/pl/forms.json +++ b/locales/pl/forms.json @@ -61,6 +61,7 @@ "errors.mustBeWholeNumber": "", "errors.matchNotFound": "", "errors.invalidUrl": "", + "errors.tierListUrlIncomplete": "", "errors.imageTooLarge": "", "errors.invalidTime": "", "errors.timeRangeIncomplete": "", diff --git a/locales/pt-BR/forms.json b/locales/pt-BR/forms.json index 8e8779681..91d6fca19 100644 --- a/locales/pt-BR/forms.json +++ b/locales/pt-BR/forms.json @@ -61,6 +61,7 @@ "errors.mustBeWholeNumber": "", "errors.matchNotFound": "", "errors.invalidUrl": "", + "errors.tierListUrlIncomplete": "", "errors.imageTooLarge": "", "errors.invalidTime": "", "errors.timeRangeIncomplete": "", diff --git a/locales/ru/forms.json b/locales/ru/forms.json index da3f4c296..fe743f692 100644 --- a/locales/ru/forms.json +++ b/locales/ru/forms.json @@ -61,6 +61,7 @@ "errors.mustBeWholeNumber": "", "errors.matchNotFound": "", "errors.invalidUrl": "", + "errors.tierListUrlIncomplete": "", "errors.imageTooLarge": "", "errors.invalidTime": "", "errors.timeRangeIncomplete": "", diff --git a/locales/zh/forms.json b/locales/zh/forms.json index 7216b0bc2..2c3107697 100644 --- a/locales/zh/forms.json +++ b/locales/zh/forms.json @@ -61,6 +61,7 @@ "errors.mustBeWholeNumber": "", "errors.matchNotFound": "", "errors.invalidUrl": "请输入有效的 URL 地址", + "errors.tierListUrlIncomplete": "", "errors.imageTooLarge": "", "errors.invalidTime": "", "errors.timeRangeIncomplete": "",