test(ci): stabilize hook and skip-git tests

This commit is contained in:
ximengkai 2026-08-18 16:19:10 +08:00
parent cc977cd559
commit 09fd524c96
8 changed files with 54 additions and 11 deletions

View file

@ -389,7 +389,7 @@ export const FileTreePanel = ({ onFocusNode }: FileTreePanelProps) => {
</div>
{/* File tree */}
<div className="scrollbar-thin flex-1 overflow-y-auto py-2">
<div className="flex-1 scrollbar-thin overflow-y-auto py-2">
{fileTree.length === 0 ? (
<div className="px-3 py-4 text-center text-xs text-text-muted">
{t('graph:fileTree.noFilesLoaded')}
@ -413,7 +413,7 @@ export const FileTreePanel = ({ onFocusNode }: FileTreePanelProps) => {
)}
{activeTab === 'filters' && (
<div className="scrollbar-thin flex-1 overflow-y-auto p-3">
<div className="flex-1 scrollbar-thin overflow-y-auto p-3">
<div className="mb-3">
<h3 className="mb-2 text-xs font-medium tracking-wide text-text-secondary uppercase">
{t('graph:fileTree.nodeTypes')}

View file

@ -247,7 +247,7 @@ export const Header = ({
/>
</div>
</div>
<div className="scrollbar-thin min-h-0 flex-1 overflow-y-auto pb-1">
<div className="min-h-0 flex-1 scrollbar-thin overflow-y-auto pb-1">
{filteredRepos.length === 0 ? (
<div className="px-4 py-3 text-sm text-text-muted">
{t('header:noRepositoriesFound', { query: repoSearchQuery })}

View file

@ -364,7 +364,7 @@ export const ProcessesPanel = () => {
</div>
{/* Process list */}
<div className="scrollbar-thin flex-1 overflow-y-auto">
<div className="flex-1 scrollbar-thin overflow-y-auto">
{/* View All Processes Card */}
<div className="px-4 py-3">
<button

View file

@ -343,7 +343,7 @@ export const QueryFAB = () => {
</div>
{showResults && queryResult.rows.length > 0 && (
<div className="scrollbar-thin max-h-48 overflow-auto border-t border-border-subtle">
<div className="max-h-48 scrollbar-thin overflow-auto border-t border-border-subtle">
<table className="w-full text-xs">
<thead className="sticky top-0 bg-surface">
<tr>

View file

@ -301,7 +301,7 @@ export const RightPanel = () => {
)}
{/* Messages */}
<div ref={scrollContainerRef} className="scrollbar-thin flex-1 overflow-y-auto p-4">
<div ref={scrollContainerRef} className="flex-1 scrollbar-thin overflow-y-auto p-4">
{chatMessages.length === 0 ? (
<div className="flex h-full flex-col items-center justify-center px-4 text-center">
<div className="mb-4 flex h-14 w-14 items-center justify-center rounded-xl bg-gradient-to-br from-accent to-node-interface text-2xl shadow-glow">
@ -426,7 +426,7 @@ export const RightPanel = () => {
onKeyDown={handleKeyDown}
placeholder={t('chat:input.placeholder')}
rows={1}
className="scrollbar-thin min-h-[36px] flex-1 resize-none border-none bg-transparent text-sm text-text-primary outline-none placeholder:text-text-muted"
className="min-h-[36px] flex-1 resize-none scrollbar-thin border-none bg-transparent text-sm text-text-primary outline-none placeholder:text-text-muted"
style={{ height: '36px', overflowY: 'hidden' }}
/>
<button

View file

@ -2166,7 +2166,12 @@ describe.skipIf(SKIP_LSOF_PATH)(
cwd: tmpDir,
},
undefined,
{ env: hookEnv(binDir) },
{
// Owner classification is the subject here. Timeout-wrapper
// containment has dedicated coverage and can race these fake
// macOS process-table fixtures.
env: { ...hookEnv(binDir), GITNEXUS_HOOK_TIMEOUT_PATH: 'disabled' },
},
);
const output = parseHookOutput(result.stdout);
expect(output).not.toBeNull();
@ -2651,7 +2656,12 @@ describe.skipIf(SKIP_LSOF_PATH)(
cwd: tmpDir,
},
undefined,
{ env: hookEnv(binDir) },
{
// Owner classification is the subject here. Timeout-wrapper
// containment has dedicated coverage and can race these fake
// macOS process-table fixtures.
env: { ...hookEnv(binDir), GITNEXUS_HOOK_TIMEOUT_PATH: 'disabled' },
},
);
const output = parseHookOutput(result.stdout);
expect(output).not.toBeNull();

View file

@ -66,6 +66,10 @@ describe('--skip-git CLI flag', () => {
...process.env,
HOME: gitnexusHome,
GITNEXUS_HOME: gitnexusHome,
// This suite tests repository-root selection, not extension installation.
// Keep child CLI runs offline so an unavailable FTS download cannot consume
// Vitest's per-test timeout.
GITNEXUS_LBUG_EXTENSION_INSTALL: 'never',
};
try {
@ -128,6 +132,7 @@ describe('--skip-git CLI flag', () => {
...process.env,
HOME: gitnexusHome,
GITNEXUS_HOME: gitnexusHome,
GITNEXUS_LBUG_EXTENSION_INSTALL: 'never',
};
try {
@ -194,6 +199,7 @@ describe('--skip-git CLI flag', () => {
...process.env,
HOME: gitnexusHome,
GITNEXUS_HOME: gitnexusHome,
GITNEXUS_LBUG_EXTENSION_INSTALL: 'never',
};
}

View file

@ -84,6 +84,19 @@ function writeExecutable(filePath: string, content: string) {
fs.writeFileSync(filePath, content, { mode: 0o755 });
}
function shellQuote(value: string) {
return `'${value.replace(/'/g, `'\\''`)}'`;
}
function writeImmediateShellExecutable(filePath: string, stdout: string, markerPath?: string) {
writeExecutable(
filePath,
`#!/bin/sh\n` +
(markerPath ? `: > ${shellQuote(markerPath)}\n` : '') +
`printf %s ${shellQuote(stdout)}\n`,
);
}
export function createHookToolDir(options: {
gitnexusStderr?: string;
gitnexusMarkerPath?: string;
@ -129,6 +142,12 @@ export function createHookToolDir(options: {
options.lsofOutputLines != null
? options.lsofOutputLines.join('\n') + (options.lsofOutputLines.length ? '\n' : '')
: (options.lsofOutput ?? '');
// The owner probe gives lsof one second. Use a shell fixture for immediate
// responses so a Node cold start cannot be misclassified as a timed-out
// (therefore fail-closed) owner under a heavily parallel test run. The
// delay, signal, and PID fixtures below still need a Node process.
const immediateLsof =
options.lsofSleepMs == null && options.lsofPidFile == null && !options.lsofIgnoreSigterm;
// Composable prologue: pidFile write MUST stay the first statement (see the
// option docs above); SIGTERM trap MUST be installed before any sleep.
const lsofPrologue =
@ -144,7 +163,11 @@ export function createHookToolDir(options: {
options.lsofSleepMs != null
? `${lsofPrologue}setTimeout(() => {}, ${Number(options.lsofSleepMs)});\n`
: `${lsofPrologue}process.stdout.write(${JSON.stringify(lsofOutput)});\nprocess.exit(0);\n`;
writeExecutable(path.join(binDir, 'lsof'), lsofBody);
if (immediateLsof) {
writeImmediateShellExecutable(path.join(binDir, 'lsof'), lsofOutput, options.lsofMarkerPath);
} else {
writeExecutable(path.join(binDir, 'lsof'), lsofBody);
}
const psBody =
options.psOutputByPid != null
@ -156,7 +179,11 @@ process.stdout.write(byPid[p] ?? '');
process.exit(0);
`
: `#!/usr/bin/env node\nprocess.stdout.write(${JSON.stringify(options.psOutput ?? '')});\nprocess.exit(0);\n`;
writeExecutable(path.join(binDir, 'ps'), psBody);
if (options.psOutputByPid == null) {
writeImmediateShellExecutable(path.join(binDir, 'ps'), options.psOutput ?? '');
} else {
writeExecutable(path.join(binDir, 'ps'), psBody);
}
return binDir;
}