vite-plugin-symfony
Version:
A Vite plugin to integrate easily Vite in your Symfony application
580 lines (553 loc) • 16.5 kB
text/typescript
import { describe, it, expect, test } from "vitest";
import {
getLegacyName,
normalizePath,
getFileInfos,
getInputRelPath,
prepareRollupInputs,
isSubdirectory,
parseVersionString,
resolveDevServerUrl,
isCssEntryPoint,
resolveUserExternal,
normalizeConfig,
trimSlashes,
} from "./utils";
import { resolvePluginEntrypointsOptions } from "./pluginOptions";
import { OutputChunk, OutputAsset, NormalizedOutputOptions } from "rollup";
import {
asyncDepChunk,
indexCss,
legacyPolyfills,
logoPng,
pageAssets,
themeCss,
welcomeJs,
welcomeLegacyJs,
} from "~tests/mocks";
import { resolveConfig, type ResolvedConfig } from "vite";
import { VitePluginSymfonyOptions } from "~/types";
import type { RenderedChunk } from "rollup";
const viteBaseConfig = {
root: "/home/me/project-dir",
base: "/build/",
} as unknown as ResolvedConfig;
describe("isCssEntryPoint", () => {
it.each([
[
{
isEntry: false,
} as unknown as RenderedChunk,
false,
],
[
{
isEntry: true,
modules: {
"/path/to/module.js": {},
"/path/to/style.css": {},
},
} as unknown as RenderedChunk,
false,
],
[
{
isEntry: true,
modules: {
"/path/to/style.module.css": {},
},
} as unknown as RenderedChunk,
false,
],
[
{
// original entrypoint is wrapped into a js file
fileName: "assets/theme-!~{001}~.js",
facadeModuleId: "/path/to/assets/theme.scss",
isEntry: true,
modules: {
// original entrypoint
"/path/to/assets/theme.scss": {},
},
viteMetadata: {
// Set of relative paths of generated css files
importedCss: new Set(["assets/theme-hIRg7xK2.css"]),
},
} as unknown as RenderedChunk,
true,
],
])("find when entrypoint is a pure css file", (chunk: RenderedChunk, expectedValue: boolean) => {
expect(isCssEntryPoint(chunk)).toBe(expectedValue);
});
});
describe("parseVersionString", () => {
it("basic", () => {
expect(parseVersionString("1.2.3")).toEqual(["1.2.3", 1, 2, 3]);
});
it("return usable version if uncommon string", () => {
expect(parseVersionString("1.2.3-dev")).toEqual(["1.2.3-dev", 1, 2, 3]);
expect(parseVersionString("1.2")).toEqual(["1.2", 1, 2, 0]);
expect(parseVersionString("1")).toEqual(["1", 1, 0, 0]);
expect(parseVersionString("1-dev")).toEqual(["1-dev", 1, 0, 0]);
});
});
describe("normalizePath", () => {
it("normalize correctly path", () => {
expect(normalizePath("path//to/deep/../file.ts")).toBe("path/to/file.ts");
});
it("keep the path unchanged on UNIX", () => {
expect(normalizePath("path/to/file.ts")).toBe("path/to/file.ts");
});
});
describe("getLegacyName", () => {
it("suffix pathname with -legacy before extension", () => {
expect(getLegacyName("assets/page/assets/index.js")).toBe("assets/page/assets/index-legacy.js");
});
});
/**
* {
viteConfig: InlineConfig,
isIPv4: boolean,
pluginOptions: Partial<VitePluginSymfonyOptions>,
expectedUrl: string,
}
*/
describe("resolveDevServerUrl", () => {
it.each([
{
message: "resolve correctly default config",
viteConfig: {},
isIPv4: true,
pluginOptions: {},
expectedUrl: "http://127.0.0.1:5173",
},
{
message: "resolve host priority 1",
viteConfig: {},
isIPv4: true,
pluginOptions: {
originOverride: "<override-all>",
viteDevServerHostname: "<less-priority>",
},
expectedUrl: "<override-all>",
},
{
message: "resolve host priority 2",
viteConfig: {
server: {
hmr: {
host: "hmr-host",
},
host: "server-host",
},
},
isIPv4: true,
pluginOptions: {
viteDevServerHostname: "plugin-host",
},
expectedUrl: "http://hmr-host:5173",
},
{
message: "resolve host priority 3 (use case docker)",
viteConfig: {
server: {
host: "0.0.0.0.",
},
},
isIPv4: true,
pluginOptions: {
viteDevServerHostname: "plugin-host",
},
expectedUrl: "http://plugin-host:5173",
},
{
message: "resolve host priority 4",
viteConfig: {
server: {
host: "server-host",
},
},
isIPv4: true,
pluginOptions: {},
expectedUrl: "http://server-host:5173",
},
{
message: "resolve host priority 5",
viteConfig: {},
isIPv4: false,
pluginOptions: {},
expectedUrl: "http://[::1]:5173",
},
])("$message", async ({ viteConfig, isIPv4, pluginOptions, expectedUrl }) => {
const address = isIPv4
? {
family: "IPv4",
address: "127.0.0.1",
port: 5173,
}
: {
family: "IPv6",
address: "::1",
port: 5173,
};
const viteResolvedConfig = await resolveConfig(viteConfig, "serve");
const pluginConfig = resolvePluginEntrypointsOptions(pluginOptions);
expect(resolveDevServerUrl(address, viteResolvedConfig, pluginConfig)).toBe(expectedUrl);
});
});
describe("getFileInfos", () => {
it("parse correctly an output", () => {
expect(getFileInfos(asyncDepChunk, "assets/lib/async-dep.js", { sriAlgorithm: false } as VitePluginSymfonyOptions))
.toMatchInlineSnapshot(`
{
"assets": [],
"css": [],
"dynamic": [],
"hash": null,
"imports": [],
"inputRelPath": "assets/lib/async-dep.js",
"js": [
"assets/async-dep-e2ac9f96.js",
],
"outputRelPath": "assets/async-dep-e2ac9f96.js",
"preload": [],
"type": "js",
}
`);
expect(getFileInfos(indexCss, "_assets/index-aa7c8190.css", { sriAlgorithm: false } as VitePluginSymfonyOptions))
.toMatchInlineSnapshot(`
{
"css": [
"assets/index-aa7c8190.css",
],
"hash": null,
"inputRelPath": "_assets/index-aa7c8190.css",
"outputRelPath": "assets/index-aa7c8190.css",
"type": "css",
}
`);
expect(getFileInfos(themeCss, "assets/theme.scss", { sriAlgorithm: false } as VitePluginSymfonyOptions))
.toMatchInlineSnapshot(`
{
"css": [
"assets/theme-44b5be96.css",
],
"hash": null,
"inputRelPath": "assets/theme.scss",
"outputRelPath": "assets/theme-44b5be96.css",
"type": "css",
}
`);
expect(getFileInfos(logoPng, "_assets/logo-d015cc3f.png", { sriAlgorithm: false } as VitePluginSymfonyOptions))
.toMatchInlineSnapshot(`
{
"hash": null,
"inputRelPath": "_assets/logo-d015cc3f.png",
"outputRelPath": "assets/logo-d015cc3f.png",
"type": "asset",
}
`);
expect(getFileInfos(welcomeJs, "assets/page/welcome/index.js", { sriAlgorithm: false } as VitePluginSymfonyOptions))
.toMatchInlineSnapshot(`
{
"assets": [],
"css": [],
"dynamic": [],
"hash": null,
"imports": [],
"inputRelPath": "assets/page/welcome/index.js",
"js": [
"assets/welcome-1e67239d.js",
],
"outputRelPath": "assets/welcome-1e67239d.js",
"preload": [],
"type": "js",
}
`);
expect(
getFileInfos(welcomeJs, "assets/page/welcome/index.js", { sriAlgorithm: "sha256" } as VitePluginSymfonyOptions),
).toMatchInlineSnapshot(`
{
"assets": [],
"css": [],
"dynamic": [],
"hash": "sha256-w+Sit18/MC+LC1iX8MrNapOiCQ8wbPX8Rb6ErbfDX1Q=",
"imports": [],
"inputRelPath": "assets/page/welcome/index.js",
"js": [
"assets/welcome-1e67239d.js",
],
"outputRelPath": "assets/welcome-1e67239d.js",
"preload": [],
"type": "js",
}
`);
expect(getFileInfos(pageAssets, "assets/page/assets/index.js", { sriAlgorithm: false } as VitePluginSymfonyOptions))
.toMatchInlineSnapshot(`
{
"assets": [
"assets/logo-d015cc3f.png",
],
"css": [
"assets/index-aa7c8190.css",
],
"dynamic": [],
"hash": null,
"imports": [],
"inputRelPath": "assets/page/assets/index.js",
"js": [
"assets/pageAssets-05cfe79c.js",
],
"outputRelPath": "assets/pageAssets-05cfe79c.js",
"preload": [],
"type": "js",
}
`);
expect(
getFileInfos(welcomeLegacyJs, "assets/page/welcome/index-legacy.js", {
sriAlgorithm: false,
} as VitePluginSymfonyOptions),
).toMatchInlineSnapshot(`
{
"assets": [],
"css": [],
"dynamic": [],
"hash": null,
"imports": [],
"inputRelPath": "assets/page/welcome/index-legacy.js",
"js": [
"assets/welcome-legacy-64979d13.js",
],
"outputRelPath": "assets/welcome-legacy-64979d13.js",
"preload": [],
"type": "js",
}
`);
expect(getFileInfos(legacyPolyfills, "vite/legacy-polyfills", { sriAlgorithm: false } as VitePluginSymfonyOptions))
.toMatchInlineSnapshot(`
{
"assets": [],
"css": [],
"dynamic": [],
"hash": null,
"imports": [],
"inputRelPath": "vite/legacy-polyfills",
"js": [
"assets/polyfills-legacy-40963d34.js",
],
"outputRelPath": "assets/polyfills-legacy-40963d34.js",
"preload": [],
"type": "js",
}
`);
});
});
describe("prepareRollupInputs", () => {
it("prepare inputs", () => {
expect(
prepareRollupInputs({
...viteBaseConfig,
build: {
rollupOptions: {
input: {
app: "./path/to/filename.ts",
theme: "./other/place/to/theme.scss",
},
},
},
} as unknown as ResolvedConfig),
).toMatchInlineSnapshot(`
{
"app": {
"inputRelPath": "path/to/filename.ts",
"inputType": "js",
},
"theme": {
"inputRelPath": "other/place/to/theme.scss",
"inputType": "css",
},
}
`);
});
});
describe("getInputRelPath", () => {
it("generate Correct path", () => {
expect(
getInputRelPath(
{
type: "asset",
fileName: "theme.css",
} as OutputAsset,
{ format: "es" } as NormalizedOutputOptions,
viteBaseConfig,
),
).toBe("_theme.css");
expect(
getInputRelPath(
{
type: "chunk",
facadeModuleId: "/home/me/project-dir/assets/page/welcome/index.js",
name: "welcome",
} as OutputChunk,
{ format: "es" } as NormalizedOutputOptions,
viteBaseConfig,
),
).toBe("assets/page/welcome/index.js");
expect(
getInputRelPath(
{
type: "chunk",
facadeModuleId: "/home/me/project-dir/assets/page/welcome/index.js",
name: "welcome",
} as OutputChunk,
{ format: "system" } as NormalizedOutputOptions,
viteBaseConfig,
),
).toBe("assets/page/welcome/index-legacy.js");
});
it("generate correct polyfills path", () => {
expect(
getInputRelPath(
{
type: "chunk",
facadeModuleId: "\0vite/legacy-polyfills",
name: "polyfills",
fileName: "polyfills-legacy.js"
} as OutputChunk,
{ format: "system" } as NormalizedOutputOptions,
viteBaseConfig
)
).toBe("vite/legacy-polyfills-legacy")
expect(
getInputRelPath(
{
type: "chunk",
facadeModuleId: "\0vite/legacy-polyfills",
name: "polyfills",
fileName: "polyfills.js"
} as OutputChunk,
{ format: "system" } as NormalizedOutputOptions,
viteBaseConfig
)
).toBe("vite/legacy-polyfills")
})
});
describe("isAncestorDir", () => {
it("subdirectory is a subdirectory", () => {
expect(isSubdirectory("/projects/vite-project", "/projects/vite-project/public")).toBe(true);
});
it("same folder is not a subdirectory", () => {
expect(isSubdirectory("/projects/vite-project", "/projects/vite-project")).toBe(false);
});
it("sibling folder is not a subdirectory", () => {
expect(isSubdirectory("/projects/vite-project", "/projects/symfony-project")).toBe(false);
});
it("sibling folder starting with same name is not a subdirectory", () => {
expect(isSubdirectory("/projects/vite-project", "/projects/vite-project-2")).toBe(false);
});
it("traversing up the tree and into a sibling folder is not a subdirectory", () => {
expect(isSubdirectory("/projects/vite-project", "/projects/vite-project/../react-project")).toBe(false);
});
it("unnormalized path in project folder: is a subdirectory", () => {
expect(isSubdirectory("/projects/vite-project", "/projects/vite-project/./public")).toBe(true);
});
it("unnormalized path with path traversal into subdirectory is a subdirectory", () => {
expect(isSubdirectory("/vite-project/../projects", "/projects/vite-project")).toBe(true);
});
it("unnormalized path relative to current directory: is not a subdirectory", () => {
expect(isSubdirectory("/projects/vite-project", "./vite-project")).toBe(false);
});
});
describe("resolveUserExternal()", () => {
test.each([
{
external: ["leaflet"],
id: "leaflet",
expectedValue: true,
},
{
external: ["other", "leaflet"],
id: "leaflet",
expectedValue: true,
},
{
external: [/node_modules/, "leaflet"],
id: "leaflet",
expectedValue: true,
},
{
external: ["other"],
id: "leaflet",
expectedValue: false,
},
{
external: [],
id: "leaflet",
expectedValue: false,
},
])("detect string as external dependency", ({ external, id, expectedValue }) => {
expect(resolveUserExternal(external, id, "root", false)).toBe(expectedValue);
});
test.each([
{
external: [/leaflet/],
id: "leaflet-draw",
expectedValue: true,
},
])("detect regex as external dependency", ({ external, id, expectedValue }) => {
expect(resolveUserExternal(external, id, "root", false)).toBe(expectedValue);
});
test.each([
{
external: (id: string) => {
return id === "leaflet" ? true : false;
},
id: "leaflet",
expectedValue: true,
},
])("detect callback as external dependency", ({ external, id, expectedValue }) => {
expect(resolveUserExternal(external, id, "root", false)).toBe(expectedValue);
});
});
describe("normalizeConfig", () => {
test.each([
{
resolvedConfig: {
plugins: [{ name: "vite:infos" }, { name: "other" }],
},
expectedValue: { plugins: ["vite:infos", "other"] },
},
{
resolvedConfig: {
func1() {},
foo: {
bar: "baz",
func2() {},
},
},
expectedValue: { foo: { bar: "baz" } },
},
])("pluginsConfigSimplification", ({ resolvedConfig, expectedValue }) => {
const normalizedConfig = normalizeConfig(resolvedConfig as any as ResolvedConfig);
expect(JSON.parse(normalizedConfig)).toMatchObject(expectedValue);
});
});
describe("trimSlashes", () => {
test("should remove slashes at the beginning and end of the string", () => {
expect(trimSlashes("/example/path/")).toBe("example/path");
expect(trimSlashes("/example/path")).toBe("example/path");
expect(trimSlashes("example/path/")).toBe("example/path");
expect(trimSlashes("example/path")).toBe("example/path");
});
test("should handle strings with only slashes", () => {
expect(trimSlashes("/")).toBe("");
expect(trimSlashes("//")).toBe("");
expect(trimSlashes("///")).toBe("");
});
test("should return the same string if there are no slashes at the beginning or end", () => {
expect(trimSlashes("example/path")).toBe("example/path");
});
test("should handle empty strings", () => {
expect(trimSlashes("")).toBe("");
});
});