Files
vue-tui/packages/runtime-tests/integration/build-output.test.ts
T
Yunfei He 48558c5af6 test(runtime): lock reconciler, measure, flex, overflow, build-output (Ink parity) (#116)
Final round-2 test-only batch (behaviors already at parity with Ink reconciler.tsx,
measure-text.tsx, flex-*.tsx, overflow.tsx, build-output.ts):
- build-output: every package.json export target resolves on disk (runtime/cli/testing)
  + the .d.mts declaration sibling for the typed libraries (runtime/testing, not cli).
- reconciler: keyed insert-between [a,c]→[a,b,c]; replace a colored <Text> child with a
  plain string; setElementText A→B + the text-context guard; marginLeft removal reset.
- measure: empty <Text> contributes height 0 in a column; non-zero left (marginLeft=5 →
  5,1); measureTextNatural trailing/only-newline heights.
- flex: alignSelf='auto' == default + alignSelf removal resets to AUTO; the two
  space-around known-yoga-bug cases converted from test.skip to test.fails (they assert
  the DESIRED output and flip to a real failure if yoga ever fixes the bug); the documented
  flexDirection/flexWrap removal-reset divergence (was comment-only) now has a visual lock.
- overflow: out-of-bounds writes produce Ink's exact clipped frame (sparse past-width cell,
  filtered hole) — tightened from toBeDefined().
- components: inline + top-level non-empty fragment in <Text>; the previously-skipped
  ST-terminated OSC-8 hyperlink hard-wrap now passes ('abcde\nfghij') — un-skipped as a lock.

Codex-reviewed GENUINE.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 03:28:35 +08:00

93 lines
3.3 KiB
TypeScript

import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, test } from "vite-plus/test";
// Mirror of Ink's test/build-output.ts: walk each published package's
// package.json `exports` map and assert every string target actually exists in
// the built `dist/`. This is an INTEGRATION test run after `vp run build`
// (`vp run ci` builds first); a missing target is a real packaging gap that
// would ship a broken `import`/`require`.
const here = path.dirname(fileURLToPath(import.meta.url));
// integration/ -> runtime-tests/ -> packages/
const packagesDir = path.resolve(here, "..", "..");
type Exports = string | { [condition: string]: Exports };
/**
* Collect every string leaf reachable from an `exports` value, descending
* through nested condition objects (`import`/`require`/`types`/...) and named
* subpaths (`.`, `./internal`, `./package.json`). Each leaf is the literal path
* the resolver would hand back, so each must exist on disk.
*/
function collectTargets(value: Exports, out: string[] = []): string[] {
if (typeof value === "string") {
out.push(value);
return out;
}
for (const nested of Object.values(value)) {
collectTargets(nested, out);
}
return out;
}
/**
* For a TYPED library entry, the declaration sibling sits next to the runtime
* `.mjs` with a `.d.mts` extension (tsdown/`vp pack` emits `index.mjs` +
* `index.d.mts`). We assert it explicitly for runtime/testing — cli has no
* public type surface (no `index.d.mts`), so it is excluded.
*/
function declarationSibling(mjsTarget: string): string {
return mjsTarget.replace(/\.mjs$/, ".d.mts");
}
type PackageCase = {
/** Directory name under packages/ */
dir: string;
/** Whether this package ships a public type surface (.d.mts siblings). */
typed: boolean;
};
const cases: PackageCase[] = [
{ dir: "runtime", typed: true },
{ dir: "cli", typed: false },
{ dir: "testing", typed: true },
];
describe("build output: package.json exports resolve to built files", () => {
for (const { dir, typed } of cases) {
const pkgDir = path.join(packagesDir, dir);
const pkgJsonPath = path.join(pkgDir, "package.json");
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8")) as {
name: string;
exports: Exports;
};
test(`${pkg.name}: every exports target exists`, () => {
const targets = collectTargets(pkg.exports);
// Sanity: the map must have at least the root entry.
expect(targets.length).toBeGreaterThan(0);
for (const target of targets) {
const abs = path.join(pkgDir, target);
expect(fs.existsSync(abs), `${pkg.name} exports target missing: ${target}`).toBe(true);
}
});
if (typed) {
test(`${pkg.name}: each .mjs library export has a .d.mts declaration sibling`, () => {
const mjsTargets = collectTargets(pkg.exports).filter(
(t) => t.endsWith(".mjs") && t.startsWith("./dist/"),
);
// A typed library must expose at least one runtime entry.
expect(mjsTargets.length).toBeGreaterThan(0);
for (const mjs of mjsTargets) {
const dts = declarationSibling(mjs);
const abs = path.join(pkgDir, dts);
expect(fs.existsSync(abs), `${pkg.name} missing declaration sibling: ${dts}`).toBe(true);
}
});
}
}
});