chore: Remove all avatar/logo references (#14532)

* chore: Remove all avatar/logo references

* Updated user profile to only use avatarUrl

* fix: minor style issue

* chore: Remove redundant includeTeamLogo

* fix: Use right avatar default in event type list

* fix: placeholder avatar team profile, target

* chore: Add logoUrl/avatarUrl to JWT

* fix: Bunch of org avatar issues, fix members list

* Fix logoUrl on org pages

* More type fixes

* Hopefully final type fixes

* Another round of type fixes

* fix: UserForm ts

* fix: Handle as return types, not input types

* fix: Remove profile and add avatarUrl

* fix: notFound as const

* fix: Seeder avatarUrl params

* revert: Migration changes for easier recovery

* fix: Add explicit type to builder as avatar is now set

* Add logoUrl to unpublished entity

* Avatar test out of scope here

* fix: Use the new avatarUrl after save

* chore: Removed getOrgAvatarUrl/getTeamAvatarUrl

* fix: Update sidebar image immediately after change

* Unable to safe unnamed user, so default

* fix: Unpublished page, add organization test

* Add more tests
This commit is contained in:
Alex van Andel authored and GitHub committed 2024-04-18 21:54:16 -07:00
1 parent 5695ba7c13
commit d25c043046
73 files changed
+644 -770

No files matched your search

+1 -1
View File
@@ -139,7 +139,7 @@ export const AppList = ({ data, handleDisconnect, variant, listClassName }: AppL
...app,
credentialOwner: {
name: team.name,
avatar: team.logo,
avatar: team.logoUrl,
teamId: team.teamId,
credentialId: team.credentialId,
readOnly: !team.isAdmin,
@@ -2,7 +2,7 @@ import useAddAppMutation from "@calcom/app-store/_utils/useAddAppMutation";
import { doesAppSupportTeamInstall } from "@calcom/app-store/utils";
import { Spinner } from "@calcom/features/calendars/weeklyview/components/spinner/Spinner";
import type { UserAdminTeams } from "@calcom/features/ee/teams/lib/getUserAdminTeams";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { RouterOutputs } from "@calcom/trpc/react";
import type { AppFrontendPayload } from "@calcom/types/App";
@@ -124,8 +124,8 @@ export const InstallAppButtonChild = ({
disabled={isInstalled}
CustomStartIcon={
<Avatar
alt={team.logo || ""}
imageSrc={team.logo || `${WEBAPP_URL}/${team.logo}/avatar.png`} // if no image, use default avatar
alt={team.logoUrl || ""}
imageSrc={getPlaceholderAvatar(team.logoUrl, team.name)} // if no image, use default avatar
size="sm"
/>
}
@@ -115,7 +115,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
// credentialIds: team?.credentialId ? [team.credentialId] : [],
credentialOwner: {
name: team.name,
avatar: team.logo,
avatar: team.logoUrl,
teamId: team.teamId,
credentialId: team.credentialId,
},
@@ -33,7 +33,7 @@ const UserProfile = () => {
const mutation = trpc.viewer.updateProfile.useMutation({
onSuccess: async (_data, context) => {
if (context.avatar) {
if (context.avatarUrl) {
showToast(t("your_user_profile_updated_successfully"), "success");
await utils.viewer.me.refetch();
} else
@@ -74,7 +74,7 @@ const UserProfile = () => {
event.preventDefault();
const enteredAvatar = avatarRef.current?.value;
mutation.mutate({
avatar: enteredAvatar,
avatarUrl: enteredAvatar,
});
}
@@ -33,7 +33,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
select: {
id: true,
name: true,
logo: true,
logoUrl: true,
},
},
},
@@ -98,6 +98,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
isPrivate: true,
isOrganization: true,
metadata: true,
logoUrl: true,
},
},
},
@@ -46,6 +46,7 @@ export type UserPageProps = {
markdownStrippedBio: string;
safeBio: string;
entity: {
logoUrl?: string | null;
considerUnpublished: boolean;
orgSlug?: string | null;
name?: string | null;
@@ -98,12 +99,7 @@ export const getServerSideProps: GetServerSideProps<UserPageProps> = async (cont
orgSlug: isValidOrgDomain ? currentOrgDomain : null,
});
const usersWithoutAvatar = usersInOrgContext.map((user) => {
const { avatar: _1, ...rest } = user;
return rest;
});
const isDynamicGroup = usersWithoutAvatar.length > 1;
const isDynamicGroup = usersInOrgContext.length > 1;
log.debug(safeStringify({ usersInOrgContext, isValidOrgDomain, currentOrgDomain, isDynamicGroup }));
if (isDynamicGroup) {
@@ -114,40 +110,27 @@ export const getServerSideProps: GetServerSideProps<UserPageProps> = async (cont
permanent: false,
destination: destinationUrl,
},
} as {
redirect: {
permanent: false;
destination: string;
};
};
} as const;
}
const users = usersWithoutAvatar.map((user) => ({
...user,
avatar: `/${user.username}/avatar.png`,
}));
const isNonOrgUser = (user: { profile: UserProfile }) => {
return !user.profile?.organization;
};
const isThereAnyNonOrgUser = users.some(isNonOrgUser);
const isThereAnyNonOrgUser = usersInOrgContext.some(isNonOrgUser);
if (!users.length || (!isValidOrgDomain && !isThereAnyNonOrgUser)) {
if (!usersInOrgContext.length || (!isValidOrgDomain && !isThereAnyNonOrgUser)) {
return {
notFound: true,
} as {
notFound: true;
};
} as const;
}
const [user] = users; //to be used when dealing with single user, not dynamic group
const [user] = usersInOrgContext; //to be used when dealing with single user, not dynamic group
const profile = {
name: user.name || user.username || "",
image: getUserAvatarUrl({
...user,
profile: user.profile,
avatarUrl: user.avatarUrl,
}),
theme: user.theme,
brandColor: user.brandColor ?? DEFAULT_LIGHT_BRAND_COLOR,
@@ -183,11 +166,11 @@ export const getServerSideProps: GetServerSideProps<UserPageProps> = async (cont
const safeBio = markdownToSafeHTML(user.bio) || "";
const markdownStrippedBio = stripMarkdown(user?.bio || "");
const org = usersWithoutAvatar[0].profile.organization;
const org = usersInOrgContext[0].profile.organization;
return {
props: {
users: users.map((user) => ({
users: usersInOrgContext.map((user) => ({
name: user.name,
username: user.username,
bio: user.bio,
@@ -197,6 +180,7 @@ export const getServerSideProps: GetServerSideProps<UserPageProps> = async (cont
away: user.away,
})),
entity: {
...(org?.logoUrl ? { logoUrl: org?.logoUrl } : {}),
considerUnpublished: !isARedirectFromNonOrgLink && org?.slug === null,
orgSlug: currentOrgDomain,
name: org?.name ?? null,
@@ -3,7 +3,6 @@
import classNames from "classnames";
import type { InferGetServerSidePropsType } from "next";
import Link from "next/link";
import { useSearchParams } from "next/navigation";
import { Toaster } from "react-hot-toast";
import {
@@ -23,7 +22,6 @@ import { type getServerSideProps } from "./users-public-view.getServerSideProps"
export function UserPage(props: InferGetServerSidePropsType<typeof getServerSideProps>) {
const { users, profile, eventTypes, markdownStrippedBio, entity } = props;
const searchParams = useSearchParams();
const [user] = users; //To be used when we only have a single user, not dynamic group
useTheme(profile.theme);
@@ -43,8 +41,6 @@ export function UserPage(props: InferGetServerSidePropsType<typeof getServerSide
...query
} = useRouterQuery();
const isRedirect = searchParams?.get("redirected") === "true" || false;
const fromUserNameRedirected = searchParams?.get("username") || "";
/*
const telemetry = useTelemetry();
useEffect(() => {
@@ -286,7 +286,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
in: userIds as number[],
},
},
select: { id: true, name: true, email: true, avatar: true, username: true },
select: { id: true, name: true, email: true, avatarUrl: true, username: true },
});
const userHashMap = new Map();
-154
View File
@@ -1,154 +0,0 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { z } from "zod";
import {
orgDomainConfig,
whereClauseForOrgWithSlugOrRequestedSlug,
} from "@calcom/features/ee/organizations/lib/orgDomains";
import { AVATAR_FALLBACK } from "@calcom/lib/constants";
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
import logger from "@calcom/lib/logger";
import prisma from "@calcom/prisma";
const log = logger.getSubLogger({ prefix: ["team/[slug]"] });
const querySchema = z
.object({
username: z.string(),
teamname: z.string(),
/**
* Passed when we want to fetch avatar of a particular organization
*/
orgSlug: z.string(),
/**
* Allow fetching avatar of a particular organization
* Avatars being public, we need not worry about others accessing it.
*/
orgId: z.string().transform((s) => Number(s)),
})
.partial();
async function getIdentityData(req: NextApiRequest) {
const { username, teamname, orgId, orgSlug } = querySchema.parse(req.query);
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(req);
const org = isValidOrgDomain ? currentOrgDomain : null;
const orgQuery = orgId
? {
id: orgId,
}
: org
? whereClauseForOrgWithSlugOrRequestedSlug(org)
: null;
if (username) {
const users = await prisma.user.findMany({
where: {
...(orgQuery
? {
profiles: {
some: {
username,
organization: orgQuery,
},
},
}
: {
username,
// If a user is moved, it isn't actually available outside of the organization. So, for non-org domain check for movedToProfileId
movedToProfileId: null,
}),
},
select: { avatar: true, email: true },
});
if (users.length > 1) {
throw new Error(`More than one user found for username "${username}"`);
}
const [user] = users;
return {
name: username,
email: user?.email,
avatar: user?.avatar,
org,
};
}
if (teamname) {
const team = await prisma.team.findFirst({
where: {
slug: teamname,
parent: orgQuery,
},
select: { logo: true },
});
return {
org,
name: teamname,
email: null,
avatar: getPlaceholderAvatar(team?.logo, teamname),
};
}
if (orgSlug) {
const orgs = await prisma.team.findMany({
where: {
...whereClauseForOrgWithSlugOrRequestedSlug(orgSlug),
},
select: {
slug: true,
logo: true,
name: true,
},
});
if (orgs.length > 1) {
// This should never happen, but instead of throwing error, we are just logging to be able to observe when it happens.
log.error("More than one organization found for slug", orgSlug);
}
const org = orgs[0];
return {
org: org?.slug,
name: org?.name,
email: null,
avatar: getPlaceholderAvatar(org?.logo, org?.name),
};
}
}
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const identity = await getIdentityData(req);
const img = identity?.avatar;
// If image isn't set or links to this route itself, use default avatar
if (!img) {
if (identity?.org) {
res.setHeader("x-cal-org", identity.org);
}
res.writeHead(302, {
Location: AVATAR_FALLBACK,
});
return res.end();
}
if (!img.includes("data:image")) {
if (identity.org) {
res.setHeader("x-cal-org", identity.org);
}
res.writeHead(302, { Location: img });
return res.end();
}
const decoded = img.toString().replace("data:image/png;base64,", "").replace("data:image/jpeg;base64,", "");
const imageResp = Buffer.from(decoded, "base64");
if (identity.org) {
res.setHeader("x-cal-org", identity.org);
}
res.writeHead(200, {
"Content-Type": "image/png",
"Content-Length": imageResp.length,
});
res.end(imageResp);
}
+10 -27
View File
@@ -22,7 +22,6 @@ import type { TRPCClientErrorLike } from "@calcom/trpc/client";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
import type { Ensure } from "@calcom/types/utils";
import {
Alert,
Button,
@@ -85,7 +84,7 @@ type Email = {
export type FormValues = {
username: string;
avatar: string;
avatarUrl: string | null;
name: string;
email: string;
bio: string;
@@ -98,15 +97,10 @@ const ProfileView = () => {
const { update } = useSession();
const { data: user, isPending } = trpc.viewer.me.useQuery({ includePasswordAdded: true });
const { data: avatarData } = trpc.viewer.avatar.useQuery(undefined, {
enabled: !isPending && !user?.avatarUrl,
});
const updateProfileMutation = trpc.viewer.updateProfile.useMutation({
onSuccess: async (res) => {
await update(res);
utils.viewer.me.invalidate();
utils.viewer.avatar.invalidate();
utils.viewer.shouldVerifyEmail.invalidate();
if (res.hasEmailBeenChanged && res.sendEmailVerification) {
@@ -250,10 +244,7 @@ const ProfileView = () => {
const userEmail = user.email || "";
const defaultValues = {
username: user.username || "",
avatar: getUserAvatarUrl({
...user,
profile: user.profile,
}),
avatarUrl: user.avatarUrl,
name: user.name || "",
email: userEmail,
bio: user.bio || "",
@@ -284,7 +275,7 @@ const ProfileView = () => {
key={JSON.stringify(defaultValues)}
defaultValues={defaultValues}
isPending={updateProfileMutation.isPending}
isFallbackImg={!user.avatarUrl && !avatarData?.avatar}
isFallbackImg={!user.avatarUrl}
user={user}
userOrganization={user.organization}
onSubmit={(values) => {
@@ -529,7 +520,7 @@ const ProfileForm = ({
const profileFormSchema = z.object({
username: z.string(),
avatar: z.string(),
avatarUrl: z.string().nullable(),
name: z
.string()
.trim()
@@ -622,17 +613,9 @@ const ProfileForm = ({
<div className="flex items-center">
<Controller
control={formMethods.control}
name="avatar"
render={({ field: { value } }) => {
const showRemoveAvatarButton = value === null ? false : !isFallbackImg;
const organization =
userOrganization && userOrganization.id
? {
...(userOrganization as Ensure<NonNullable<typeof user.organization>, "id">),
slug: userOrganization.slug || null,
requestedSlug: userOrganization.metadata?.requestedSlug || null,
}
: null;
name="avatarUrl"
render={({ field: { value, onChange } }) => {
const showRemoveAvatarButton = value !== null;
return (
<>
<UserAvatar data-testid="profile-upload-avatar" previewSrc={value} size="lg" user={user} />
@@ -644,9 +627,9 @@ const ProfileForm = ({
id="avatar-upload"
buttonMsg={t("upload_avatar")}
handleAvatarChange={(newAvatar) => {
formMethods.setValue("avatar", newAvatar, { shouldDirty: true });
onChange(newAvatar);
}}
imageSrc={value}
imageSrc={getUserAvatarUrl({ avatarUrl: value })}
triggerButtonColor={showRemoveAvatarButton ? "secondary" : "primary"}
/>
@@ -654,7 +637,7 @@ const ProfileForm = ({
<Button
color="secondary"
onClick={() => {
formMethods.setValue("avatar", "", { shouldDirty: true });
onChange(null);
}}>
{t("remove")}
</Button>
+3 -5
View File
@@ -13,7 +13,7 @@ import { useEffect } from "react";
import { sdkActionManager, useIsEmbed } from "@calcom/embed-core/embed-iframe";
import EventTypeDescription from "@calcom/features/eventtypes/components/EventTypeDescription";
import { getOrgAvatarUrl, getTeamAvatarUrl } from "@calcom/lib/getAvatarUrl";
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
import useTheme from "@calcom/lib/hooks/useTheme";
@@ -67,6 +67,7 @@ function TeamPage({
<div className="flex h-full min-h-[100dvh] items-center justify-center">
<UnpublishedEntity
{...{ [slugPropertyName]: team.parent ? parentSlug : teamSlug }}
logoUrl={team.parent?.logoUrl || team.logoUrl}
name={team.parent ? team.parent.name : team.name}
/>
</div>
@@ -161,10 +162,7 @@ function TeamPage({
</div>
);
const profileImageSrc =
isValidOrgDomain || team.isOrganization
? getOrgAvatarUrl({ slug: currentOrgDomain, logoUrl: team.logoUrl })
: getTeamAvatarUrl({ slug: team.slug, logoUrl: team.logoUrl, organizationId: team.parent?.id });
const profileImageSrc = getPlaceholderAvatar(team.logoUrl || team.parent?.logoUrl, team.name);
return (
<>
@@ -74,10 +74,6 @@ test.describe("Change username on settings", () => {
});
expect(updatedUser.username).toBe("demo.username");
// Check if user avatar can be accessed and response headers contain 'image/' in the content type
const response = await page.goto("/demo.username/avatar.png");
expect(response?.headers()?.["content-type"]).toContain("image/");
});
test("User can update to PREMIUM username", async ({ page, users }, testInfo) => {
+158 -10
View File
@@ -1,15 +1,18 @@
import { expect } from "@playwright/test";
import path from "path";
import { CAL_URL } from "@calcom/lib/constants";
import { prisma } from "@calcom/prisma";
import { test } from "../lib/fixtures";
test.describe("UploadAvatar", async () => {
test("can upload an image", async ({ page, users }) => {
const user = await users.create({});
test.describe("User Avatar", async () => {
test("it can upload a user profile image", async ({ page, users }) => {
const user = await users.create({ name: "John Doe" });
await user.apiLogin();
let objectKey: string;
await test.step("Can upload an initial picture", async () => {
await page.goto("/settings/my-account/profile");
@@ -26,8 +29,6 @@ test.describe("UploadAvatar", async () => {
await page.getByTestId("upload-avatar").click();
await page.locator("input[name='name']").fill(user.email);
await page.getByText("Update").click();
await page.waitForSelector("text=Settings updated successfully");
@@ -41,18 +42,165 @@ test.describe("UploadAvatar", async () => {
},
});
// Wait for the avatar image with the http src to appear instead of the data uri
const avatar = page.getByTestId("profile-upload-avatar").locator("img[src^=http]");
objectKey = response.objectKey;
const src = await avatar.getAttribute("src");
const avatarImage = page.getByTestId("profile-upload-avatar").locator("img");
await expect(src).toContain(response.objectKey);
await expect(avatarImage).toHaveAttribute("src", new RegExp(`^\/api\/avatar\/${objectKey}\.png$`));
const urlResponse = await page.request.get(`/api/avatar/${response.objectKey}.png`, {
const urlResponse = await page.request.get((await avatarImage.getAttribute("src")) || "", {
maxRedirects: 0,
});
await expect(urlResponse?.status()).toBe(200);
});
await test.step("View avatar on the public page", async () => {
await page.goto(`/${user.username}`);
await expect(page.locator(`img`)).toHaveAttribute(
"src",
new RegExp(`\/api\/avatar\/${objectKey}\.png$`)
);
// verify objectKey is passed to the OG image
// yes, OG image URI encodes at multiple places.. don't want to mess with that.
await expect(page.locator('meta[property="og:image"]')).toHaveAttribute(
"content",
new RegExp(
encodeURIComponent(`meetingImage=${encodeURIComponent(`${CAL_URL}/api/avatar/${objectKey}.png`)}`)
)
);
});
});
});
test.describe("Team Logo", async () => {
test("it can upload a team logo image", async ({ page, users }) => {
const user = await users.create(undefined, { hasTeam: true });
const { team } = await user.getFirstTeamMembership();
await user.apiLogin();
await page.goto(`/settings/teams/${team.id}/profile`);
await test.step("Can upload an initial picture", async () => {
await page.getByTestId("open-upload-avatar-dialog").click();
const [fileChooser] = await Promise.all([
// It is important to call waitForEvent before click to set up waiting.
page.waitForEvent("filechooser"),
// Opens the file chooser.
page.getByTestId("open-upload-image-filechooser").click(),
]);
await fileChooser.setFiles(`${path.dirname(__filename)}/../fixtures/cal.png`);
await page.getByTestId("upload-avatar").click();
await page.getByText("Update").click();
await page.waitForSelector("text=Your team has been updated successfully.");
const response = await prisma.avatar.findUniqueOrThrow({
where: {
teamId_userId_isBanner: {
userId: 0,
teamId: team.id,
isBanner: false,
},
},
});
const avatarImage = page.getByTestId("profile-upload-logo").locator("img");
await expect(avatarImage).toHaveAttribute(
"src",
new RegExp(`^\/api\/avatar\/${response.objectKey}\.png$`)
);
const urlResponse = await page.request.get((await avatarImage.getAttribute("src")) || "", {
maxRedirects: 0,
});
await expect(urlResponse?.status()).toBe(200);
await expect(
page.getByTestId("tab-teams").locator(`img[src="/api/avatar/${response.objectKey}.png"]`)
).toBeVisible();
});
});
});
test.describe("Organization Logo", async () => {
test("it can upload a organization logo image", async ({ page, users, orgs }) => {
const owner = await users.create(undefined, { hasTeam: true, isUnpublished: true, isOrg: true });
const { team: org } = await owner.getOrgMembership();
await owner.apiLogin();
await page.goto("/settings/organizations/profile");
let objectKey: string;
await test.step("Can upload an initial picture", async () => {
await page.getByTestId("open-upload-avatar-dialog").click();
const [fileChooser] = await Promise.all([
// It is important to call waitForEvent before click to set up waiting.
page.waitForEvent("filechooser"),
// Opens the file chooser.
page.getByTestId("open-upload-image-filechooser").click(),
]);
await fileChooser.setFiles(`${path.dirname(__filename)}/../fixtures/cal.png`);
await page.getByTestId("upload-avatar").click();
await page.getByText("Update").click();
await page.waitForSelector("text=Your organization updated successfully");
const response = await prisma.avatar.findUniqueOrThrow({
where: {
teamId_userId_isBanner: {
userId: 0,
teamId: org.id,
isBanner: false,
},
},
});
objectKey = response.objectKey;
const avatarImage = page.getByTestId("profile-upload-logo").locator("img");
await expect(avatarImage).toHaveAttribute(
"src",
new RegExp(`^\/api\/avatar\/${response.objectKey}\.png$`)
);
const urlResponse = await page.request.get((await avatarImage.getAttribute("src")) || "", {
maxRedirects: 0,
});
await expect(urlResponse?.status()).toBe(200);
// TODO: Implement the org logo updating in the sidebar
// this should be done in the orgBrandingContext
});
const requestedSlug = org.metadata?.requestedSlug;
await test.step("it shows the correct logo on the unpublished public page", async () => {
await page.goto(`/org/${requestedSlug}`);
expect(await page.locator('[data-testid="empty-screen"]').count()).toBe(1);
await expect(page.locator(`img`)).toHaveAttribute(
"src",
new RegExp(`^\/api\/avatar\/${objectKey}\.png$`)
);
});
// TODO: add test for published team.
// unpublished works regardless of orgDomain but when it is published it does work
});
});
+7 -8
View File
@@ -9,7 +9,6 @@ test.describe.configure({ mode: "parallel" });
const title = (name: string) => `${name} is unpublished`;
const description = (entity: string) =>
`This ${entity} link is currently not available. Please contact the ${entity} owner or ask them to publish it.`;
const avatar = (slug: string, entity = "team") => `/${entity}/${slug}/avatar.png`;
test.afterAll(async ({ users }) => {
await users.deleteAll();
@@ -24,7 +23,7 @@ test.describe("Unpublished", () => {
expect(await page.locator('[data-testid="empty-screen"]').count()).toBe(1);
expect(await page.locator(`h2:has-text("${title(team.name)}")`).count()).toBe(1);
expect(await page.locator(`div:text("${description("team")}")`).count()).toBe(1);
await expect(page.locator(`img`)).toHaveAttribute("src", avatar(requestedSlug));
await expect(page.locator(`img`)).toHaveAttribute("src", /.*/);
});
test("Regular team event type", async ({ page, users }) => {
@@ -40,7 +39,7 @@ test.describe("Unpublished", () => {
expect(await page.locator('[data-testid="empty-screen"]').count()).toBe(1);
expect(await page.locator(`h2:has-text("${title(team.name)}")`).count()).toBe(1);
expect(await page.locator(`div:text("${description("team")}")`).count()).toBe(1);
await expect(page.locator(`img`)).toHaveAttribute("src", avatar(requestedSlug));
await expect(page.locator(`img`)).toHaveAttribute("src", /.*/);
});
test("Organization profile", async ({ users, page }) => {
@@ -52,7 +51,7 @@ test.describe("Unpublished", () => {
expect(await page.locator('[data-testid="empty-screen"]').count()).toBe(1);
expect(await page.locator(`h2:has-text("${title(org.name)}")`).count()).toBe(1);
expect(await page.locator(`div:text("${description("organization")}")`).count()).toBe(1);
await expect(page.locator(`img`)).toHaveAttribute("src", avatar(requestedSlug, "org"));
await expect(page.locator(`img`)).toHaveAttribute("src", /.*/);
});
test("Organization sub-team", async ({ users, page }) => {
@@ -70,7 +69,7 @@ test.describe("Unpublished", () => {
expect(await page.locator('[data-testid="empty-screen"]').count()).toBe(1);
expect(await page.locator(`h2:has-text("${title(org.name)}")`).count()).toBe(1);
expect(await page.locator(`div:text("${description("organization")}")`).count()).toBe(1);
await expect(page.locator(`img`)).toHaveAttribute("src", avatar(requestedSlug, "org"));
await expect(page.locator(`img`)).toHaveAttribute("src", /.*/);
});
test("Organization sub-team event-type", async ({ users, page }) => {
@@ -90,7 +89,7 @@ test.describe("Unpublished", () => {
expect(await page.locator('[data-testid="empty-screen"]').count()).toBe(1);
expect(await page.locator(`h2:has-text("${title(org.name)}")`).count()).toBe(1);
expect(await page.locator(`div:text("${description("organization")}")`).count()).toBe(1);
await expect(page.locator(`img`)).toHaveAttribute("src", avatar(requestedSlug, "org"));
await expect(page.locator(`img`)).toHaveAttribute("src", /.*/);
});
test("Organization user", async ({ users, page }) => {
@@ -102,7 +101,7 @@ test.describe("Unpublished", () => {
expect(await page.locator('[data-testid="empty-screen"]').count()).toBe(1);
expect(await page.locator(`h2:has-text("${title(org.name)}")`).count()).toBe(1);
expect(await page.locator(`div:text("${description("organization")}")`).count()).toBe(1);
await expect(page.locator(`img`)).toHaveAttribute("src", avatar(requestedSlug, "org"));
await expect(page.locator(`img`)).toHaveAttribute("src", /.*/);
});
test("Organization user event-type", async ({ users, page }) => {
@@ -115,6 +114,6 @@ test.describe("Unpublished", () => {
expect(await page.locator('[data-testid="empty-screen"]').count()).toBe(1);
expect(await page.locator(`h2:has-text("${title(org.name)}")`).count()).toBe(1);
expect(await page.locator(`div:text("${description("organization")}")`).count()).toBe(1);
await expect(page.locator(`img`)).toHaveAttribute("src", avatar(requestedSlug, "org"));
await expect(page.locator(`img`)).toHaveAttribute("src", /.*/);
});
});