UNPKG

@esmx/core

Version:

A high-performance microfrontend framework supporting Vue, React, Preact, Solid, and Svelte with SSR and Module Federation capabilities.

720 lines (719 loc) 26 kB
import path from "node:path"; import { describe, expect, it } from "vitest"; import { addPackageExportsToScopes, createDefaultExports, getEnvironmentExports, getEnvironmentImports, getEnvironmentScopes, getEnvironments, getLinks, parseModuleConfig, parsedExportValue, processExportArray, processObjectExport, processStringExport, resolveExportFile } from "./module-config.mjs"; describe("Module Config Parser", () => { describe("parseModuleConfig", () => { it("should parse basic module config with name and root", () => { const result = parseModuleConfig("test-module", "/test/root"); expect(result.name).toBe("test-module"); expect(result.root).toBe("/test/root"); expect(result.links).toBeDefined(); expect(result.environments).toBeDefined(); }); it("should handle empty config object", () => { const result = parseModuleConfig("test-module", "/test/root", {}); expect(result.name).toBe("test-module"); expect(result.root).toBe("/test/root"); }); it("should handle undefined config parameter", () => { const result = parseModuleConfig( "test-module", "/test/root", void 0 ); expect(result.name).toBe("test-module"); expect(result.root).toBe("/test/root"); }); it("should process links configuration", () => { const config = { links: { "custom-link": "/custom/path" } }; const result = parseModuleConfig( "test-module", "/test/root", config ); expect(result.links["custom-link"]).toBeDefined(); }); it("should generate client and server environments", () => { const result = parseModuleConfig("test-module", "/test/root"); expect(result.environments.client).toBeDefined(); expect(result.environments.server).toBeDefined(); }); it("should handle absolute and relative paths in links", () => { const config = { links: { absolute: "/absolute/path", relative: "relative/path" } }; const result = parseModuleConfig( "test-module", "/test/root", config ); expect(result.links.absolute.root).toBe("/absolute/path"); expect(result.links.relative.root).toBe("relative/path"); }); it("should maintain type safety across transformations", () => { const result = parseModuleConfig("test-module", "/test/root"); expect(typeof result.name).toBe("string"); expect(typeof result.root).toBe("string"); expect(typeof result.links).toBe("object"); expect(typeof result.environments).toBe("object"); }); }); describe("getLinks", () => { it("should create default link for module name", () => { const result = getLinks("test-module", "/test/root", {}); expect(result["test-module"]).toBeDefined(); expect(result["test-module"].name).toBe("test-module"); }); it("should process custom links configuration", () => { const config = { links: { "custom-link": "/custom/path" } }; const result = getLinks("test-module", "/test/root", config); expect(result["custom-link"]).toBeDefined(); expect(result["custom-link"].name).toBe("custom-link"); }); it("should handle empty links object", () => { const result = getLinks("test-module", "/test/root", {}); expect(Object.keys(result)).toHaveLength(1); expect(result["test-module"]).toBeDefined(); }); it("should resolve absolute paths correctly", () => { const config = { links: { absolute: "/absolute/path" } }; const result = getLinks("test-module", "/test/root", config); expect(result.absolute.root).toBe("/absolute/path"); }); it("should resolve relative paths from root", () => { const config = { links: { relative: "relative/path" } }; const result = getLinks("test-module", "/test/root", config); expect(result.relative.root).toBe("relative/path"); }); it("should generate client and server manifest paths", () => { const result = getLinks("test-module", "/test/root", {}); const link = result["test-module"]; const expectedRoot = path.resolve("/test/root", "dist"); expect(link.client).toBe(path.resolve(expectedRoot, "client")); expect(link.clientManifestJson).toBe( path.resolve(expectedRoot, "client/manifest.json") ); expect(link.server).toBe(path.resolve(expectedRoot, "server")); expect(link.serverManifestJson).toBe( path.resolve(expectedRoot, "server/manifest.json") ); }); it("should handle Windows path separators", () => { const result = getLinks("test-module", "C:\\test\\root", {}); const link = result["test-module"]; expect(link.client).toMatch(/[/\\]/); expect(link.server).toMatch(/[/\\]/); }); it("should handle Unix path separators", () => { const result = getLinks("test-module", "/test/root", {}); const link = result["test-module"]; const expectedRoot = path.resolve("/test/root", "dist"); expect(link.client).toBe(path.resolve(expectedRoot, "client")); expect(link.server).toBe(path.resolve(expectedRoot, "server")); }); it("should handle paths with special characters", () => { const result = getLinks("test-module", "/test/root@1.0.0", {}); const link = result["test-module"]; expect(link.root).toBe(path.resolve("/test/root@1.0.0", "dist")); }); it("should handle paths with spaces", () => { const result = getLinks( "test-module", "/test/root with spaces", {} ); const link = result["test-module"]; expect(link.root).toBe( path.resolve("/test/root with spaces", "dist") ); }); }); describe("Environment Import Functions", () => { describe("getEnvironmentImports", () => { it("should filter imports by environment", () => { const imports = { react: "react", vue: { client: "vue", server: "vue/server" } }; const clientResult = getEnvironmentImports("client", imports); const serverResult = getEnvironmentImports("server", imports); expect(clientResult.react).toBe("react"); expect(clientResult.vue).toBe("vue"); expect(serverResult.vue).toBe("vue/server"); }); it("should handle string import values", () => { const imports = { react: "react" }; const result = getEnvironmentImports("client", imports); expect(result.react).toBe("react"); }); it("should handle object import values with matching environment", () => { const imports = { vue: { client: "vue", server: "vue/server" } }; const result = getEnvironmentImports("client", imports); expect(result.vue).toBe("vue"); }); it("should skip imports when environment value is undefined", () => { const imports = { vue: { client: "vue", server: "vue/server" } }; const result = getEnvironmentImports("server", imports); expect(result.vue).toBe("vue/server"); }); it("should handle empty imports object", () => { const result = getEnvironmentImports("client", {}); expect(Object.keys(result)).toHaveLength(0); }); }); describe("getEnvironmentScopes", () => { it("should process scoped imports per environment", () => { const scopes = { utils: { lodash: "lodash", moment: { client: "moment", server: "moment/server" } } }; const result = getEnvironmentScopes("client", scopes); expect(result.utils.lodash).toBe("lodash"); expect(result.utils.moment).toBe("moment"); }); it("should handle empty scopes object", () => { const result = getEnvironmentScopes("client", {}); expect(Object.keys(result)).toHaveLength(0); }); }); describe("getEnvironments", () => { it("should combine imports, exports and scopes", () => { const config = { imports: { react: "react" }, scopes: { utils: { lodash: "lodash" } } }; const result = getEnvironments(config, "client", "test-module"); expect(result.imports.react).toBe("react"); expect(result.scopes.utils.lodash).toBe("lodash"); expect(result.exports).toBeDefined(); }); it("should preserve import mapping types", () => { const config = { imports: { react: "react" } }; const result = getEnvironments(config, "client", "test-module"); expect(typeof result.imports).toBe("object"); expect(typeof result.exports).toBe("object"); expect(typeof result.scopes).toBe("object"); }); it("should add pkg exports to scopes with empty string key", () => { const config = { exports: ["pkg:lodash", "pkg:react", "./src/component"] }; const result = getEnvironments(config, "client", "test-module"); const emptyScope = result.scopes[""]; expect(emptyScope).toBeDefined(); expect(emptyScope.lodash).toBe("test-module/lodash"); expect(emptyScope.react).toBe("test-module/react"); expect(emptyScope["./src/component"]).toBeUndefined(); }); it("should not override existing empty string scope", () => { const config = { exports: ["pkg:lodash"], scopes: { "": { existing: "existing-value" } } }; const result = getEnvironments(config, "client", "test-module"); const emptyScope = result.scopes[""]; expect(emptyScope).toBeDefined(); expect(emptyScope.existing).toBe("existing-value"); expect(emptyScope.lodash).toBe("test-module/lodash"); }); }); describe("addPackageExportsToScopes", () => { it("should create scopes for pkg exports", () => { const exports = { lodash: { name: "lodash", file: "lodash", pkg: true }, react: { name: "react", file: "react", pkg: true }, "./src/component": { name: "./src/component", file: "./src/component", pkg: false } }; const scopes = {}; const result = addPackageExportsToScopes( exports, scopes, "test-module" ); const emptyScope = result[""]; expect(emptyScope).toBeDefined(); expect(emptyScope.lodash).toBe("test-module/lodash"); expect(emptyScope.react).toBe("test-module/react"); expect(emptyScope["./src/component"]).toBeUndefined(); }); it("should not override existing empty string scope", () => { const exports = { lodash: { name: "lodash", file: "lodash", pkg: true } }; const scopes = { "": { existing: "existing-value" } }; const result = addPackageExportsToScopes( exports, scopes, "test-module" ); const emptyScope = result[""]; expect(emptyScope.existing).toBe("existing-value"); expect(emptyScope.lodash).toBe("test-module/lodash"); }); it("should handle empty exports", () => { const exports = {}; const scopes = {}; const result = addPackageExportsToScopes( exports, scopes, "test-module" ); expect(Object.keys(result)).toHaveLength(0); }); }); }); describe("Export Processing Functions", () => { describe("createDefaultExports", () => { it("should generate client default exports", () => { const result = createDefaultExports("client"); expect(result["src/entry.client"].file).toBe( "./src/entry.client" ); expect(result["src/entry.server"].file).toBe(""); }); it("should generate server default exports", () => { const result = createDefaultExports("server"); expect(result["src/entry.client"].file).toBe(""); expect(result["src/entry.server"].file).toBe( "./src/entry.server" ); }); it("should handle client environment switch case", () => { const result = createDefaultExports("client"); expect(result["src/entry.client"].file).toBe( "./src/entry.client" ); expect(result["src/entry.server"].file).toBe(""); }); it("should handle server environment switch case", () => { const result = createDefaultExports("server"); expect(result["src/entry.client"].file).toBe(""); expect(result["src/entry.server"].file).toBe( "./src/entry.server" ); }); }); describe("processStringExport", () => { it("should parse simple string export", () => { const result = processStringExport("./src/component"); expect(result["./src/component"]).toBeDefined(); expect(result["./src/component"].file).toBe("./src/component"); }); }); describe("processObjectExport", () => { it("should handle environment-specific exports", () => { const exportObject = { "./src/component": { client: "./src/component.client", server: "./src/component.server" } }; const result = processObjectExport(exportObject, "client"); expect(result["./src/component"].file).toBe( "./src/component.client" ); }); it("should process mixed string and object exports", () => { const exportObject = { "./src/utils": "./src/utils", "./src/component": { client: "./src/component.client", server: "./src/component.server" } }; const result = processObjectExport(exportObject, "client"); expect(result["./src/utils"].file).toBe("./src/utils"); expect(result["./src/component"].file).toBe( "./src/component.client" ); }); it("should handle string config values in export object", () => { const exportObject = { "./src/utils": "./src/utils" }; const result = processObjectExport(exportObject, "client"); expect(result["./src/utils"].file).toBe("./src/utils"); }); it("should handle object config values in export object", () => { const exportObject = { "./src/component": { client: "./src/component.client", server: "./src/component.server" } }; const result = processObjectExport(exportObject, "client"); expect(result["./src/component"].file).toBe( "./src/component.client" ); }); it("should handle empty export object", () => { const result = processObjectExport({}, "client"); expect(Object.keys(result)).toHaveLength(0); }); }); describe("resolveExportFile", () => { it("should handle string config", () => { const result = resolveExportFile( "./src/component", "client", "component" ); expect(result).toBe("./src/component"); }); it("should return string config directly", () => { const result = resolveExportFile( "./src/component", "client", "component" ); expect(result).toBe("./src/component"); }); it("should resolve environment-specific paths", () => { const config = { client: "./src/component.client", server: "./src/component.server" }; const result = resolveExportFile(config, "client", "component"); expect(result).toBe("./src/component.client"); }); it("should return empty string when value is false", () => { const config = { client: false, server: "./src/component.server" }; const result = resolveExportFile(config, "client", "component"); expect(result).toBe(""); }); it("should return name when value is empty string", () => { const config = { client: "", server: "./src/component.server" }; const result = resolveExportFile(config, "client", "component"); expect(result).toBe("component"); }); it("should return environment value when it's a string", () => { const config = { client: "./src/component.client", server: "./src/component.server" }; const result = resolveExportFile(config, "client", "component"); expect(result).toBe("./src/component.client"); }); it("should return name when environment value is undefined", () => { const config = { client: "./src/component.client" }; const result = resolveExportFile(config, "server", "component"); expect(result).toBe("component"); }); it("should handle invalid config types gracefully", () => { const result = resolveExportFile( {}, "client", "component" ); expect(result).toBe("component"); }); }); describe("processExportArray", () => { it("should combine multiple export configurations", () => { const exportArray = ["./src/component1", "./src/component2"]; const result = processExportArray(exportArray, "client"); expect(result["./src/component1"]).toBeDefined(); expect(result["./src/component2"]).toBeDefined(); }); it("should handle string items in export array", () => { const exportArray = ["./src/component"]; const result = processExportArray(exportArray, "client"); expect(result["./src/component"]).toBeDefined(); }); it("should handle object items in export array", () => { const exportArray = [ { "./src/component": "./src/component" } ]; const result = processExportArray(exportArray, "client"); expect(result["./src/component"]).toBeDefined(); }); it("should handle empty export array", () => { const result = processExportArray([], "client"); expect(Object.keys(result)).toHaveLength(0); }); }); describe("getEnvironmentExports", () => { it("should merge default and user exports", () => { const config = { exports: ["./src/custom"] }; const result = getEnvironmentExports(config, "client"); expect(result["src/entry.client"]).toBeDefined(); expect(result["./src/custom"]).toBeDefined(); }); it("should handle config without exports property", () => { const result = getEnvironmentExports({}, "client"); expect(result["src/entry.client"]).toBeDefined(); }); it("should handle config with exports property", () => { const config = { exports: ["./src/custom"] }; const result = getEnvironmentExports(config, "client"); expect(result["./src/custom"]).toBeDefined(); }); }); }); describe("parsedExportValue", () => { it("should handle pkg: prefixed exports", () => { const result = parsedExportValue("pkg:lodash"); expect(result.name).toBe("lodash"); expect(result.pkg).toBe(true); expect(result.file).toBe("lodash"); }); it("should handle root: prefixed exports", () => { const result = parsedExportValue("root:src/component.tsx"); expect(result.name).toBe("src/component"); expect(result.pkg).toBe(false); expect(result.file).toBe("./src/component.tsx"); }); it("should process regular file exports", () => { const result = parsedExportValue("./src/component"); expect(result.name).toBe("./src/component"); expect(result.pkg).toBe(false); expect(result.file).toBe("./src/component"); }); it("should handle pkg: prefixed values correctly", () => { const result = parsedExportValue("pkg:@scope/package"); expect(result.name).toBe("@scope/package"); expect(result.pkg).toBe(true); expect(result.file).toBe("@scope/package"); }); it("should handle root: prefixed values with extension removal", () => { const result = parsedExportValue("root:src/component.tsx"); expect(result.name).toBe("src/component"); expect(result.pkg).toBe(false); expect(result.file).toBe("./src/component.tsx"); }); it("should handle root: prefixed values without extensions", () => { const result = parsedExportValue("root:src/utils"); expect(result.name).toBe("src/utils"); expect(result.pkg).toBe(false); expect(result.file).toBe("./src/utils"); }); it("should handle regular values without prefixes", () => { const result = parsedExportValue("./src/component.tsx"); expect(result.name).toBe("./src/component.tsx"); expect(result.pkg).toBe(false); expect(result.file).toBe("./src/component.tsx"); }); it("should preserve file extensions for non-root exports", () => { const result = parsedExportValue("./src/component.tsx"); expect(result.file).toBe("./src/component.tsx"); }); it("should handle malformed pkg: values", () => { const result = parsedExportValue("pkg:"); expect(result.name).toBe(""); expect(result.pkg).toBe(true); expect(result.file).toBe(""); }); it("should handle malformed root: values", () => { const result = parsedExportValue("root:"); expect(result.name).toBe(""); expect(result.pkg).toBe(false); expect(result.file).toBe("./"); }); it("should handle file extensions correctly in root: prefix", () => { const result = parsedExportValue("root:src/component.tsx"); expect(result.name).toBe("src/component"); expect(result.file).toBe("./src/component.tsx"); }); it("should handle nested paths in pkg: prefix", () => { const result = parsedExportValue("pkg:@scope/package/subpath"); expect(result.name).toBe("@scope/package/subpath"); expect(result.pkg).toBe(true); expect(result.file).toBe("@scope/package/subpath"); }); }); describe("Integration Tests", () => { it("should work end-to-end with complex configuration", () => { const config = { links: { shared: "/shared/modules" }, imports: { react: "react", vue: { client: "vue", server: "vue/server" } }, scopes: { utils: { lodash: "lodash" } }, exports: [ "./src/component", { "./src/utils": { client: "./src/utils.client", server: "./src/utils.server" } } ] }; const result = parseModuleConfig( "test-module", "/test/root", config ); expect(result.name).toBe("test-module"); expect(result.links.shared).toBeDefined(); expect(result.environments.client.imports.react).toBe("react"); expect(result.environments.client.imports.vue).toBe("vue"); expect(result.environments.server.imports.vue).toBe("vue/server"); }); it("should correctly filter environment-specific exports", () => { const config = { exports: [ { "./src/component": { client: "./src/component.client", server: "./src/component.server" } } ] }; const clientResult = parseModuleConfig( "test-module", "/test/root", config ); const serverResult = parseModuleConfig( "test-module", "/test/root", config ); expect( clientResult.environments.client.exports["./src/component"].file ).toBe("./src/component.client"); expect( serverResult.environments.server.exports["./src/component"].file ).toBe("./src/component.server"); }); it("should maintain cross-environment consistency", () => { const result = parseModuleConfig("test-module", "/test/root"); expect( result.environments.client.exports["src/entry.client"].file ).toBe("./src/entry.client"); expect( result.environments.server.exports["src/entry.server"].file ).toBe("./src/entry.server"); }); it("should work with path resolution across different environments", () => { const config = { links: { relative: "relative/path" } }; const result = parseModuleConfig( "test-module", "/test/root", config ); expect(result.links.relative.root).toBe("relative/path"); }); }); describe("Error Handling", () => { it("should handle invalid config types gracefully in resolveExportFile", () => { const result = resolveExportFile({}, "client", "component"); expect(result).toBe("component"); }); it("should handle malformed pkg: values in parsedExportValue", () => { const result = parsedExportValue("pkg:"); expect(result.name).toBe(""); expect(result.pkg).toBe(true); }); it("should handle malformed root: values in parsedExportValue", () => { const result = parsedExportValue("root:"); expect(result.name).toBe(""); expect(result.pkg).toBe(false); }); }); });