test: Add RegExp tests for Next.config.js rewrites (#9681)
* Add RegExp tests * Update apps/web/test/lib/next-config.test.ts * Dont avoid rewrite for /booked event slug * Add /embed/embed.js test * Add dynamic group booking link testpull/9526/head
parent
6bdb2b94eb
commit
820b2da16d
|
@ -1,10 +1,10 @@
|
|||
require("dotenv").config({ path: "../../.env" });
|
||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||
const os = require("os");
|
||||
const glob = require("glob");
|
||||
const englishTranslation = require("./public/static/locales/en/common.json");
|
||||
const { withAxiom } = require("next-axiom");
|
||||
const { i18n } = require("./next-i18next.config");
|
||||
const { pages } = require("./pages");
|
||||
|
||||
if (!process.env.NEXTAUTH_SECRET) throw new Error("Please set NEXTAUTH_SECRET");
|
||||
if (!process.env.CALENDSO_ENCRYPTION_KEY) throw new Error("Please set CALENDSO_ENCRYPTION_KEY");
|
||||
|
@ -82,16 +82,19 @@ if (process.env.ANALYZE === "true") {
|
|||
|
||||
plugins.push(withAxiom);
|
||||
|
||||
/** Needed to rewrite public booking page, gets all static pages but [user] */
|
||||
const pages = glob
|
||||
.sync("pages/**/[^_]*.{tsx,js,ts}", { cwd: __dirname })
|
||||
.map((filename) =>
|
||||
filename
|
||||
.substr(6)
|
||||
.replace(/(\.tsx|\.js|\.ts)/, "")
|
||||
.replace(/\/.*/, "")
|
||||
)
|
||||
.filter((v, i, self) => self.indexOf(v) === i && !v.startsWith("[user]"));
|
||||
// .* matches / as well(Note: *(i.e wildcard) doesn't match / but .*(i.e. RegExp) does)
|
||||
// It would match /free/30min but not /bookings/upcoming because 'bookings' is an item in pages
|
||||
// It would also not match /free/30min/embed because we are ensuring just two slashes
|
||||
// ?!book ensures it doesn't match /free/book page which doesn't have a corresponding new-booker page.
|
||||
// [^/]+ makes the RegExp match the full path, it seems like a partial match doesn't work.
|
||||
// book$ ensures that only /book is excluded from rewrite(which is at the end always) and not /booked
|
||||
|
||||
// Important Note: When modifying these RegExps update apps/web/test/lib/next-config.test.ts as well
|
||||
const userTypeRouteRegExp = `/:user((?!${pages.join("/|")})[^/]*)/:type((?!book$)[^/]+)`;
|
||||
const teamTypeRouteRegExp = "/team/:slug/:type((?!book$)[^/]+)";
|
||||
const privateLinkRouteRegExp = "/d/:link/:slug((?!book$)[^/]+)";
|
||||
const embedUserTypeRouteRegExp = `/:user((?!${pages.join("|")}).*)/:type/embed`;
|
||||
const embedTeamTypeRouteRegExp = "/team/:slug/:type/embed";
|
||||
|
||||
/** @type {import("next").NextConfig} */
|
||||
const nextConfig = {
|
||||
|
@ -183,14 +186,6 @@ const nextConfig = {
|
|||
return config;
|
||||
},
|
||||
async rewrites() {
|
||||
// .* matches / as well(Note: *(i.e wildcard) doesn't match / but .*(i.e. RegExp) does)
|
||||
// It would match /free/30min but not /bookings/upcoming because 'bookings' is an item in pages
|
||||
// It would also not match /free/30min/embed because we are ensuring just two slashes
|
||||
// ?!book ensures it doesn't match /free/book page which doesn't have a corresponding new-booker page.
|
||||
// [^/]+ makes the RegExp match the full path, it seems like a partial match doesn't work.
|
||||
const userTypeRouteRegExp = `/:user((?!${pages.join("/|")})[^/]*)/:type((?!book)[^/]+)`;
|
||||
const teamTypeRouteRegExp = "/team/:slug/:type((?!book)[^/]+)";
|
||||
const privateLinkRouteRegExp = "/d/:link/:slug((?!book)[^/]+)";
|
||||
let rewrites = [
|
||||
{
|
||||
source: "/org/:slug",
|
||||
|
@ -248,12 +243,12 @@ const nextConfig = {
|
|||
// Keep cookie based booker enabled to test new-booker embed in production
|
||||
...[
|
||||
{
|
||||
source: `/:user((?!${pages.join("|")}).*)/:type/embed`,
|
||||
source: embedUserTypeRouteRegExp,
|
||||
destination: "/new-booker/:user/:type/embed",
|
||||
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
||||
},
|
||||
{
|
||||
source: "/team/:slug/:type/embed",
|
||||
source: embedTeamTypeRouteRegExp,
|
||||
destination: "/new-booker/team/:slug/:type/embed",
|
||||
has: [{ type: "cookie", key: "new-booker-enabled" }],
|
||||
},
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
const glob = require("glob");
|
||||
|
||||
/** Needed to rewrite public booking page, gets all static pages but [user] */
|
||||
const pages = glob
|
||||
.sync("pages/**/[^_]*.{tsx,js,ts}", { cwd: __dirname })
|
||||
.map((filename) =>
|
||||
filename
|
||||
.substr(6)
|
||||
.replace(/(\.tsx|\.js|\.ts)/, "")
|
||||
.replace(/\/.*/, "")
|
||||
)
|
||||
.filter((v, i, self) => self.indexOf(v) === i && !v.startsWith("[user]"));
|
||||
|
||||
exports.pages = pages;
|
|
@ -0,0 +1,157 @@
|
|||
|
||||
import { it, expect, describe, beforeAll, afterAll } from "vitest";
|
||||
let userTypeRouteRegExp: RegExp;
|
||||
let teamTypeRouteRegExp:RegExp;
|
||||
let privateLinkRouteRegExp:RegExp
|
||||
let embedUserTypeRouteRegExp:RegExp
|
||||
let embedTeamTypeRouteRegExp:RegExp
|
||||
|
||||
|
||||
const getRegExpFromNextJsRewriteRegExp = (nextJsRegExp:string) => {
|
||||
// const parts = nextJsRegExp.split(':');
|
||||
|
||||
// const validNamedGroupRegExp = parts.map((part, index)=>{
|
||||
// if (index === 0) {
|
||||
// return part;
|
||||
// }
|
||||
// if (part.match(/^[a-zA-Z0-9]+$/)) {
|
||||
// return `(?<${part}>[^/]+)`
|
||||
// }
|
||||
// part = part.replace(new RegExp('([^(]+)(.*)'), '(?<$1>$2)');
|
||||
// return part
|
||||
// }).join('');
|
||||
|
||||
// TODO: If we can easily convert the exported rewrite regexes from next.config.js to a valid named capturing group regex, it would be best
|
||||
// Next.js does an exact match as per my testing.
|
||||
return new RegExp(`^${nextJsRegExp}$`)
|
||||
}
|
||||
|
||||
describe('next.config.js - RegExp', ()=>{
|
||||
beforeAll(async()=>{
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// process.env.NEXTAUTH_SECRET = process.env.NEXTAUTH_URL = process.env.CALENDSO_ENCRYPTION_KEY = 1
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const pages = require("../../pages").pages
|
||||
|
||||
// How to convert a Next.js rewrite RegExp/wildcard to a valid JS named capturing Group RegExp?
|
||||
// - /:user/ -> (?<user>[^/]+)
|
||||
// - /:user(?!404)[^/]+/ -> (?<user>((?!404)[^/]+))
|
||||
|
||||
// userTypeRouteRegExp = `/:user((?!${pages.join("/|")})[^/]*)/:type((?!book$)[^/]+)`;
|
||||
userTypeRouteRegExp = getRegExpFromNextJsRewriteRegExp(`/(?<user>((?!${pages.join("/|")})[^/]*))/(?<type>((?!book$)[^/]+))`);
|
||||
|
||||
// teamTypeRouteRegExp = "/team/:slug/:type((?!book$)[^/]+)";
|
||||
teamTypeRouteRegExp = getRegExpFromNextJsRewriteRegExp("/team/(?<slug>[^/]+)/(?<type>((?!book$)[^/]+))");
|
||||
|
||||
// privateLinkRouteRegExp = "/d/:link/:slug((?!book$)[^/]+)";
|
||||
privateLinkRouteRegExp = getRegExpFromNextJsRewriteRegExp("/d/(?<link>[^/]+)/(?<slug>((?!book$)[^/]+))");
|
||||
|
||||
// embedUserTypeRouteRegExp = `/:user((?!${pages.join("|")}).*)/:type/embed`;
|
||||
embedUserTypeRouteRegExp = getRegExpFromNextJsRewriteRegExp(`/(?<user>((?!${pages.join("|")}).*))/(?<type>[^/]+)/embed`);
|
||||
|
||||
// embedTeamTypeRouteRegExp = "/team/:slug/:type/embed";
|
||||
embedTeamTypeRouteRegExp = getRegExpFromNextJsRewriteRegExp("/team/(?<slug>[^/]+)/(?<type>[^/]+)/embed");
|
||||
});
|
||||
|
||||
afterAll(()=>{
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
process.env.NEXTAUTH_SECRET = process.env.NEXTAUTH_URL = process.env.CALENDSO_ENCRYPTION_KEY = undefined
|
||||
})
|
||||
|
||||
it("Booking Urls", async () => {
|
||||
expect(userTypeRouteRegExp.exec('/free/30')?.groups).toContain({
|
||||
user: 'free',
|
||||
type: '30'
|
||||
})
|
||||
|
||||
// Edgecase of username starting with team also works
|
||||
expect(userTypeRouteRegExp.exec('/teampro/30')?.groups).toContain({
|
||||
user: 'teampro',
|
||||
type: '30'
|
||||
})
|
||||
|
||||
expect(userTypeRouteRegExp.exec('/teampro+pro/30')?.groups).toContain({
|
||||
user: 'teampro+pro',
|
||||
type: '30'
|
||||
})
|
||||
|
||||
expect(userTypeRouteRegExp.exec('/teampro+pro/book')).toEqual(null)
|
||||
|
||||
// Because /book doesn't have a corresponding new-booker route.
|
||||
expect(userTypeRouteRegExp.exec('/free/book')).toEqual(null)
|
||||
|
||||
// Because /booked is a normal event name
|
||||
expect(userTypeRouteRegExp.exec('/free/booked')?.groups).toEqual({
|
||||
user: 'free',
|
||||
type: 'booked'
|
||||
})
|
||||
|
||||
|
||||
expect(embedUserTypeRouteRegExp.exec('/free/30/embed')?.groups).toContain({
|
||||
user: 'free',
|
||||
type:'30'
|
||||
})
|
||||
|
||||
expect(teamTypeRouteRegExp.exec('/team/seeded/30')?.groups).toContain({
|
||||
slug: 'seeded',
|
||||
type: '30'
|
||||
})
|
||||
|
||||
// Because /book doesn't have a corresponding new-booker route.
|
||||
expect(teamTypeRouteRegExp.exec('/team/seeded/book')).toEqual(null)
|
||||
|
||||
expect(teamTypeRouteRegExp.exec('/team/seeded/30/embed')).toEqual(null)
|
||||
|
||||
expect(embedTeamTypeRouteRegExp.exec('/team/seeded/30/embed')?.groups).toContain({
|
||||
slug: 'seeded',
|
||||
type:'30'
|
||||
})
|
||||
|
||||
expect(privateLinkRouteRegExp.exec('/d/3v4s321CXRJZx5TFxkpPvd/30min')?.groups).toContain({
|
||||
link: '3v4s321CXRJZx5TFxkpPvd',
|
||||
slug: '30min'
|
||||
})
|
||||
|
||||
expect(privateLinkRouteRegExp.exec('/d/3v4s321CXRJZx5TFxkpPvd/30min')?.groups).toContain({
|
||||
link: '3v4s321CXRJZx5TFxkpPvd',
|
||||
slug: '30min'
|
||||
})
|
||||
|
||||
// Because /book doesn't have a corresponding new-booker route.
|
||||
expect(privateLinkRouteRegExp.exec('/d/3v4s321CXRJZx5TFxkpPvd/book')).toEqual(null)
|
||||
});
|
||||
|
||||
it('Non booking Urls', ()=>{
|
||||
|
||||
expect(userTypeRouteRegExp.exec('/404')).toEqual(null)
|
||||
expect(teamTypeRouteRegExp.exec('/404')).toEqual(null)
|
||||
|
||||
expect(userTypeRouteRegExp.exec('/404/30')).toEqual(null)
|
||||
expect(teamTypeRouteRegExp.exec('/404/30')).toEqual(null)
|
||||
|
||||
expect(userTypeRouteRegExp.exec('/api')).toEqual(null)
|
||||
expect(teamTypeRouteRegExp.exec('/api')).toEqual(null)
|
||||
|
||||
expect(userTypeRouteRegExp.exec('/api/30')).toEqual(null)
|
||||
expect(teamTypeRouteRegExp.exec('/api/30')).toEqual(null)
|
||||
|
||||
expect(userTypeRouteRegExp.exec('/workflows/30')).toEqual(null)
|
||||
expect(teamTypeRouteRegExp.exec('/workflows/30')).toEqual(null)
|
||||
|
||||
expect(userTypeRouteRegExp.exec('/event-types/30')).toEqual(null)
|
||||
expect(teamTypeRouteRegExp.exec('/event-types/30')).toEqual(null)
|
||||
|
||||
expect(userTypeRouteRegExp.exec('/teams/1')).toEqual(null)
|
||||
expect(teamTypeRouteRegExp.exec('/teams/1')).toEqual(null)
|
||||
|
||||
expect(userTypeRouteRegExp.exec('/teams')).toEqual(null)
|
||||
expect(teamTypeRouteRegExp.exec('/teams')).toEqual(null)
|
||||
|
||||
// Note that even though it matches /embed/embed.js, but it's served from /public and the regexes are in afterEach, it won't hit the flow.
|
||||
// expect(userTypeRouteRegExp.exec('/embed/embed.js')).toEqual(null)
|
||||
// expect(teamTypeRouteRegExp.exec('/embed/embed.js')).toEqual(null)
|
||||
})
|
||||
})
|
||||
|
Loading…
Reference in New Issue