mirror of
https://github.com/tiennm99/ccstatusline.git
synced 2026-09-02 12:19:32 +00:00
test: isolate config-path behavior across suite
This commit is contained in:
@@ -1,42 +1,44 @@
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import {
|
||||
afterEach,
|
||||
afterAll,
|
||||
beforeAll,
|
||||
afterEach,
|
||||
beforeEach,
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
vi,
|
||||
type Mock
|
||||
it
|
||||
} from 'vitest';
|
||||
|
||||
import {
|
||||
getConfigPath,
|
||||
isCustomConfigPath
|
||||
} from '../config';
|
||||
CCSTATUSLINE_COMMANDS,
|
||||
getClaudeSettingsPath,
|
||||
installStatusLine,
|
||||
isKnownCommand
|
||||
} from '../claude-settings';
|
||||
import { initConfigPath } from '../config';
|
||||
|
||||
vi.mock('../config', () => ({
|
||||
getConfigPath: vi.fn(() => '/default/settings.json'),
|
||||
isCustomConfigPath: vi.fn(() => false)
|
||||
}));
|
||||
const ORIGINAL_CLAUDE_CONFIG_DIR = process.env.CLAUDE_CONFIG_DIR;
|
||||
let testClaudeConfigDir = '';
|
||||
|
||||
const mockIsCustomConfigPath = isCustomConfigPath as Mock;
|
||||
const mockGetConfigPath = getConfigPath as Mock;
|
||||
const ORIGINAL_PLATFORM = process.platform;
|
||||
|
||||
function setProcessPlatform(platform: NodeJS.Platform): void {
|
||||
Object.defineProperty(process, 'platform', {
|
||||
value: platform,
|
||||
configurable: true
|
||||
});
|
||||
function readInstalledCommand(): string {
|
||||
const settingsPath = getClaudeSettingsPath();
|
||||
const content = fs.readFileSync(settingsPath, 'utf-8');
|
||||
const data = JSON.parse(content) as { statusLine?: { command?: string } };
|
||||
return data.statusLine?.command ?? '';
|
||||
}
|
||||
|
||||
// Safety net: point CLAUDE_CONFIG_DIR at a temp path so even if the fs mock
|
||||
// leaks, writes will never land in the user's real ~/.claude directory.
|
||||
const ORIGINAL_CLAUDE_CONFIG_DIR = process.env.CLAUDE_CONFIG_DIR;
|
||||
beforeEach(() => {
|
||||
testClaudeConfigDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ccstatusline-claude-settings-'));
|
||||
process.env.CLAUDE_CONFIG_DIR = testClaudeConfigDir;
|
||||
initConfigPath();
|
||||
});
|
||||
|
||||
beforeAll(() => {
|
||||
process.env.CLAUDE_CONFIG_DIR = '/tmp/ccstatusline-test-fake-claude-config';
|
||||
afterEach(() => {
|
||||
initConfigPath();
|
||||
if (testClaudeConfigDir) {
|
||||
fs.rmSync(testClaudeConfigDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
@@ -47,25 +49,6 @@ afterAll(() => {
|
||||
}
|
||||
});
|
||||
|
||||
// Capture settings written by installStatusLine via the mock writeFile callback.
|
||||
let savedSettings: Record<string, unknown> = {};
|
||||
|
||||
vi.mock('fs', () => ({
|
||||
existsSync: vi.fn(() => false),
|
||||
statSync: vi.fn(),
|
||||
promises: {
|
||||
readFile: vi.fn(() => Promise.resolve('{}')),
|
||||
writeFile: vi.fn((_path: string, content: string) => {
|
||||
savedSettings = JSON.parse(content) as Record<string, unknown>;
|
||||
return Promise.resolve(undefined);
|
||||
}),
|
||||
mkdir: vi.fn(() => Promise.resolve(undefined))
|
||||
}
|
||||
}));
|
||||
|
||||
// Dynamic import so the module picks up the mocked fs references.
|
||||
const { CCSTATUSLINE_COMMANDS, isKnownCommand, installStatusLine } = await import('../claude-settings');
|
||||
|
||||
describe('isKnownCommand', () => {
|
||||
it('should match exact NPM command', () => {
|
||||
expect(isKnownCommand(CCSTATUSLINE_COMMANDS.NPM)).toBe(true);
|
||||
@@ -113,77 +96,39 @@ describe('isKnownCommand', () => {
|
||||
});
|
||||
|
||||
describe('buildCommand via installStatusLine', () => {
|
||||
beforeEach(() => {
|
||||
savedSettings = {};
|
||||
setProcessPlatform(ORIGINAL_PLATFORM);
|
||||
mockIsCustomConfigPath.mockReturnValue(false);
|
||||
mockGetConfigPath.mockReturnValue('/default/settings.json');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setProcessPlatform(ORIGINAL_PLATFORM);
|
||||
});
|
||||
|
||||
it('should use base command when no custom config path', async () => {
|
||||
mockIsCustomConfigPath.mockReturnValue(false);
|
||||
initConfigPath();
|
||||
await installStatusLine(false);
|
||||
|
||||
const statusLine = savedSettings.statusLine as { command: string };
|
||||
expect(statusLine.command).toBe(CCSTATUSLINE_COMMANDS.NPM);
|
||||
expect(readInstalledCommand()).toBe(CCSTATUSLINE_COMMANDS.NPM);
|
||||
});
|
||||
|
||||
it('should append --config with simple path (no quoting needed)', async () => {
|
||||
mockIsCustomConfigPath.mockReturnValue(true);
|
||||
mockGetConfigPath.mockReturnValue('/tmp/settings.json');
|
||||
initConfigPath('/tmp/settings.json');
|
||||
await installStatusLine(false);
|
||||
|
||||
const statusLine = savedSettings.statusLine as { command: string };
|
||||
expect(statusLine.command).toBe(`${CCSTATUSLINE_COMMANDS.NPM} --config /tmp/settings.json`);
|
||||
expect(readInstalledCommand()).toBe(`${CCSTATUSLINE_COMMANDS.NPM} --config /tmp/settings.json`);
|
||||
});
|
||||
|
||||
it('should quote path with spaces', async () => {
|
||||
mockIsCustomConfigPath.mockReturnValue(true);
|
||||
mockGetConfigPath.mockReturnValue('/my path/settings.json');
|
||||
initConfigPath('/my path/settings.json');
|
||||
await installStatusLine(false);
|
||||
|
||||
const statusLine = savedSettings.statusLine as { command: string };
|
||||
expect(statusLine.command).toBe(`${CCSTATUSLINE_COMMANDS.NPM} --config '/my path/settings.json'`);
|
||||
expect(readInstalledCommand()).toBe(`${CCSTATUSLINE_COMMANDS.NPM} --config '/my path/settings.json'`);
|
||||
});
|
||||
|
||||
it('should quote path with parentheses', async () => {
|
||||
mockIsCustomConfigPath.mockReturnValue(true);
|
||||
mockGetConfigPath.mockReturnValue('/my(path)/settings.json');
|
||||
initConfigPath('/my(path)/settings.json');
|
||||
await installStatusLine(false);
|
||||
|
||||
const statusLine = savedSettings.statusLine as { command: string };
|
||||
expect(statusLine.command).toBe(`${CCSTATUSLINE_COMMANDS.NPM} --config '/my(path)/settings.json'`);
|
||||
expect(readInstalledCommand()).toBe(`${CCSTATUSLINE_COMMANDS.NPM} --config '/my(path)/settings.json'`);
|
||||
});
|
||||
|
||||
it('should escape embedded single quotes in path', async () => {
|
||||
mockIsCustomConfigPath.mockReturnValue(true);
|
||||
mockGetConfigPath.mockReturnValue('/my\'path/settings.json');
|
||||
initConfigPath('/my\'path/settings.json');
|
||||
await installStatusLine(false);
|
||||
|
||||
const statusLine = savedSettings.statusLine as { command: string };
|
||||
expect(statusLine.command).toBe(`${CCSTATUSLINE_COMMANDS.NPM} --config '/my'\\''path/settings.json'`);
|
||||
expect(readInstalledCommand()).toBe(`${CCSTATUSLINE_COMMANDS.NPM} --config '/my'\\''path/settings.json'`);
|
||||
});
|
||||
|
||||
it('should use bunx command when useBunx is true', async () => {
|
||||
mockIsCustomConfigPath.mockReturnValue(true);
|
||||
mockGetConfigPath.mockReturnValue('/my path/settings.json');
|
||||
initConfigPath('/my path/settings.json');
|
||||
await installStatusLine(true);
|
||||
|
||||
const statusLine = savedSettings.statusLine as { command: string };
|
||||
expect(statusLine.command).toBe(`${CCSTATUSLINE_COMMANDS.BUNX} --config '/my path/settings.json'`);
|
||||
expect(readInstalledCommand()).toBe(`${CCSTATUSLINE_COMMANDS.BUNX} --config '/my path/settings.json'`);
|
||||
});
|
||||
|
||||
it('should use Windows-safe double quoting for custom config paths', async () => {
|
||||
setProcessPlatform('win32');
|
||||
mockIsCustomConfigPath.mockReturnValue(true);
|
||||
mockGetConfigPath.mockReturnValue('C:\\Users\\Alice\\My Settings\\settings.json');
|
||||
await installStatusLine(false);
|
||||
|
||||
const statusLine = savedSettings.statusLine as { command: string };
|
||||
expect(statusLine.command).toBe(`${CCSTATUSLINE_COMMANDS.NPM} --config \"C:\\Users\\Alice\\My Settings\\settings.json\"`);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -19,10 +19,9 @@ import {
|
||||
|
||||
const MOCK_HOME_DIR = '/tmp/ccstatusline-config-test-home';
|
||||
|
||||
vi.mock('os', () => ({ homedir: () => MOCK_HOME_DIR }));
|
||||
|
||||
let loadSettings: () => Promise<Settings>;
|
||||
let saveSettings: (settings: Settings) => Promise<void>;
|
||||
let initConfigPath: (filePath?: string) => void;
|
||||
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
|
||||
|
||||
function getSettingsPaths(): { configDir: string; settingsPath: string; backupPath: string } {
|
||||
@@ -39,10 +38,13 @@ describe('config utilities', () => {
|
||||
const configModule = await import('../config');
|
||||
loadSettings = configModule.loadSettings;
|
||||
saveSettings = configModule.saveSettings;
|
||||
initConfigPath = configModule.initConfigPath;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fs.rmSync(MOCK_HOME_DIR, { recursive: true, force: true });
|
||||
const { settingsPath } = getSettingsPaths();
|
||||
initConfigPath(settingsPath);
|
||||
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
|
||||
});
|
||||
|
||||
@@ -52,6 +54,7 @@ describe('config utilities', () => {
|
||||
|
||||
afterAll(() => {
|
||||
fs.rmSync(MOCK_HOME_DIR, { recursive: true, force: true });
|
||||
initConfigPath();
|
||||
});
|
||||
|
||||
it('writes defaults when settings file does not exist', async () => {
|
||||
|
||||
@@ -168,4 +168,4 @@ export async function uninstallStatusLine(): Promise<void> {
|
||||
export async function getExistingStatusLine(): Promise<string | null> {
|
||||
const settings = await loadClaudeSettings();
|
||||
return settings.statusLine?.command ?? null;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -446,4 +446,4 @@ export function getPowerlineTheme(name: string): PowerlineTheme | undefined {
|
||||
|
||||
export function getDefaultPowerlineTheme(): string {
|
||||
return 'nord-aurora';
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -158,4 +158,4 @@ export async function saveSettings(settings: Settings): Promise<void> {
|
||||
};
|
||||
|
||||
await writeSettingsJson(settingsWithVersion, paths);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user