import ts from "typescript"; /* eslint-disable security/detect-non-literal-fs-filename */ import assert from "node:assert/strict"; import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterEach, describe, it } from "node:test"; import { normalizeDeclarationImports } from "./normalizeDeclarationImportsPlugin"; describe("normalizeDeclarationImports", () => { let tempDir = ""; afterEach(async () => { if (tempDir) { await fs.rm(tempDir, { recursive: true, force: true }); tempDir = ""; } }); it("publishes resolvable .d.mts imports for the typings subpath", async () => { tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "delement-declarations-")); const packageDir = path.join(tempDir, "node_modules", "@delement", "ui"); const typesDir = path.join(packageDir, "dist", "types"); const componentsDir = path.join(typesDir, "components"); const consumerPath = path.join(tempDir, "consumer.ts"); await fs.mkdir(componentsDir, { recursive: true }); await fs.writeFile(path.join(packageDir, "package.json"), JSON.stringify({ name: "@delement/ui", type: "module", exports: { "./typings": { types: "./dist/types/index.d.mts", }, }, })); await fs.writeFile(path.join(typesDir, "index.d.mts"), `export type { IComponent, IComponentBaseProps, } from "./components/index"; `); await fs.writeFile(path.join(componentsDir, "index.d.mts"), `export interface IComponentBaseProps { baseClass?: string; extraClasses?: Record; extraAttrs?: Record; } export type TComponentProps = IComponentBaseProps & Omit; export interface IComponent { (props: TComponentProps): null; } `); await fs.writeFile(consumerPath, `import type { IComponent, IComponentBaseProps, } from "@delement/ui/typings"; interface IProps extends IComponentBaseProps { label: string; } const Component: IComponent = ({ label, baseClass, extraClasses, extraAttrs }) => { label.toUpperCase(); baseClass?.toUpperCase(); Object.keys(extraClasses ?? {}); Object.keys(extraAttrs ?? {}); return null; }; void Component; `); const result = normalizeDeclarationImports(path.join(packageDir, "dist")); const entrypoint = await fs.readFile(path.join(typesDir, "index.d.mts"), "utf8"); assert.equal(result.replacements, 1); assert.match(entrypoint, /from "\.\/components\/index\.d\.mts"/); const program = ts.createProgram([ consumerPath ], { module: ts.ModuleKind.ESNext, moduleResolution: ts.ModuleResolutionKind.Bundler, noEmit: true, skipLibCheck: false, strict: true, target: ts.ScriptTarget.ESNext, }); const diagnostics = ts.getPreEmitDiagnostics(program); assert.deepEqual(diagnostics.map((diagnostic) => ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n")), []); }); it("fails for an unresolved extensionless declaration import", async () => { tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "delement-declarations-")); await fs.writeFile(path.join(tempDir, "index.d.mts"), 'export type { Missing } from "./missing";\n'); assert.throws( () => normalizeDeclarationImports(tempDir), /Unresolved declaration import "\.\/missing"/ ); }); it("rewrites source-only #package aliases to relative declarations", async () => { tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "delement-declarations-")); const componentDir = path.join(tempDir, "components", "btn"); const typesDir = path.join(tempDir, "types"); await fs.mkdir(componentDir, { recursive: true }); await fs.mkdir(typesDir, { recursive: true }); await fs.writeFile(path.join(componentDir, "index.d.mts"), 'import type { Props } from "#package/types";\nexport type BtnProps = Props;\n'); await fs.writeFile(path.join(typesDir, "index.d.mts"), "export interface Props { label: string; }\n"); const result = normalizeDeclarationImports(tempDir); const declaration = await fs.readFile(path.join(componentDir, "index.d.mts"), "utf8"); assert.equal(result.replacements, 1); assert.match(declaration, /from "\.\.\/\.\.\/types\/index\.d\.mts"/); }); });