"use strict"; // The package as a consumer resolves it. A package can load itself by name through its own "exports", so // require("alex2node") and import("alex2node") below take the path an installed copy takes. const { test } = require("node:test"); const assert = require("node:assert/strict"); const fs = require("node:fs"); const os = require("node:os"); const path = require("node:path"); const { builtinModules } = require("node:module"); const { spawnSync } = require("node:child_process"); const pkg = require("alex2node/package.json"); const ROOT = path.join(__dirname, ".."); const DIST = path.join(ROOT, "dist"); // Every file under dir, relative to it, sorted. A staging directory left by a killed build is not part of dist/. function files(dir, prefix = "") { return fs.readdirSync(path.join(dir, prefix), { withFileTypes: true }) .filter((entry) => !entry.name.startsWith(".build-")) .flatMap((entry) => (entry.isDirectory() ? files(dir, path.join(prefix, entry.name)) : [path.join(prefix, entry.name)])) .sort(); } test("require(\"alex2node\") loads the CommonJS build", () => { assert.equal(require.resolve("alex2node"), path.join(DIST, "cjs", "index.js")); assert.equal(typeof require("alex2node").Alex2MQTT, "function"); }); test("import(\"alex2node\") loads the ES module build, with the export names of the CommonJS build", async () => { const cjs = require("alex2node"); const esm = await import("alex2node"); // Had the import reached the CommonJS file, the namespace would carry "default" and the classes would be identical. assert.deepEqual(Object.keys(esm).sort(), Object.keys(cjs).sort()); assert.notEqual(esm.Alex2MQTT, cjs.Alex2MQTT); assert.equal(esm.DEFAULT_HOST, cjs.DEFAULT_HOST); }); test("\"main\", \"module\" and \"types\" name the same files as \"exports\", for resolvers that do not read it", () => { const entry = pkg.exports["."]; assert.deepEqual([pkg.main, pkg.module, pkg.types], [entry.require, entry.import, entry.types]); for (const file of [pkg.main, pkg.module, pkg.types]) assert.ok(fs.existsSync(path.join(ROOT, file)), file); assert.equal(require(path.join(ROOT, pkg.main)), require("alex2node")); }); test("the builds import Node built-ins and the declared dependencies, nothing else", () => { // "mqtt/lib/x" and "@scope/name/x" belong to the packages "mqtt" and "@scope/name" const packageOf = (specifier) => specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/"); const imported = new Set(); for (const file of files(DIST).filter((f) => f.endsWith(".js"))) { const source = fs.readFileSync(path.join(DIST, file), "utf8"); for (const [, specifier] of source.matchAll(/(?:\brequire\(|\bfrom\s+|\bimport\s+)"([^".][^"]*)"/g)) { if (!builtinModules.includes(specifier.replace(/^node:/, ""))) imported.add(packageOf(specifier)); } } assert.deepEqual([...imported].sort(), Object.keys(pkg.dependencies).sort()); }); test("dist/ is what npm run build produces from src/", () => { const fresh = fs.mkdtempSync(path.join(os.tmpdir(), "alex2node-build-")); try { const build = spawnSync(process.execPath, [path.join(ROOT, "scripts", "build.mjs"), "--out", fresh], { encoding: "utf8" }); assert.equal(build.status, 0, `the build failed:\n${build.stdout}${build.stderr}`); assert.deepEqual(files(DIST), files(fresh), "dist/ has other files than a build writes: npm run build"); const stale = files(fresh).filter((f) => !fs.readFileSync(path.join(DIST, f)).equals(fs.readFileSync(path.join(fresh, f)))); assert.deepEqual(stale, [], "dist/ is older than src/: npm run build"); } finally { fs.rmSync(fresh, { recursive: true, force: true }); } });