diff --git a/test/basic.test.ts b/test/basic.test.ts deleted file mode 100644 index 215a0be..0000000 --- a/test/basic.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { fileURLToPath } from "node:url"; -import { setup, $fetch } from "@nuxt/test-utils"; - -describe("ssr", async () => { - await setup({ - rootDir: fileURLToPath(new URL("./fixtures/basic", import.meta.url)), - }); - - it("renders the index page", async () => { - // Get response to a server-rendered page with `$fetch`. - const html = await $fetch("/"); - expect(html).toContain("
basic
"); - }); -}); diff --git a/test/build.test.ts b/test/build.test.ts new file mode 100644 index 0000000..9e4d91c --- /dev/null +++ b/test/build.test.ts @@ -0,0 +1,36 @@ +import { describe, it, expect } from "vitest"; +import { fileURLToPath } from "node:url"; +import { buildNuxtApp, getOutputFiles } from "@/test/utils"; + +const ROOT_DIR = fileURLToPath(new URL("./fixtures/basic", import.meta.url)); + +//TODO: Explore how we can test the generated HTML file +// Need also to investigate if the programmatic build has a different output than the CLI build +describe("nuxt-singlefile", async () => { + await buildNuxtApp({ + rootDir: ROOT_DIR, + }); + + it("builds without throwing an error", async () => { + expect(true).toBe(true); + }); + it("generates an html file", async () => { + const outputFiles = await getOutputFiles({ + rootDir: ROOT_DIR, + }); + + expect(outputFiles).toSatisfy((files: string[]) => + files.some((file) => file.includes("index.html")), + ); + }); + it("generates only one html file", async () => { + const outputFiles = await getOutputFiles({ + rootDir: ROOT_DIR, + }); + + expect(outputFiles).toSatisfy( + (files: string[]) => + files.filter((file) => file.includes(".html")).length === 1, + ); + }); +}); diff --git a/test/src/runtime/utils/generateInlinedStringForTag.spec.ts b/test/src/runtime/utils/generateInlinedStringForTag.spec.ts new file mode 100644 index 0000000..841a9dd --- /dev/null +++ b/test/src/runtime/utils/generateInlinedStringForTag.spec.ts @@ -0,0 +1,22 @@ +import { describe, it, expect } from "vitest"; +import { generateInlinedStringForTag } from "@/src/runtime/utils/generateInlinedStringForTag"; + +describe("generateInlinedStringForTag", () => { + it("should generate an inlined script tag with the given content", () => { + const content = "console.log('Hello, world!');"; + const type = "script"; + + const result = generateInlinedStringForTag(content, type); + + expect(result).toBe(``); + }); + + it("should generate an inlined style tag with the given content", () => { + const content = "body { background-color: #f0f0f0; }"; + const type = "style"; + + const result = generateInlinedStringForTag(content, type); + + expect(result).toBe(``); + }); +}); diff --git a/test/src/runtime/utils/generateSingleFileRenderContext.spec.ts b/test/src/runtime/utils/generateSingleFileRenderContext.spec.ts new file mode 100644 index 0000000..7558ac0 --- /dev/null +++ b/test/src/runtime/utils/generateSingleFileRenderContext.spec.ts @@ -0,0 +1,130 @@ +import { describe, it, expect, vi, afterEach } from "vitest"; +import { generateSingleFileRenderContext } from "@/src/runtime/utils/generateSingleFileRenderContext"; +import type { NuxtRenderHTMLContext } from "nuxt/dist/core/runtime/nitro/renderer"; + +const CORRECT_PATH_TO_FILES = "/path/to/files"; +const FIRST_JS_FILE = "test-file.js"; +const SECOND_JS_FILE = "index2.js"; +const FIRST_CSS_FILE = "index.css"; +const SECOND_CSS_FILE = "test-file2.css"; + +const BASIC_HTML_RENDER_CONTEXT: NuxtRenderHTMLContext = { + island: false, + htmlAttrs: [], + head: [ + ``, + ``, + ], + bodyAttrs: [], + bodyPrepend: [], + body: ["
"], + bodyAppend: [ + '\n', + ], +}; + +describe("generateSingleFileRenderContext", () => { + vi.mock("node:fs/promises", async () => ({ + ...(await vi.importActual( + "node:fs/promises", + )), + readFile: vi.fn(async (...params: Array) => + params[0].startsWith(CORRECT_PATH_TO_FILES) + ? Promise.resolve(`mocked file content for ${params[0]}`) + : Promise.reject(`invalid path: ${params[0]}`), + ), + })); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("should generate a new render context with inlined head content", async () => { + const baseHtmlRenderContext = BASIC_HTML_RENDER_CONTEXT; + + const result = await generateSingleFileRenderContext( + baseHtmlRenderContext, + CORRECT_PATH_TO_FILES, + ); + + expect(result).toEqual({ + ...baseHtmlRenderContext, + head: expect.arrayContaining([ + expect.stringContaining(``, + ``, + ``, + ``, + ], + }; + + const result = await generateSingleFileRenderContext( + baseHtmlRenderContext, + CORRECT_PATH_TO_FILES, + ); + + expect(result).toEqual({ + ...baseHtmlRenderContext, + head: expect.arrayContaining([ + '', + '', + expect.stringContaining(``; + const pathToFiles = CORRECT_PATH_TO_FILES; + const type = "script"; + + const result = await getInlineContentForTagsType({ + baseHtml, + pathToFiles, + type, + }); + + expect(result).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + tagString: baseHtml, + content: expect.stringContaining(`/${FIRST_JS_FILE}`), + type: type, + }), + ]), + ); + }); + + it("should return the correct inline content for multiple script tags", async () => { + const baseHtml = ` + + + `; + const pathToFiles = CORRECT_PATH_TO_FILES; + const type = "script"; + + const result = await getInlineContentForTagsType({ + baseHtml, + pathToFiles, + type, + }); + + expect(result).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + tagString: ``, + content: expect.stringContaining(`/${FIRST_JS_FILE}`), + type: type, + }), + expect.objectContaining({ + tagString: ``, + content: expect.stringContaining(`/${SECOND_JS_FILE}`), + type: type, + }), + ]), + ); + }); + + it("should return the correct inline content for a single style tag", async () => { + const baseHtml = ``; + const pathToFiles = CORRECT_PATH_TO_FILES; + const type = "style"; + + const result = await getInlineContentForTagsType({ + baseHtml, + pathToFiles, + type, + }); + + expect(result).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + tagString: baseHtml, + content: expect.stringContaining(`/${FIRST_CSS_FILE}`), + type: type, + }), + ]), + ); + }); + + it("should return the correct inline content for multiple style tags", async () => { + const baseHtml = ` + + + `; + const pathToFiles = CORRECT_PATH_TO_FILES; + const type = "style"; + + const result = await getInlineContentForTagsType({ + baseHtml, + pathToFiles, + type, + }); + + expect(result).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + tagString: ``, + content: expect.stringContaining(`/${FIRST_CSS_FILE}`), + type: type, + }), + expect.objectContaining({ + tagString: ``, + content: expect.stringContaining(`/${SECOND_CSS_FILE}`), + type: type, + }), + ]), + ); + }); + + it("should return the correct inline content for mixed tags", async () => { + const baseHtml = ` + + + + + + + `; + const pathToFiles = CORRECT_PATH_TO_FILES; + const type = "style"; + + const result = await getInlineContentForTagsType({ + baseHtml, + pathToFiles, + type, + }); + + expect(result).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + tagString: ``, + content: expect.stringContaining(`/${FIRST_CSS_FILE}`), + type: type, + }), + expect.objectContaining({ + tagString: ``, + content: expect.stringContaining(`/${SECOND_CSS_FILE}`), + type: type, + }), + ]), + ); + }); + + it("should return an empty array if no tags of the given type are found", async () => { + const baseHtml = ``; + const pathToFiles = CORRECT_PATH_TO_FILES; + const type = "script"; + + const result = await getInlineContentForTagsType({ + baseHtml, + pathToFiles, + type, + }); + + expect(result).toEqual([]); + }); + + it("should throw an error if the path to files is invalid", async () => { + const baseHtml = ``; + const pathToFiles = "/invalid/path"; + const type = "script"; + + await expect( + getInlineContentForTagsType({ baseHtml, pathToFiles, type }), + ).rejects.toThrow(expect.stringContaining(pathToFiles)); + }); + + it("should throw an error if the type is invalid", async () => { + const baseHtml = ``; + const pathToFiles = CORRECT_PATH_TO_FILES; + const type = "invalidType"; + + await expect( + // @ts-expect-error + getInlineContentForTagsType({ baseHtml, pathToFiles, type }), + ).rejects.toThrow(`invalid type: ${type}`); + }); +}); diff --git a/test/src/runtime/utils/getInlinedHeadContent.spec.ts b/test/src/runtime/utils/getInlinedHeadContent.spec.ts new file mode 100644 index 0000000..dc0708a --- /dev/null +++ b/test/src/runtime/utils/getInlinedHeadContent.spec.ts @@ -0,0 +1,106 @@ +import { describe, it, expect, vi, afterEach } from "vitest"; +import { getInlinedHeadContent } from "@/src/runtime/utils/getInlinedHeadContent"; + +const CORRECT_PATH_TO_FILES = "/path/to/files"; +const FIRST_JS_FILE = "test-file.js"; +const SECOND_JS_FILE = "index2.js"; +const FIRST_CSS_FILE = "index.css"; +const SECOND_CSS_FILE = "test-file2.css"; + +describe("getInlinedHeadContent", () => { + vi.mock("node:fs/promises", async () => ({ + ...(await vi.importActual( + "node:fs/promises", + )), + readFile: vi.fn(async (...params: Array) => + params[0].startsWith(CORRECT_PATH_TO_FILES) + ? Promise.resolve(`mocked file content for ${params[0]}`) + : Promise.reject(`invalid path: ${params[0]}`), + ), + })); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("should inline the content of each tag type in the head HTML", async () => { + const head = [ + ``, + ``, + ]; + + const pathToFiles = CORRECT_PATH_TO_FILES; + + const result = await getInlinedHeadContent(head, pathToFiles); + + expect(result).toEqual( + expect.arrayContaining([ + expect.stringContaining(``, + ``, + ``, + ``, + ]; + const pathToFiles = CORRECT_PATH_TO_FILES; + + const result = await getInlinedHeadContent(head, pathToFiles); + + expect(result).toEqual( + expect.arrayContaining([ + '', + '', + expect.stringContaining(``, + ``, + ]; + const pathToFiles = "/invalid/path"; + + await expect(getInlinedHeadContent(head, pathToFiles)).rejects.toThrow( + expect.stringContaining(pathToFiles), + ); + }); +}); diff --git a/test/utils/index.ts b/test/utils/index.ts new file mode 100644 index 0000000..4fcfa77 --- /dev/null +++ b/test/utils/index.ts @@ -0,0 +1,22 @@ +import { resolve } from "node:path"; +import { loadNuxt, buildNuxt } from "@nuxt/kit"; +import { globby } from "globby"; +import { memoize } from "lodash-es"; + +export const buildNuxtApp = async ({ rootDir }: { rootDir: string }) => { + const nuxt = await loadNuxt({ + cwd: rootDir, + ready: true, + }); + await buildNuxt(nuxt); + return nuxt; +}; + +export const rawGetOutputFiles = async ({ rootDir }: { rootDir: string }) => { + const outputFilesPath = resolve(rootDir, ".output"); + const outputFiles = await globby(outputFilesPath); + + return outputFiles; +}; + +export const getOutputFiles = memoize(rawGetOutputFiles);