mirror of
https://github.com/Fission-AI/OpenSpec.git
synced 2026-09-14 20:16:53 +08:00
9ac6330430
* fix(cli): respect --no-interactive flag in validate command The validate command's spinner was starting regardless of the --no-interactive flag, causing hangs in pre-commit hooks. Changes: - Pass noInteractive option to runBulkValidation - Handle Commander.js --no-* flag syntax (sets interactive=false) - Only start ora spinner when in interactive mode - Add CI environment variable check to isInteractive() for industry standard compliance * test: add unit tests for interactive utilities and CLI flag - Export resolveNoInteractive() helper for reuse - Add InteractiveOptions type export for testing - Refactor validate.ts to use resolveNoInteractive() - Add 17 unit tests for isInteractive() and resolveNoInteractive() - Add CLI integration test for --no-interactive flag This prevents future regressions where Commander.js --no-* flag parsing is not properly handled.
126 lines
4.6 KiB
TypeScript
126 lines
4.6 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
import { isInteractive, resolveNoInteractive, InteractiveOptions } from '../../src/utils/interactive.js';
|
|
|
|
describe('interactive utilities', () => {
|
|
let originalOpenSpecInteractive: string | undefined;
|
|
let originalCI: string | undefined;
|
|
let originalStdinIsTTY: boolean | undefined;
|
|
|
|
beforeEach(() => {
|
|
// Save original environment
|
|
originalOpenSpecInteractive = process.env.OPEN_SPEC_INTERACTIVE;
|
|
originalCI = process.env.CI;
|
|
originalStdinIsTTY = process.stdin.isTTY;
|
|
|
|
// Clear environment for clean testing
|
|
delete process.env.OPEN_SPEC_INTERACTIVE;
|
|
delete process.env.CI;
|
|
});
|
|
|
|
afterEach(() => {
|
|
// Restore original environment
|
|
if (originalOpenSpecInteractive !== undefined) {
|
|
process.env.OPEN_SPEC_INTERACTIVE = originalOpenSpecInteractive;
|
|
} else {
|
|
delete process.env.OPEN_SPEC_INTERACTIVE;
|
|
}
|
|
if (originalCI !== undefined) {
|
|
process.env.CI = originalCI;
|
|
} else {
|
|
delete process.env.CI;
|
|
}
|
|
// Restore stdin.isTTY
|
|
Object.defineProperty(process.stdin, 'isTTY', {
|
|
value: originalStdinIsTTY,
|
|
writable: true,
|
|
configurable: true,
|
|
});
|
|
});
|
|
|
|
describe('resolveNoInteractive', () => {
|
|
it('should return true when noInteractive is true', () => {
|
|
expect(resolveNoInteractive({ noInteractive: true })).toBe(true);
|
|
});
|
|
|
|
it('should return true when interactive is false (Commander.js style)', () => {
|
|
// This is how Commander.js handles --no-interactive flag
|
|
expect(resolveNoInteractive({ interactive: false })).toBe(true);
|
|
});
|
|
|
|
it('should return false when noInteractive is false', () => {
|
|
expect(resolveNoInteractive({ noInteractive: false })).toBe(false);
|
|
});
|
|
|
|
it('should return false when interactive is true', () => {
|
|
expect(resolveNoInteractive({ interactive: true })).toBe(false);
|
|
});
|
|
|
|
it('should return false for empty options object', () => {
|
|
expect(resolveNoInteractive({})).toBe(false);
|
|
});
|
|
|
|
it('should return false for undefined', () => {
|
|
expect(resolveNoInteractive(undefined)).toBe(false);
|
|
});
|
|
|
|
it('should handle boolean value true', () => {
|
|
expect(resolveNoInteractive(true)).toBe(true);
|
|
});
|
|
|
|
it('should handle boolean value false', () => {
|
|
expect(resolveNoInteractive(false)).toBe(false);
|
|
});
|
|
|
|
it('should prioritize noInteractive over interactive when both set', () => {
|
|
// noInteractive: true should win
|
|
expect(resolveNoInteractive({ noInteractive: true, interactive: true })).toBe(true);
|
|
// If noInteractive is false, check interactive
|
|
expect(resolveNoInteractive({ noInteractive: false, interactive: false })).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('isInteractive', () => {
|
|
it('should return false when noInteractive is true', () => {
|
|
expect(isInteractive({ noInteractive: true })).toBe(false);
|
|
});
|
|
|
|
it('should return false when interactive is false (Commander.js --no-interactive)', () => {
|
|
expect(isInteractive({ interactive: false })).toBe(false);
|
|
});
|
|
|
|
it('should return false when OPEN_SPEC_INTERACTIVE env var is 0', () => {
|
|
process.env.OPEN_SPEC_INTERACTIVE = '0';
|
|
Object.defineProperty(process.stdin, 'isTTY', { value: true, writable: true, configurable: true });
|
|
expect(isInteractive({})).toBe(false);
|
|
});
|
|
|
|
it('should return false when CI env var is set', () => {
|
|
process.env.CI = 'true';
|
|
Object.defineProperty(process.stdin, 'isTTY', { value: true, writable: true, configurable: true });
|
|
expect(isInteractive({})).toBe(false);
|
|
});
|
|
|
|
it('should return false when CI env var is set to any value', () => {
|
|
// CI can be set to any value, not just "true"
|
|
process.env.CI = '1';
|
|
Object.defineProperty(process.stdin, 'isTTY', { value: true, writable: true, configurable: true });
|
|
expect(isInteractive({})).toBe(false);
|
|
});
|
|
|
|
it('should return false when stdin is not a TTY', () => {
|
|
Object.defineProperty(process.stdin, 'isTTY', { value: false, writable: true, configurable: true });
|
|
expect(isInteractive({})).toBe(false);
|
|
});
|
|
|
|
it('should return true when stdin is TTY and no flags disable it', () => {
|
|
Object.defineProperty(process.stdin, 'isTTY', { value: true, writable: true, configurable: true });
|
|
expect(isInteractive({})).toBe(true);
|
|
});
|
|
|
|
it('should return true when stdin is TTY and options are undefined', () => {
|
|
Object.defineProperty(process.stdin, 'isTTY', { value: true, writable: true, configurable: true });
|
|
expect(isInteractive(undefined)).toBe(true);
|
|
});
|
|
});
|
|
});
|