Initial builds E2E tests

This commit is contained in:
Kalle
2023-04-23 13:26:18 +03:00
parent 1f6bda88b4
commit 0c32456e72
12 changed files with 181 additions and 24 deletions

View File

@@ -84,7 +84,10 @@ export function BuildCard({ build, owner, canEdit = false }: BuildProps) {
} = build;
return (
<div className={clsx("build", { build__private: build.private })}>
<div
className={clsx("build", { build__private: build.private })}
data-testid="build-card"
>
<div>
<div className="build__top-row">
{modes && modes.length > 0 && (
@@ -101,7 +104,9 @@ export function BuildCard({ build, owner, canEdit = false }: BuildProps) {
))}
</div>
)}
<h2 className="build__title">{title}</h2>
<h2 className="build__title" data-testid="build-title">
{title}
</h2>
</div>
<div className="build__date-author-row">
{owner && (
@@ -193,6 +198,7 @@ export function BuildCard({ build, owner, canEdit = false }: BuildProps) {
variant="minimal"
size="tiny"
to={`new?buildId=${id}&userId=${user!.id}`}
testId="edit-build"
>
<EditIcon className="build__icon" />
</LinkButton>

View File

@@ -221,6 +221,7 @@ export function UserCombobox({
);
}
// TODO: [object Object] flickers when server rendered with initialValue
export function WeaponCombobox({
id,
required,

View File

@@ -3,4 +3,8 @@ select
from
"Build"
where
"ownerId" = @userId;
"Build"."ownerId" = @userId
and (
"Build"."private" = 0
or "Build"."ownerId" = @loggedInUserId
)

View File

@@ -90,8 +90,15 @@ export const updateByReplacing = sql.transaction(
}
);
export function countByUserId(userId: Build["ownerId"]) {
return (countByUserIdStm.get({ userId })?.count ?? 0) as number;
export function countByUserId({
userId,
loggedInUserId,
}: {
userId: Build["ownerId"];
loggedInUserId?: UserWithPlusTier["id"];
}) {
return (countByUserIdStm.get({ userId, loggedInUserId })?.count ??
0) as number;
}
export interface BuildWeaponWithTop500Info {

7
app/db/seed/constants.ts Normal file
View File

@@ -0,0 +1,7 @@
export const ADMIN_TEST_AVATAR = "f34d6169979e60dfe63de0f96c8050f3";
export const NZAP_TEST_DISCORD_ID = "455039198672453645";
export const NZAP_TEST_AVATAR = "f809176af93132c3db5f0a5019e96339"; // https://cdn.discordapp.com/avatars/455039198672453645/f809176af93132c3db5f0a5019e96339.webp?size=160
export const NZAP_TEST_ID = 2;
export const AMOUNT_OF_CALENDAR_EVENTS = 200;

View File

@@ -30,14 +30,13 @@ import { mySlugify } from "~/utils/urls";
import { createVod } from "~/features/vods/queries/createVod.server";
import placements from "./placements.json";
const ADMIN_TEST_AVATAR = "f34d6169979e60dfe63de0f96c8050f3";
const NZAP_TEST_DISCORD_ID = "455039198672453645";
const NZAP_TEST_AVATAR = "f809176af93132c3db5f0a5019e96339"; // https://cdn.discordapp.com/avatars/455039198672453645/f809176af93132c3db5f0a5019e96339.webp?size=160
const NZAP_TEST_ID = 2;
const AMOUNT_OF_CALENDAR_EVENTS = 200;
import {
NZAP_TEST_DISCORD_ID,
ADMIN_TEST_AVATAR,
NZAP_TEST_AVATAR,
NZAP_TEST_ID,
AMOUNT_OF_CALENDAR_EVENTS,
} from "./constants";
const calendarEventWithToToolsSz = () => calendarEventWithToTools(true);
const calendarEventWithToToolsTeamsSz = () =>
@@ -875,7 +874,9 @@ function adminBuilds() {
function manySplattershotBuilds() {
// ensure 500 has at least one splattershot build for x placement test
const users = [
...userIdsInRandomOrder().filter((id) => id !== 500 && id !== ADMIN_ID),
...userIdsInRandomOrder().filter(
(id) => id !== 500 && id !== ADMIN_ID && id !== NZAP_TEST_ID
),
500,
];

View File

@@ -162,7 +162,7 @@ export const action: ActionFunction = async ({ request }) => {
};
// TODO: messing with these and "one mode selection" can cause problems when teams
// have already chosend maps for their pools
// have already chosen maps for their pools
const deserializedMaps = (() => {
if (!data.pool) return;

View File

@@ -16,6 +16,7 @@ import { userTopPlacements } from "~/features/top-search";
import { findVods } from "~/features/vods";
import { useTranslation } from "~/hooks/useTranslation";
import { useUser } from "~/modules/auth";
import { getUserId } from "~/modules/auth/user.server";
import { canAddCustomizedColorsToUserProfile } from "~/permissions";
import styles from "~/styles/u.css";
import { notFoundIfFalsy, type SendouRouteHandle } from "~/utils/remix";
@@ -69,7 +70,8 @@ export const userParamsSchema = z.object({ identifier: z.string() });
export type UserPageLoaderData = SerializeFrom<typeof loader>;
export const loader = ({ params }: LoaderArgs) => {
export const loader = async ({ params, request }: LoaderArgs) => {
const loggedInUser = await getUserId(request);
const { identifier } = userParamsSchema.parse(params);
const user = notFoundIfFalsy(db.users.findByIdentifier(identifier));
@@ -94,7 +96,10 @@ export const loader = ({ params }: LoaderArgs) => {
css: canAddCustomizedColorsToUserProfile(user) ? user.css : undefined,
badges: db.badges.findByOwnerId(user.id),
results: db.calendarEvents.findResultsByUserId(user.id),
buildsCount: db.builds.countByUserId(user.id),
buildsCount: db.builds.countByUserId({
userId: user.id,
loggedInUserId: loggedInUser?.id,
}),
vods: findVods({ userId: user.id }),
playerId,
topPlacements,
@@ -125,7 +130,11 @@ export default function UserPageLayout() {
</SubNavLink>
)}
{(isOwnPage || data.buildsCount > 0) && (
<SubNavLink to={userBuildsPage(data)} prefetch="intent">
<SubNavLink
to={userBuildsPage(data)}
prefetch="intent"
data-testid="builds-tab"
>
{t("pages.builds")} ({data.buildsCount})
</SubNavLink>
)}

View File

@@ -77,7 +77,11 @@ export default function UserBuildsPage() {
{isOwnPage && (
<div className="stack sm horizontal items-center justify-end">
{data.builds.length < BUILD.MAX_COUNT ? (
<LinkButton to={userNewBuildPage(parentPageData)} size="tiny">
<LinkButton
to={userNewBuildPage(parentPageData)}
size="tiny"
testId="new-build-button"
>
{t("addBuild")}
</LinkButton>
) : (

View File

@@ -228,6 +228,7 @@ function TitleInput() {
{t("forms.title")}
</Label>
<input
id="title"
name="title"
required
minLength={BUILD.TITLE_MIN_LENGTH}
@@ -285,7 +286,7 @@ function ModeCheckboxes() {
name={mode}
type="checkbox"
defaultChecked={modes.includes(mode)}
data-cy={`${mode}-checkbox`}
data-testid={`${mode}-checkbox`}
/>
</div>
))}
@@ -357,6 +358,7 @@ function WeaponsSelector() {
disabled={weapons.length === BUILD.MAX_WEAPONS_COUNT}
onClick={() => setWeapons((weapons) => [...weapons, 0])}
icon={<PlusIcon />}
testId="add-weapon-button"
/>
{weapons.length > 1 && (
<Button

View File

@@ -9,10 +9,27 @@ export async function selectWeapon({
name: string;
inputName?: string;
}) {
const weaponCombobox = page.getByTestId(`${inputName}-combobox-input`);
await weaponCombobox.clear();
await weaponCombobox.fill(name);
await weaponCombobox.press("Enter");
return selectComboboxValue({ page, value: name, inputName });
}
export async function selectComboboxValue({
page,
value,
inputName,
locator,
}: {
page: Page;
value: string;
inputName?: string;
locator?: Locator;
}) {
if (!locator && !inputName) {
throw new Error("Must provide either locator or inputName");
}
const combobox = locator ?? page.getByTestId(`${inputName!}-combobox-input`);
await combobox.clear();
await combobox.fill(value);
await combobox.press("Enter");
}
/** page.goto that waits for the page to be hydrated before proceeding */

99
e2e/builds.spec.ts Normal file
View File

@@ -0,0 +1,99 @@
import { expect, test } from "@playwright/test";
import { ADMIN_DISCORD_ID } from "~/constants";
import { NZAP_TEST_DISCORD_ID, NZAP_TEST_ID } from "~/db/seed/constants";
import {
impersonate,
navigate,
seed,
selectComboboxValue,
} from "~/utils/playwright";
import { userBuildsPage, userNewBuildPage } from "~/utils/urls";
test.describe("Builds", () => {
test("adds a build", async ({ page }) => {
await seed(page);
await impersonate(page, NZAP_TEST_ID);
await navigate({
page,
url: userNewBuildPage({ discordId: NZAP_TEST_DISCORD_ID }),
});
await selectComboboxValue({
locator: page.getByTestId("weapon-combobox-input").first(),
value: "Tenta Brella",
page,
});
await page.getByTestId("add-weapon-button").click();
await selectComboboxValue({
locator: page.getByTestId("weapon-combobox-input").nth(1),
value: "Splat Brella",
page,
});
await selectComboboxValue({
inputName: "HEAD",
value: "White Headband",
page,
});
await selectComboboxValue({
inputName: "CLOTHES",
value: "Basic Tee",
page,
});
await selectComboboxValue({
inputName: "SHOES",
value: "Blue Lo-Tops",
page,
});
for (let i = 0; i < 12; i++) {
await page.getByTestId("ISM-ability-button").click();
}
await page.getByLabel("Title").fill("Test Build");
await page.getByLabel("Description").fill("Test Description");
await page.getByTestId("SZ-checkbox").click();
await page.getByTestId("submit-button").click();
await expect(page.getByTestId("new-build-button")).toBeVisible();
await expect(page.getByAltText("Tenta Brella")).toBeVisible();
await expect(page.getByAltText("Splat Brella")).toBeVisible();
await expect(page.getByAltText("Tower Control")).toBeVisible();
await expect(page.getByAltText("Splat Zones")).not.toBeVisible();
await expect(page.getByTestId("build-title")).toContainText("Test Build");
});
test("makes build private", async ({ page }) => {
await seed(page);
await impersonate(page);
await navigate({
page,
url: userBuildsPage({ discordId: ADMIN_DISCORD_ID }),
});
await page.getByTestId("edit-build").first().click();
await page.getByLabel("Private").click();
await page.getByTestId("submit-button").click();
await expect(page.getByTestId("builds-tab")).toContainText("Builds (50)");
await expect(page.getByTestId("build-card").first()).toContainText(
"Private"
);
await impersonate(page, NZAP_TEST_ID);
await navigate({
page,
url: userBuildsPage({ discordId: ADMIN_DISCORD_ID }),
});
await expect(page.getByTestId("builds-tab")).toContainText("Builds (49)");
await expect(page.getByTestId("build-card").first()).not.toContainText(
"Private"
);
});
});