Files
vue-tui/packages/runtime-tests/integration/lifecycle/leak.sequential.test.tsx
T
Yunfei He ee6004b8be test(runtime-tests): run the main suite concurrently by default
Enable sequence.concurrent: true in vite.config.ts so the non-PTY suite runs
concurrently like the PTY suite. Stress-verified stable (8/8 at maxForks=4);
the suite drops from ~13s to ~4-5s.

Three test patterns were incompatible with concurrency; handled per cause:

- Inline snapshots (background-color, borders): the module-level `expect`
  loses snapshot test context under concurrency. Fixed in place by using the
  context-local `expect` (async ({ expect }) => ...), so they stay concurrent.

- Process-global state (throttle/animation-scheduler use fake timers; leak
  asserts on process exit/SIGINT listener counts and live yoga nodes): a
  concurrent sibling clobbers the shared global mid-test. These genuinely
  require serial execution, so they move to *.sequential.test.* files with
  it.sequential / describe.sequential and a header explaining why.

`vp run ready` passes.
2026-05-29 16:54:13 +08:00

63 lines
1.8 KiB
TypeScript

// Sequential: asserts on process-global resource counts (process exit/SIGINT
// listeners, live yoga nodes). Concurrent siblings that mount/unmount apps add
// and remove those listeners/nodes, polluting the counts. Tests are it.sequential.
import { defineComponent, nextTick, shallowRef } from "vue";
import { expect, test } from "vite-plus/test";
import { render } from "@vue-tui/testing";
import { Box, Text, useInput } from "@vue-tui/runtime";
import { yogaNodeTracker } from "@vue-tui/runtime/internal";
test.sequential("50 render/unmount cycles leak zero process listeners", async () => {
const exitBefore = process.listenerCount("exit");
const sigintBefore = process.listenerCount("SIGINT");
const App = defineComponent(() => () => <Text>x</Text>);
for (let i = 0; i < 50; i++) {
const { unmount } = await render(App);
unmount();
}
expect(process.listenerCount("exit")).toBe(exitBefore);
expect(process.listenerCount("SIGINT")).toBe(sigintBefore);
});
test.sequential("100 render/unmount cycles leak zero yoga nodes", async () => {
yogaNodeTracker.reset();
const App = defineComponent(() => () => <Text>x</Text>);
for (let i = 0; i < 100; i++) {
const { unmount } = await render(App);
unmount();
}
expect(yogaNodeTracker.snapshot().live).toBe(0);
});
test.sequential("raw mode stays on when one of two useInput components unmounts", async () => {
const showB = shallowRef(true);
const Listener = defineComponent(() => {
useInput(() => {});
return () => <Text>x</Text>;
});
const App = defineComponent(() => {
return () => (
<Box>
<Listener />
{showB.value ? <Listener /> : null}
</Box>
);
});
const { terminal } = await render(App);
expect(terminal.rawMode.current).toBe(true);
showB.value = false;
await nextTick();
expect(terminal.rawMode.current).toBe(true);
});