import { join } from "path"; import fse from "fs-extra"; import execa from "execa"; import Builder, { ASSETS_DIR } from "../../src/build"; import { DEFAULT_LAMBDA_CODE_DIR, API_LAMBDA_CODE_DIR, IMAGE_LAMBDA_CODE_DIR } from "../../src/build"; import { cleanupDir, removeNewLineChars } from "../test-utils"; import { OriginRequestDefaultHandlerManifest } from "../../src/types"; jest.mock("execa"); describe("Builder Tests (dynamic)", () => { let fseRemoveSpy: jest.SpyInstance; let fseEmptyDirSpy: jest.SpyInstance; let defaultBuildManifest: OriginRequestDefaultHandlerManifest; const fixturePath = join(__dirname, "./dynamic-app-fixture"); const outputDir = join(fixturePath, ".test_sls_next_output"); beforeEach(async () => { const mockExeca = execa as jest.Mock; mockExeca.mockResolvedValueOnce(); fseRemoveSpy = jest.spyOn(fse, "remove").mockImplementation(() => { return; }); fseEmptyDirSpy = jest.spyOn(fse, "emptyDir"); const builder = new Builder(fixturePath, outputDir); await builder.build(); defaultBuildManifest = await fse.readJSON( join(outputDir, `${DEFAULT_LAMBDA_CODE_DIR}/manifest.json`) ); }); afterEach(() => { fseEmptyDirSpy.mockRestore(); fseRemoveSpy.mockRestore(); return cleanupDir(outputDir); }); describe("Cleanup", () => { it(".next directory is emptied except for cache/ folder", () => { expect(fseRemoveSpy).toBeCalledWith( join(fixturePath, ".next/serverless") ); expect(fseRemoveSpy).toBeCalledWith( join(fixturePath, ".next/prerender-manifest.json") ); expect(fseRemoveSpy).not.toBeCalledWith(join(fixturePath, ".next/cache")); }); it("output directory is cleanup before building", () => { expect(fseEmptyDirSpy).toBeCalledWith( expect.stringContaining(join(".test_sls_next_output", "default-lambda")) ); expect(fseEmptyDirSpy).toBeCalledWith( expect.stringContaining(join(".test_sls_next_output", "api-lambda")) ); expect(fseEmptyDirSpy).toBeCalledWith( expect.stringContaining(join(".test_sls_next_output", "assets")) ); }); }); describe("Default Handler Manifest", () => { it("adds full manifest", () => { const { buildId, publicFiles, pages: { dynamic, ssr, ssg, html }, trailingSlash } = defaultBuildManifest; expect(removeNewLineChars(buildId)).toEqual("test-build-id"); expect(dynamic).toEqual([ { route: "/catchall/[...slug]", regex: "^\\/catchall(?:\\/((?:[^\\/#\\?]+?)(?:\\/(?:[^\\/#\\?]+?))*))?[\\/#\\?]?$" }, { route: "/en/catchall/[...slug]", regex: "^\\/en\\/catchall(?:\\/((?:[^\\/#\\?]+?)(?:\\/(?:[^\\/#\\?]+?))*))?[\\/#\\?]?$" }, { route: "/en/fallback/[slug]", regex: "^\\/en\\/fallback(?:\\/([^\\/#\\?]+?))[\\/#\\?]?$" }, { route: "/en/fallback-blocking/[slug]", regex: "^\\/en\\/fallback-blocking(?:\\/([^\\/#\\?]+?))[\\/#\\?]?$" }, { route: "/en/no-fallback/[slug]", regex: "^\\/en\\/no-fallback(?:\\/([^\\/#\\?]+?))[\\/#\\?]?$" }, { route: "/en/optional-catchall/[[...slug]]", regex: "^\\/en\\/optional-catchall(?:\\/((?:[^\\/#\\?]+?)(?:\\/(?:[^\\/#\\?]+?))*))?[\\/#\\?]?$" }, { route: "/fallback/[slug]", regex: "^\\/fallback(?:\\/([^\\/#\\?]+?))[\\/#\\?]?$" }, { route: "/fallback-blocking/[slug]", regex: "^\\/fallback-blocking(?:\\/([^\\/#\\?]+?))[\\/#\\?]?$" }, { route: "/nl/catchall/[...slug]", regex: "^\\/nl\\/catchall(?:\\/((?:[^\\/#\\?]+?)(?:\\/(?:[^\\/#\\?]+?))*))?[\\/#\\?]?$" }, { route: "/nl/fallback/[slug]", regex: "^\\/nl\\/fallback(?:\\/([^\\/#\\?]+?))[\\/#\\?]?$" }, { route: "/nl/fallback-blocking/[slug]", regex: "^\\/nl\\/fallback-blocking(?:\\/([^\\/#\\?]+?))[\\/#\\?]?$" }, { route: "/nl/no-fallback/[slug]", regex: "^\\/nl\\/no-fallback(?:\\/([^\\/#\\?]+?))[\\/#\\?]?$" }, { route: "/nl/optional-catchall/[[...slug]]", regex: "^\\/nl\\/optional-catchall(?:\\/((?:[^\\/#\\?]+?)(?:\\/(?:[^\\/#\\?]+?))*))?[\\/#\\?]?$" }, { route: "/no-fallback/[slug]", regex: "^\\/no-fallback(?:\\/([^\\/#\\?]+?))[\\/#\\?]?$" }, { route: "/optional-catchall/[[...slug]]", regex: "^\\/optional-catchall(?:\\/((?:[^\\/#\\?]+?)(?:\\/(?:[^\\/#\\?]+?))*))?[\\/#\\?]?$" } ]); expect(ssr).toEqual({ dynamic: { "/catchall/[...slug]": "pages/catchall/[...slug].js", "/en/catchall/[...slug]": "pages/catchall/[...slug].js", "/en/fallback-blocking/[slug]": "pages/fallback-blocking/[slug].js", "/en/fallback/[slug]": "pages/fallback/[slug].js", "/en/no-fallback/[slug]": "pages/no-fallback/[slug].js", "/en/optional-catchall/[[...slug]]": "pages/optional-catchall/[[...slug]].js", "/fallback-blocking/[slug]": "pages/fallback-blocking/[slug].js", "/fallback/[slug]": "pages/fallback/[slug].js", "/nl/catchall/[...slug]": "pages/catchall/[...slug].js", "/nl/fallback-blocking/[slug]": "pages/fallback-blocking/[slug].js", "/nl/fallback/[slug]": "pages/fallback/[slug].js", "/nl/no-fallback/[slug]": "pages/no-fallback/[slug].js", "/nl/optional-catchall/[[...slug]]": "pages/optional-catchall/[[...slug]].js", "/no-fallback/[slug]": "pages/no-fallback/[slug].js", "/optional-catchall/[[...slug]]": "pages/optional-catchall/[[...slug]].js" }, nonDynamic: { "/en/_error": "pages/_error.js", "/en/optional-catchall": "pages/optional-catchall/[[...slug]].js", "/en/ssg": "pages/ssg.js", "/en/ssr": "pages/ssr.js", "/nl/_error": "pages/_error.js", "/nl/optional-catchall": "pages/optional-catchall/[[...slug]].js", "/nl/ssg": "pages/ssg.js", "/nl/ssr": "pages/ssr.js" } }); expect(html).toEqual({ dynamic: {}, nonDynamic: { "/en/404": "pages/en/404.html", "/en/500": "pages/en/500.html", "/en/static": "pages/en/static.html", "/nl/404": "pages/nl/404.html", "/nl/500": "pages/nl/500.html", "/nl/static": "pages/nl/static.html" } }); expect(ssg).toEqual({ dynamic: { "/en/fallback-blocking/[slug]": { fallback: null }, "/en/fallback/[slug]": { fallback: "/en/fallback/[slug].html" }, "/en/no-fallback/[slug]": { fallback: false }, "/nl/fallback-blocking/[slug]": { fallback: null }, "/nl/fallback/[slug]": { fallback: "/nl/fallback/[slug].html" }, "/nl/no-fallback/[slug]": { fallback: false } }, nonDynamic: { "/en": { initialRevalidateSeconds: false, srcRoute: null }, "/en/fallback-blocking/a": { initialRevalidateSeconds: false, srcRoute: "/fallback-blocking/[slug]" }, "/en/fallback/a": { initialRevalidateSeconds: false, srcRoute: "/fallback/[slug]" }, "/en/no-fallback/a": { initialRevalidateSeconds: 60, srcRoute: "/no-fallback/[slug]" }, "/en/ssg": { initialRevalidateSeconds: 60, srcRoute: null }, "/nl": { initialRevalidateSeconds: false, srcRoute: null }, "/nl/ssg": { initialRevalidateSeconds: 60, srcRoute: null } }, notFound: { "/fallback/not-found": true } }); expect(publicFiles).toEqual({ "/public-file.txt": "public-file.txt" }); expect(trailingSlash).toBe(false); }); }); describe("API Handler", () => { it("has empty API handler directory", async () => { expect.assertions(1); const apiDir = await fse.readdir( join(outputDir, `${API_LAMBDA_CODE_DIR}`) ); expect(apiDir).toEqual([]); }); }); describe("Images Handler", () => { it("has empty images handler directory", async () => { expect.assertions(1); const imageDir = await fse.readdir( join(outputDir, `${IMAGE_LAMBDA_CODE_DIR}`) ); expect(imageDir).toEqual([]); }); }); describe("Default Handler", () => { it("copies build files", async () => { const files = await fse.readdir( join(outputDir, `${DEFAULT_LAMBDA_CODE_DIR}`) ); const pages = await fse.readdir( join(outputDir, `${DEFAULT_LAMBDA_CODE_DIR}/pages`) ); const enPages = await fse.readdir( join(outputDir, `${DEFAULT_LAMBDA_CODE_DIR}/pages/en`) ); const enFallbackPages = await fse.readdir( join(outputDir, `${DEFAULT_LAMBDA_CODE_DIR}/pages/en/fallback`) ); const enFallbackBlockingPages = await fse.readdir( join(outputDir, `${DEFAULT_LAMBDA_CODE_DIR}/pages/en/fallback-blocking`) ); const enNoFallbackPages = await fse.readdir( join(outputDir, `${DEFAULT_LAMBDA_CODE_DIR}/pages/en/no-fallback`) ); const catchallPages = await fse.readdir( join(outputDir, `${DEFAULT_LAMBDA_CODE_DIR}/pages/catchall`) ); const optionalCatchallPages = await fse.readdir( join(outputDir, `${DEFAULT_LAMBDA_CODE_DIR}/pages/optional-catchall`) ); const fallbackPages = await fse.readdir( join(outputDir, `${DEFAULT_LAMBDA_CODE_DIR}/pages/fallback`) ); const fallbackBlockingPages = await fse.readdir( join(outputDir, `${DEFAULT_LAMBDA_CODE_DIR}/pages/fallback-blocking`) ); const nofallbackPages = await fse.readdir( join(outputDir, `${DEFAULT_LAMBDA_CODE_DIR}/pages/no-fallback`) ); const apiDirExists = await fse.pathExists( join(outputDir, `${DEFAULT_LAMBDA_CODE_DIR}/pages/api`) ); expect(files).toEqual( expect.arrayContaining([ "index.js", // there are more chunks but it should at least contain the entry point "manifest.json", "pages", "prerender-manifest.json", "routes-manifest.json" ]) ); // api pages should not be included in the default lambda expect(apiDirExists).toEqual(false); // Since en and nl are ultimately empty, those don't need to be here expect(pages).toEqual([ "_error.js", "catchall", "en", "fallback", "fallback-blocking", "nl", "no-fallback", "optional-catchall", "ssg.js", "ssr.js" ]); // These could be removed expect(enPages).toEqual(["fallback", "fallback-blocking", "no-fallback"]); expect(enFallbackPages).toEqual([]); expect(enFallbackBlockingPages).toEqual([]); expect(enNoFallbackPages).toEqual([]); expect(catchallPages).toEqual(["[...slug].js"]); expect(optionalCatchallPages).toEqual(["[[...slug]].js"]); expect(fallbackPages).toEqual(["[slug].js"]); expect(fallbackBlockingPages).toEqual(["[slug].js"]); expect(nofallbackPages).toEqual(["[slug].js"]); }); }); describe("Assets", () => { it("copies locale-specific asset files", async () => { //expect.assertions(9); // Root const preDataFiles = await fse.readdir( join(outputDir, `${ASSETS_DIR}/_next/data`) ); expect(preDataFiles).toEqual(["test-build-id"]); const nextDataFiles = await fse.readdir( join(outputDir, `${ASSETS_DIR}/_next/data/test-build-id`) ); expect(nextDataFiles).toEqual(["en", "en.json", "nl", "nl.json"]); // English const enNextDataFiles = await fse.readdir( join(outputDir, `${ASSETS_DIR}/_next/data/test-build-id/en`) ); expect(enNextDataFiles).toEqual([ "fallback", "fallback-blocking", "no-fallback", "ssg.json" ]); const enPageFiles = await fse.readdir( join(outputDir, `${ASSETS_DIR}/static-pages/test-build-id/en`) ); expect(enPageFiles).toEqual([ "404.html", "500.html", "fallback", "fallback-blocking", "no-fallback", "ssg.html", "static.html" ]); const enFallbackFiles = await fse.readdir( join(outputDir, `${ASSETS_DIR}/static-pages/test-build-id/en/fallback`) ); expect(enFallbackFiles).toEqual(["[slug].html", "a.html"]); const enFallbackBlockingFiles = await fse.readdir( join( outputDir, `${ASSETS_DIR}/static-pages/test-build-id/en/fallback-blocking` ) ); expect(enFallbackBlockingFiles).toEqual(["a.html"]); const enNoFallbackFiles = await fse.readdir( join( outputDir, `${ASSETS_DIR}/static-pages/test-build-id/en/no-fallback` ) ); expect(enNoFallbackFiles).toEqual(["a.html"]); // Dutch const nlNextDataFiles = await fse.readdir( join(outputDir, `${ASSETS_DIR}/_next/data/test-build-id/nl`) ); expect(nlNextDataFiles).toEqual(["ssg.json"]); const nlPageFiles = await fse.readdir( join(outputDir, `${ASSETS_DIR}/static-pages/test-build-id/nl`) ); expect(nlPageFiles).toEqual([ "404.html", "500.html", "fallback", "ssg.html", "static.html" ]); const nlFallbackFiles = await fse.readdir( join(outputDir, `${ASSETS_DIR}/static-pages/test-build-id/nl/fallback`) ); expect(nlFallbackFiles).toEqual(["[slug].html"]); }); }); });