Stop submit bubbling out of every Dialog

This commit is contained in:
Kalle
2026-10-02 20:37:43 +03:00
parent 803157180c
commit d0d1da6e7b
6 changed files with 61 additions and 35 deletions

View File

@@ -91,6 +91,8 @@ function DialogElement({
tabIndex={-1}
closedby="closerequest"
onClose={onClose}
// a portal doesn't stop React events from bubbling to an outer form
onSubmit={(e) => e.stopPropagation()}
{...backdropPressHandlers}
>
{children}

View File

@@ -381,21 +381,16 @@ function ImportTeamSection({
heading={t("forms:regImportTeam")}
onClose={() => setIsOpen(false)}
>
{/* The modal is portaled out of the registration <form> in the DOM,
but its submit event still bubbles through the React tree to the
outer form. Stop it here so importing doesn't submit registration. */}
<div onSubmit={(e) => e.stopPropagation()}>
<SendouForm
schema={importTeamFormSchema}
onApply={handleApply}
submitButtonText={t("forms:regImportTeam")}
>
<ImportTeamFields
currentTournamentId={currentTournamentId}
teamsRef={teamsRef}
/>
</SendouForm>
</div>
<SendouForm
schema={importTeamFormSchema}
onApply={handleApply}
submitButtonText={t("forms:regImportTeam")}
>
<ImportTeamFields
currentTournamentId={currentTournamentId}
teamsRef={teamsRef}
/>
</SendouForm>
</SendouDialog>
) : null}
</div>

View File

@@ -0,0 +1,5 @@
---
navItem: u
type: bug
---
Fix setting a friend code from the edit profile page

View File

@@ -37,6 +37,17 @@ export class UserEditProfilePage {
await search.press("Enter");
}
friendCodeButton(friendCode: string) {
return this.page.getByRole("button", { name: `SW-${friendCode}` });
}
async setFriendCode(friendCode: string) {
await this.page.getByRole("button", { name: "Set friend code" }).click();
const dialog = this.page.getByRole("dialog", { name: "Friend code" });
await dialog.getByLabel("Friend code").fill(friendCode);
await submit(this.page, dialog.getByTestId("submit-button"));
}
async deleteWeapon(name: string | RegExp) {
await this.page
.getByRole("button", { name })

View File

@@ -1,6 +1,9 @@
import { NZAP_TEST_ID } from "~/db/seed/constants";
import { ADMIN_ID } from "~/features/admin/admin-constants";
import { MATCHES_COUNT_NEEDED_FOR_LEADERBOARD } from "~/features/leaderboards/leaderboards-constants";
import * as Seasons from "~/features/mmr/core/Seasons";
import { tournamentStreamsPage, twitchUrl } from "~/utils/urls";
import type { Factories } from "./helpers/factories";
import {
expect,
expectNoErrorPage,
@@ -57,16 +60,7 @@ test.describe("Sidebar streams", () => {
page,
factories,
}) => {
const [topPlayer] = await createNamedUsers(factories, ["TopPlayer"], {
twitch: XRANK_TWITCH,
});
await factories.XRankPlacementFactory.create(
{ playerUserId: topPlayer.id, power: TOP_PLAYER_XP },
{ refreshPeakXp: true },
);
await factories.LiveStreamFactory.replaceAll([
{ userId: topPlayer.id, twitch: XRANK_TWITCH },
]);
await createLiveXRankStreamer(factories);
await impersonate(page, NZAP_TEST_ID);
await new FrontPage(page).goto();
@@ -85,16 +79,7 @@ test.describe("Sidebar streams", () => {
page,
factories,
}) => {
const [topPlayer] = await createNamedUsers(factories, ["TopPlayer"], {
twitch: XRANK_TWITCH,
});
await factories.XRankPlacementFactory.create(
{ playerUserId: topPlayer.id, power: TOP_PLAYER_XP },
{ refreshPeakXp: true },
);
await factories.LiveStreamFactory.replaceAll([
{ userId: topPlayer.id, twitch: XRANK_TWITCH },
]);
await createLiveXRankStreamer(factories);
await factories.TournamentFactory.create(
{
@@ -210,3 +195,20 @@ test.describe("Sidebar streams", () => {
await expectNoErrorPage(page);
});
});
async function createLiveXRankStreamer(factories: Factories) {
const [topPlayer] = await createNamedUsers(factories, ["TopPlayer"], {
twitch: XRANK_TWITCH,
});
await factories.XRankPlacementFactory.create(
{ playerUserId: topPlayer.id, power: TOP_PLAYER_XP },
{ refreshPeakXp: true },
);
await factories.SkillFactory.create(
{ userId: topPlayer.id, season: Seasons.currentOrPrevious()!.nth },
{ matchesCount: MATCHES_COUNT_NEEDED_FOR_LEADERBOARD },
);
await factories.LiveStreamFactory.replaceAll([
{ userId: topPlayer.id, twitch: XRANK_TWITCH },
]);
}

View File

@@ -11,6 +11,7 @@ import type { MainWeaponId, StageId } from "~/modules/in-game-lists/types";
import type { Factories } from "./helpers/factories";
import { expect, impersonate, isNotVisible, test } from "./helpers/playwright";
import { SettingsPage } from "./pages/settings/settings-page";
import { UserEditProfilePage } from "./pages/user/user-edit-profile-page";
import { UserEditWidgetsPage } from "./pages/user/user-edit-widgets-page";
import { UserPage } from "./pages/user/user-page";
import { UserSeasonsPage } from "./pages/user/user-seasons-page";
@@ -132,6 +133,16 @@ test.describe("User page", () => {
await expect(userPage.text("Lean#1234")).toBeVisible();
});
test("sets friend code from the edit profile page", async ({ page }) => {
await impersonate(page, NZAP_TEST_ID);
const editProfile = new UserEditProfilePage(page);
await editProfile.goto(NZAP_TEST_DISCORD_ID);
await editProfile.setFriendCode("0123-4567-8901");
await expect(editProfile.friendCodeButton("0123-4567-8901")).toBeVisible();
});
test("customizes theme colors and resets them", async ({
page,
factories,