chore: [app-router-migration-2] migrate trpc, ssgInit, ssrInit (#12593)
* chore: migrate trpc, ssgInit, ssrInit * manual: fix emails * manual: fix ts issues * Update packages/emails/README.md * remove unneeded use client statements * fix flaky locale tests, fix flaky login test --------- Co-authored-by: Omar López <zomars@me.com>
This commit is contained in:
parent
bf2af799d5
commit
db625157d1
|
@ -0,0 +1,212 @@
|
|||
// originally from in the "experimental playground for tRPC + next.js 13" repo owned by trpc team
|
||||
// file link: https://github.com/trpc/next-13/blob/main/%40trpc/next-layout/createTRPCNextLayout.ts
|
||||
// repo link: https://github.com/trpc/next-13
|
||||
// code is / will continue to be adapted for our usage
|
||||
import { dehydrate, QueryClient } from "@tanstack/query-core";
|
||||
import type { DehydratedState, QueryKey } from "@tanstack/react-query";
|
||||
|
||||
import type { Maybe, TRPCClientError, TRPCClientErrorLike } from "@calcom/trpc";
|
||||
import {
|
||||
callProcedure,
|
||||
type AnyProcedure,
|
||||
type AnyQueryProcedure,
|
||||
type AnyRouter,
|
||||
type DataTransformer,
|
||||
type inferProcedureInput,
|
||||
type inferProcedureOutput,
|
||||
type inferRouterContext,
|
||||
type MaybePromise,
|
||||
type ProcedureRouterRecord,
|
||||
} from "@calcom/trpc/server";
|
||||
|
||||
import { createRecursiveProxy, createFlatProxy } from "@trpc/server/shared";
|
||||
|
||||
export function getArrayQueryKey(
|
||||
queryKey: string | [string] | [string, ...unknown[]] | unknown[],
|
||||
type: string
|
||||
): QueryKey {
|
||||
const queryKeyArrayed = Array.isArray(queryKey) ? queryKey : [queryKey];
|
||||
const [arrayPath, input] = queryKeyArrayed;
|
||||
|
||||
if (!input && (!type || type === "any")) {
|
||||
return Array.isArray(arrayPath) && arrayPath.length !== 0 ? [arrayPath] : ([] as unknown as QueryKey);
|
||||
}
|
||||
|
||||
return [
|
||||
arrayPath,
|
||||
{
|
||||
...(typeof input !== "undefined" && { input: input }),
|
||||
...(type && type !== "any" && { type: type }),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// copy starts
|
||||
// copied from trpc/trpc repo
|
||||
// ref: https://github.com/trpc/trpc/blob/main/packages/next/src/withTRPC.tsx#L37-#L58
|
||||
function transformQueryOrMutationCacheErrors<
|
||||
TState extends DehydratedState["queries"][0] | DehydratedState["mutations"][0]
|
||||
>(result: TState): TState {
|
||||
const error = result.state.error as Maybe<TRPCClientError<any>>;
|
||||
if (error instanceof Error && error.name === "TRPCClientError") {
|
||||
const newError: TRPCClientErrorLike<any> = {
|
||||
message: error.message,
|
||||
data: error.data,
|
||||
shape: error.shape,
|
||||
};
|
||||
return {
|
||||
...result,
|
||||
state: {
|
||||
...result.state,
|
||||
error: newError,
|
||||
},
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// copy ends
|
||||
|
||||
interface CreateTRPCNextLayoutOptions<TRouter extends AnyRouter> {
|
||||
router: TRouter;
|
||||
createContext: () => MaybePromise<inferRouterContext<TRouter>>;
|
||||
transformer?: DataTransformer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type DecorateProcedure<TProcedure extends AnyProcedure> = TProcedure extends AnyQueryProcedure
|
||||
? {
|
||||
fetch(input: inferProcedureInput<TProcedure>): Promise<inferProcedureOutput<TProcedure>>;
|
||||
fetchInfinite(input: inferProcedureInput<TProcedure>): Promise<inferProcedureOutput<TProcedure>>;
|
||||
prefetch(input: inferProcedureInput<TProcedure>): Promise<inferProcedureOutput<TProcedure>>;
|
||||
prefetchInfinite(input: inferProcedureInput<TProcedure>): Promise<inferProcedureOutput<TProcedure>>;
|
||||
}
|
||||
: never;
|
||||
|
||||
type OmitNever<TType> = Pick<
|
||||
TType,
|
||||
{
|
||||
[K in keyof TType]: TType[K] extends never ? never : K;
|
||||
}[keyof TType]
|
||||
>;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type DecoratedProcedureRecord<
|
||||
TProcedures extends ProcedureRouterRecord,
|
||||
TPath extends string = ""
|
||||
> = OmitNever<{
|
||||
[TKey in keyof TProcedures]: TProcedures[TKey] extends AnyRouter
|
||||
? DecoratedProcedureRecord<TProcedures[TKey]["_def"]["record"], `${TPath}${TKey & string}.`>
|
||||
: TProcedures[TKey] extends AnyQueryProcedure
|
||||
? DecorateProcedure<TProcedures[TKey]>
|
||||
: never;
|
||||
}>;
|
||||
|
||||
type CreateTRPCNextLayout<TRouter extends AnyRouter> = DecoratedProcedureRecord<TRouter["_def"]["record"]> & {
|
||||
dehydrate(): Promise<DehydratedState>;
|
||||
queryClient: QueryClient;
|
||||
};
|
||||
|
||||
const getStateContainer = <TRouter extends AnyRouter>(opts: CreateTRPCNextLayoutOptions<TRouter>) => {
|
||||
let _trpc: {
|
||||
queryClient: QueryClient;
|
||||
context: inferRouterContext<TRouter>;
|
||||
} | null = null;
|
||||
|
||||
return () => {
|
||||
if (_trpc === null) {
|
||||
_trpc = {
|
||||
context: opts.createContext(),
|
||||
queryClient: new QueryClient(),
|
||||
};
|
||||
}
|
||||
|
||||
return _trpc;
|
||||
};
|
||||
};
|
||||
|
||||
export function createTRPCNextLayout<TRouter extends AnyRouter>(
|
||||
opts: CreateTRPCNextLayoutOptions<TRouter>
|
||||
): CreateTRPCNextLayout<TRouter> {
|
||||
const getState = getStateContainer(opts);
|
||||
|
||||
const transformer = opts.transformer ?? {
|
||||
serialize: (v) => v,
|
||||
deserialize: (v) => v,
|
||||
};
|
||||
|
||||
return createFlatProxy((key) => {
|
||||
const state = getState();
|
||||
const { queryClient } = state;
|
||||
if (key === "queryClient") {
|
||||
return queryClient;
|
||||
}
|
||||
|
||||
if (key === "dehydrate") {
|
||||
// copy starts
|
||||
// copied from trpc/trpc repo
|
||||
// ref: https://github.com/trpc/trpc/blob/main/packages/next/src/withTRPC.tsx#L214-#L229
|
||||
const dehydratedCache = dehydrate(queryClient, {
|
||||
shouldDehydrateQuery() {
|
||||
// makes sure errors are also dehydrated
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
// since error instances can't be serialized, let's make them into `TRPCClientErrorLike`-objects
|
||||
const dehydratedCacheWithErrors = {
|
||||
...dehydratedCache,
|
||||
queries: dehydratedCache.queries.map(transformQueryOrMutationCacheErrors),
|
||||
mutations: dehydratedCache.mutations.map(transformQueryOrMutationCacheErrors),
|
||||
};
|
||||
|
||||
return () => transformer.serialize(dehydratedCacheWithErrors);
|
||||
}
|
||||
// copy ends
|
||||
|
||||
return createRecursiveProxy(async (callOpts) => {
|
||||
const path = [key, ...callOpts.path];
|
||||
const utilName = path.pop();
|
||||
const ctx = await state.context;
|
||||
|
||||
const caller = opts.router.createCaller(ctx);
|
||||
|
||||
const pathStr = path.join(".");
|
||||
const input = callOpts.args[0];
|
||||
|
||||
if (utilName === "fetchInfinite") {
|
||||
return queryClient.fetchInfiniteQuery(getArrayQueryKey([path, input], "infinite"), () =>
|
||||
caller.query(pathStr, input)
|
||||
);
|
||||
}
|
||||
|
||||
if (utilName === "prefetch") {
|
||||
return queryClient.prefetchQuery({
|
||||
queryKey: getArrayQueryKey([path, input], "query"),
|
||||
queryFn: async () => {
|
||||
const res = await callProcedure({
|
||||
procedures: opts.router._def.procedures,
|
||||
path: pathStr,
|
||||
rawInput: input,
|
||||
ctx,
|
||||
type: "query",
|
||||
});
|
||||
return res;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (utilName === "prefetchInfinite") {
|
||||
return queryClient.prefetchInfiniteQuery(getArrayQueryKey([path, input], "infinite"), () =>
|
||||
caller.query(pathStr, input)
|
||||
);
|
||||
}
|
||||
|
||||
return queryClient.fetchQuery(getArrayQueryKey([path, input], "query"), () =>
|
||||
caller.query(pathStr, input)
|
||||
);
|
||||
}) as CreateTRPCNextLayout<TRouter>;
|
||||
});
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||
import { headers } from "next/headers";
|
||||
import superjson from "superjson";
|
||||
|
||||
import { CALCOM_VERSION } from "@calcom/lib/constants";
|
||||
import prisma, { readonlyPrisma } from "@calcom/prisma";
|
||||
import { appRouter } from "@calcom/trpc/server/routers/_app";
|
||||
|
||||
import { createTRPCNextLayout } from "./createTRPCNextLayout";
|
||||
|
||||
export async function ssgInit() {
|
||||
const locale = headers().get("x-locale") ?? "en";
|
||||
|
||||
const i18n = (await serverSideTranslations(locale, ["common"])) || "en";
|
||||
|
||||
const ssg = createTRPCNextLayout({
|
||||
router: appRouter,
|
||||
transformer: superjson,
|
||||
createContext() {
|
||||
return { prisma, insightsDb: readonlyPrisma, session: null, locale, i18n };
|
||||
},
|
||||
});
|
||||
|
||||
// i18n translations are already retrieved from serverSideTranslations call, there is no need to run a i18n.fetch
|
||||
// we can set query data directly to the queryClient
|
||||
const queryKey = [
|
||||
["viewer", "public", "i18n"],
|
||||
{ input: { locale, CalComVersion: CALCOM_VERSION }, type: "query" },
|
||||
];
|
||||
|
||||
ssg.queryClient.setQueryData(queryKey, { i18n });
|
||||
|
||||
return ssg;
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
import { type GetServerSidePropsContext } from "next";
|
||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||
import { headers, cookies } from "next/headers";
|
||||
import superjson from "superjson";
|
||||
|
||||
import { getLocale } from "@calcom/features/auth/lib/getLocale";
|
||||
import { CALCOM_VERSION } from "@calcom/lib/constants";
|
||||
import prisma, { readonlyPrisma } from "@calcom/prisma";
|
||||
import { appRouter } from "@calcom/trpc/server/routers/_app";
|
||||
|
||||
import { createTRPCNextLayout } from "./createTRPCNextLayout";
|
||||
|
||||
export async function ssrInit(options?: { noI18nPreload: boolean }) {
|
||||
const req = {
|
||||
headers: headers(),
|
||||
cookies: cookies(),
|
||||
};
|
||||
|
||||
const locale = await getLocale(req);
|
||||
|
||||
const i18n = (await serverSideTranslations(locale, ["common", "vital"])) || "en";
|
||||
|
||||
const ssr = createTRPCNextLayout({
|
||||
router: appRouter,
|
||||
transformer: superjson,
|
||||
createContext() {
|
||||
return {
|
||||
prisma,
|
||||
insightsDb: readonlyPrisma,
|
||||
session: null,
|
||||
locale,
|
||||
i18n,
|
||||
req: req as unknown as GetServerSidePropsContext["req"],
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// i18n translations are already retrieved from serverSideTranslations call, there is no need to run a i18n.fetch
|
||||
// we can set query data directly to the queryClient
|
||||
const queryKey = [
|
||||
["viewer", "public", "i18n"],
|
||||
{ input: { locale, CalComVersion: CALCOM_VERSION }, type: "query" },
|
||||
];
|
||||
if (!options?.noI18nPreload) {
|
||||
ssr.queryClient.setQueryData(queryKey, { i18n });
|
||||
}
|
||||
|
||||
await Promise.allSettled([
|
||||
// So feature flags are available on first render
|
||||
ssr.viewer.features.map.prefetch(),
|
||||
// Provides a better UX to the users who have already upgraded.
|
||||
ssr.viewer.teams.hasTeamPlan.prefetch(),
|
||||
ssr.viewer.public.session.prefetch(),
|
||||
]);
|
||||
|
||||
return ssr;
|
||||
}
|
|
@ -154,6 +154,9 @@ const matcherConfigUserTypeEmbedRoute = {
|
|||
|
||||
/** @type {import("next").NextConfig} */
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
serverComponentsExternalPackages: ["next-i18next"],
|
||||
},
|
||||
i18n: {
|
||||
...i18n,
|
||||
localeDetection: false,
|
||||
|
|
|
@ -673,8 +673,8 @@ export async function login(
|
|||
await passwordLocator.fill(user.password ?? user.username!);
|
||||
await signInLocator.click();
|
||||
|
||||
// Moving away from waiting 2 seconds, as it is not a reliable way to expect session to be started
|
||||
await page.waitForLoadState("networkidle");
|
||||
// waiting for specific login request to resolve
|
||||
await page.waitForResponse(/\/api\/auth\/callback\/credentials/);
|
||||
}
|
||||
|
||||
export async function apiLogin(
|
||||
|
|
|
@ -11,7 +11,8 @@ test.describe("unauthorized user sees correct translations (de)", async () => {
|
|||
|
||||
test("should use correct translations and html attributes", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await page.waitForLoadState("load");
|
||||
// we dont need to wait for styles and images, only for dom
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
|
||||
await page.locator("html[lang=de]").waitFor({ state: "attached" });
|
||||
await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
|
||||
|
@ -35,7 +36,7 @@ test.describe("unauthorized user sees correct translations (ar)", async () => {
|
|||
|
||||
test("should use correct translations and html attributes", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await page.waitForLoadState("load");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
|
||||
await page.locator("html[lang=ar]").waitFor({ state: "attached" });
|
||||
await page.locator("html[dir=rtl]").waitFor({ state: "attached" });
|
||||
|
@ -59,7 +60,7 @@ test.describe("unauthorized user sees correct translations (zh)", async () => {
|
|||
|
||||
test("should use correct translations and html attributes", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await page.waitForLoadState("load");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
|
||||
await page.locator("html[lang=zh]").waitFor({ state: "attached" });
|
||||
await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
|
||||
|
@ -83,7 +84,7 @@ test.describe("unauthorized user sees correct translations (zh-CN)", async () =>
|
|||
|
||||
test("should use correct translations and html attributes", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await page.waitForLoadState("load");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
|
||||
await page.locator("html[lang=zh-CN]").waitFor({ state: "attached" });
|
||||
await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
|
||||
|
@ -107,7 +108,7 @@ test.describe("unauthorized user sees correct translations (zh-TW)", async () =>
|
|||
|
||||
test("should use correct translations and html attributes", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await page.waitForLoadState("load");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
|
||||
await page.locator("html[lang=zh-TW]").waitFor({ state: "attached" });
|
||||
await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
|
||||
|
@ -131,7 +132,7 @@ test.describe("unauthorized user sees correct translations (pt)", async () => {
|
|||
|
||||
test("should use correct translations and html attributes", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await page.waitForLoadState("load");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
|
||||
await page.locator("html[lang=pt]").waitFor({ state: "attached" });
|
||||
await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
|
||||
|
@ -155,7 +156,7 @@ test.describe("unauthorized user sees correct translations (pt-br)", async () =>
|
|||
|
||||
test("should use correct translations and html attributes", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await page.waitForLoadState("load");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
|
||||
await page.locator("html[lang=pt-BR]").waitFor({ state: "attached" });
|
||||
await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
|
||||
|
@ -179,7 +180,7 @@ test.describe("unauthorized user sees correct translations (es-419)", async () =
|
|||
|
||||
test("should use correct translations and html attributes", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await page.waitForLoadState("load");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
|
||||
// es-419 is disabled in i18n config, so es should be used as fallback
|
||||
await page.locator("html[lang=es]").waitFor({ state: "attached" });
|
||||
|
@ -213,57 +214,61 @@ test.describe("authorized user sees correct translations (de)", async () => {
|
|||
await test.step("should navigate to /event-types and show German translations", async () => {
|
||||
await page.goto("/event-types");
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
|
||||
await page.locator("html[lang=de]").waitFor({ state: "attached" });
|
||||
await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
|
||||
|
||||
{
|
||||
const locator = page.getByText("Ereignistypen", { exact: true });
|
||||
expect(await locator.count()).toBeGreaterThanOrEqual(1);
|
||||
const locator = page.getByRole("heading", { name: "Ereignistypen", exact: true });
|
||||
// locator.count() does not wait for elements
|
||||
// but event-types page is client side, so it takes some time to render html
|
||||
// thats why we need to use method that awaits for the element
|
||||
// https://github.com/microsoft/playwright/issues/14278#issuecomment-1131754679
|
||||
await expect(locator).toHaveCount(1);
|
||||
}
|
||||
|
||||
{
|
||||
const locator = page.getByText("Event Types", { exact: true });
|
||||
expect(await locator.count()).toEqual(0);
|
||||
await expect(locator).toHaveCount(0);
|
||||
}
|
||||
});
|
||||
|
||||
await test.step("should navigate to /bookings and show German translations", async () => {
|
||||
await page.goto("/bookings");
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
|
||||
await page.locator("html[lang=de]").waitFor({ state: "attached" });
|
||||
await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
|
||||
|
||||
{
|
||||
const locator = page.getByText("Buchungen", { exact: true });
|
||||
expect(await locator.count()).toBeGreaterThanOrEqual(1);
|
||||
const locator = page.getByRole("heading", { name: "Buchungen", exact: true });
|
||||
await expect(locator).toHaveCount(1);
|
||||
}
|
||||
|
||||
{
|
||||
const locator = page.getByText("Bookings", { exact: true });
|
||||
expect(await locator.count()).toEqual(0);
|
||||
await expect(locator).toHaveCount(0);
|
||||
}
|
||||
});
|
||||
|
||||
await test.step("should reload the /bookings and show German translations", async () => {
|
||||
await page.reload();
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
|
||||
await page.locator("html[lang=de]").waitFor({ state: "attached" });
|
||||
await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
|
||||
|
||||
{
|
||||
const locator = page.getByText("Buchungen", { exact: true });
|
||||
expect(await locator.count()).toBeGreaterThanOrEqual(1);
|
||||
const locator = page.getByRole("heading", { name: "Buchungen", exact: true });
|
||||
await expect(locator).toHaveCount(1);
|
||||
}
|
||||
|
||||
{
|
||||
const locator = page.getByText("Bookings", { exact: true });
|
||||
expect(await locator.count()).toEqual(0);
|
||||
await expect(locator).toHaveCount(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -285,57 +290,57 @@ test.describe("authorized user sees correct translations (pt-br)", async () => {
|
|||
await test.step("should navigate to /event-types and show Brazil-Portuguese translations", async () => {
|
||||
await page.goto("/event-types");
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
|
||||
await page.locator("html[lang=pt-br]").waitFor({ state: "attached" });
|
||||
await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
|
||||
|
||||
{
|
||||
const locator = page.getByText("Tipos de Eventos", { exact: true });
|
||||
expect(await locator.count()).toBeGreaterThanOrEqual(1);
|
||||
const locator = page.getByRole("heading", { name: "Tipos de Eventos", exact: true });
|
||||
await expect(locator).toHaveCount(1);
|
||||
}
|
||||
|
||||
{
|
||||
const locator = page.getByText("Event Types", { exact: true });
|
||||
expect(await locator.count()).toEqual(0);
|
||||
await expect(locator).toHaveCount(0);
|
||||
}
|
||||
});
|
||||
|
||||
await test.step("should navigate to /bookings and show Brazil-Portuguese translations", async () => {
|
||||
await page.goto("/bookings");
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
|
||||
await page.locator("html[lang=pt-br]").waitFor({ state: "attached" });
|
||||
await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
|
||||
|
||||
{
|
||||
const locator = page.getByText("Reservas", { exact: true });
|
||||
expect(await locator.count()).toBeGreaterThanOrEqual(1);
|
||||
const locator = page.getByRole("heading", { name: "Reservas", exact: true });
|
||||
await expect(locator).toHaveCount(1);
|
||||
}
|
||||
|
||||
{
|
||||
const locator = page.getByText("Bookings", { exact: true });
|
||||
expect(await locator.count()).toEqual(0);
|
||||
await expect(locator).toHaveCount(0);
|
||||
}
|
||||
});
|
||||
|
||||
await test.step("should reload the /bookings and show Brazil-Portuguese translations", async () => {
|
||||
await page.reload();
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
|
||||
await page.locator("html[lang=pt-br]").waitFor({ state: "attached" });
|
||||
await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
|
||||
|
||||
{
|
||||
const locator = page.getByText("Reservas", { exact: true });
|
||||
expect(await locator.count()).toBeGreaterThanOrEqual(1);
|
||||
const locator = page.getByRole("heading", { name: "Reservas", exact: true });
|
||||
await expect(locator).toHaveCount(1);
|
||||
}
|
||||
|
||||
{
|
||||
const locator = page.getByText("Bookings", { exact: true });
|
||||
expect(await locator.count()).toEqual(0);
|
||||
await expect(locator).toHaveCount(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -357,57 +362,57 @@ test.describe("authorized user sees correct translations (ar)", async () => {
|
|||
await test.step("should navigate to /event-types and show Arabic translations", async () => {
|
||||
await page.goto("/event-types");
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
|
||||
await page.locator("html[lang=ar]").waitFor({ state: "attached" });
|
||||
await page.locator("html[dir=rtl]").waitFor({ state: "attached" });
|
||||
|
||||
{
|
||||
const locator = page.getByText("أنواع الحدث", { exact: true });
|
||||
expect(await locator.count()).toBeGreaterThanOrEqual(1);
|
||||
const locator = page.getByRole("heading", { name: "أنواع الحدث", exact: true });
|
||||
await expect(locator).toHaveCount(1);
|
||||
}
|
||||
|
||||
{
|
||||
const locator = page.getByText("Event Types", { exact: true });
|
||||
expect(await locator.count()).toEqual(0);
|
||||
await expect(locator).toHaveCount(0);
|
||||
}
|
||||
});
|
||||
|
||||
await test.step("should navigate to /bookings and show Arabic translations", async () => {
|
||||
await page.goto("/bookings");
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
|
||||
await page.locator("html[lang=ar]").waitFor({ state: "attached" });
|
||||
await page.locator("html[dir=rtl]").waitFor({ state: "attached" });
|
||||
|
||||
{
|
||||
const locator = page.getByText("عمليات الحجز", { exact: true });
|
||||
expect(await locator.count()).toBeGreaterThanOrEqual(1);
|
||||
const locator = page.getByRole("heading", { name: "عمليات الحجز", exact: true });
|
||||
await expect(locator).toHaveCount(1);
|
||||
}
|
||||
|
||||
{
|
||||
const locator = page.getByText("Bookings", { exact: true });
|
||||
expect(await locator.count()).toEqual(0);
|
||||
await expect(locator).toHaveCount(0);
|
||||
}
|
||||
});
|
||||
|
||||
await test.step("should reload the /bookings and show Arabic translations", async () => {
|
||||
await page.reload();
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
|
||||
await page.locator("html[lang=ar]").waitFor({ state: "attached" });
|
||||
await page.locator("html[dir=rtl]").waitFor({ state: "attached" });
|
||||
|
||||
{
|
||||
const locator = page.getByText("عمليات الحجز", { exact: true });
|
||||
expect(await locator.count()).toBeGreaterThanOrEqual(1);
|
||||
const locator = page.getByRole("heading", { name: "عمليات الحجز", exact: true });
|
||||
await expect(locator).toHaveCount(1);
|
||||
}
|
||||
|
||||
{
|
||||
const locator = page.getByText("Bookings", { exact: true });
|
||||
expect(await locator.count()).toEqual(0);
|
||||
await expect(locator).toHaveCount(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -429,7 +434,7 @@ test.describe("authorized user sees changed translations (de->ar)", async () =>
|
|||
await test.step("should change the language and show Arabic translations", async () => {
|
||||
await page.goto("/settings/my-account/general");
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
|
||||
await page.locator(".bg-default > div > div:nth-child(2)").first().click();
|
||||
await page.locator("#react-select-2-option-0").click();
|
||||
|
@ -444,32 +449,33 @@ test.describe("authorized user sees changed translations (de->ar)", async () =>
|
|||
await page.locator("html[dir=rtl]").waitFor({ state: "attached" });
|
||||
|
||||
{
|
||||
const locator = page.getByText("عام", { exact: true }); // "general"
|
||||
expect(await locator.count()).toBeGreaterThanOrEqual(1);
|
||||
// at least one is visible
|
||||
const locator = page.getByText("عام", { exact: true }).last(); // "general"
|
||||
await expect(locator).toBeVisible();
|
||||
}
|
||||
|
||||
{
|
||||
const locator = page.getByText("Allgemein", { exact: true }); // "general"
|
||||
expect(await locator.count()).toEqual(0);
|
||||
await expect(locator).toHaveCount(0);
|
||||
}
|
||||
});
|
||||
|
||||
await test.step("should reload and show Arabic translations", async () => {
|
||||
await page.reload();
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
|
||||
await page.locator("html[lang=ar]").waitFor({ state: "attached" });
|
||||
await page.locator("html[dir=rtl]").waitFor({ state: "attached" });
|
||||
|
||||
{
|
||||
const locator = page.getByText("عام", { exact: true }); // "general"
|
||||
expect(await locator.count()).toBeGreaterThanOrEqual(1);
|
||||
const locator = page.getByText("عام", { exact: true }).last(); // "general"
|
||||
await expect(locator).toBeVisible();
|
||||
}
|
||||
|
||||
{
|
||||
const locator = page.getByText("Allgemein", { exact: true }); // "general"
|
||||
expect(await locator.count()).toEqual(0);
|
||||
await expect(locator).toHaveCount(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -491,7 +497,7 @@ test.describe("authorized user sees changed translations (de->pt-BR) [locale1]",
|
|||
await test.step("should change the language and show Brazil-Portuguese translations", async () => {
|
||||
await page.goto("/settings/my-account/general");
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
|
||||
await page.locator(".bg-default > div > div:nth-child(2)").first().click();
|
||||
await page.locator("#react-select-2-option-14").click();
|
||||
|
@ -506,32 +512,32 @@ test.describe("authorized user sees changed translations (de->pt-BR) [locale1]",
|
|||
await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
|
||||
|
||||
{
|
||||
const locator = page.getByText("Geral", { exact: true }); // "general"
|
||||
expect(await locator.count()).toBeGreaterThanOrEqual(1);
|
||||
const locator = page.getByText("Geral", { exact: true }).last(); // "general"
|
||||
await expect(locator).toBeVisible();
|
||||
}
|
||||
|
||||
{
|
||||
const locator = page.getByText("Allgemein", { exact: true }); // "general"
|
||||
expect(await locator.count()).toEqual(0);
|
||||
await expect(locator).toHaveCount(0);
|
||||
}
|
||||
});
|
||||
|
||||
await test.step("should reload and show Brazil-Portuguese translations", async () => {
|
||||
await page.reload();
|
||||
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
|
||||
await page.locator("html[lang=pt-BR]").waitFor({ state: "attached" });
|
||||
await page.locator("html[dir=ltr]").waitFor({ state: "attached" });
|
||||
|
||||
{
|
||||
const locator = page.getByText("Geral", { exact: true }); // "general"
|
||||
expect(await locator.count()).toBeGreaterThanOrEqual(1);
|
||||
const locator = page.getByText("Geral", { exact: true }).last(); // "general"
|
||||
await expect(locator).toBeVisible();
|
||||
}
|
||||
|
||||
{
|
||||
const locator = page.getByText("Allgemein", { exact: true }); // "general"
|
||||
expect(await locator.count()).toEqual(0);
|
||||
await expect(locator).toHaveCount(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
```ts
|
||||
import { renderEmail } from "@calcom/emails";
|
||||
|
||||
renderEmail("TeamInviteEmail", {
|
||||
await renderEmail("TeamInviteEmail", {
|
||||
language: t,
|
||||
from: "teampro@example.com",
|
||||
to: "pro@example.com",
|
||||
|
|
Loading…
Reference in New Issue
Block a user