import { describe, it, expect } from 'bun:test'; import { readFileSync } from 'fs'; import { join } from 'path'; const installSourcePath = join( __dirname, '..', 'src', 'npx-cli', 'commands', 'install.ts', ); const installSource = readFileSync(installSourcePath, 'utf-8'); describe('Install Non-TTY Support', () => { describe('isInteractive flag', () => { it('defines isInteractive based on process.stdin.isTTY', () => { expect(installSource).toContain('const isInteractive = process.stdin.isTTY === true'); }); it('uses strict equality (===) not truthy check for isTTY', () => { const match = installSource.match(/const isInteractive = process\.stdin\.isTTY === true/); expect(match).not.toBeNull(); }); }); describe('runTasks helper', () => { it('defines a runTasks function', () => { expect(installSource).toContain('async function runTasks'); }); it('has interactive branch using p.tasks', () => { expect(installSource).toContain('await p.tasks(tasks)'); }); it('has non-interactive fallback using console.log', () => { expect(installSource).toContain('console.log(` ${msg}`)'); }); it('branches on isInteractive', () => { expect(installSource).toContain('if (isInteractive)'); }); }); describe('log wrapper', () => { it('defines log.info that falls back to console.log', () => { expect(installSource).toContain('info: (msg: string) =>'); expect(installSource).toMatch(/info:.*console\.log/); }); it('defines log.success that falls back to console.log', () => { expect(installSource).toContain('success: (msg: string) =>'); expect(installSource).toMatch(/success:.*console\.log/); }); it('defines log.warn that falls back to console.warn', () => { expect(installSource).toContain('warn: (msg: string) =>'); expect(installSource).toMatch(/warn:.*console\.warn/); }); it('defines log.error that falls back to console.error', () => { expect(installSource).toContain('error: (msg: string) =>'); expect(installSource).toMatch(/error:.*console\.error/); }); }); describe('non-interactive install path', () => { it('defaults to claude-code when not interactive and no IDE specified', () => { expect(installSource).toContain("selectedIDEs = ['claude-code']"); }); it('uses console.log for intro in non-interactive mode', () => { expect(installSource).toContain("console.log('claude-mem install')"); }); it('uses console.log for note/summary in non-interactive mode', () => { expect(installSource).toContain("console.log(`\\n ${installStatus}`)"); }); }); describe('TaskDescriptor interface', () => { it('defines a task interface with title and task function', () => { expect(installSource).toContain('interface TaskDescriptor'); expect(installSource).toContain('title: string'); expect(installSource).toContain('task: (message: (msg: string) => void) => Promise'); }); }); describe('InstallOptions interface', () => { it('exports InstallOptions with optional ide field', () => { expect(installSource).toContain('export interface InstallOptions'); expect(installSource).toContain('ide?: string'); }); }); describe('post-install Next Steps copy', () => { it('frames the choice as two paths', () => { expect(installSource).toContain('Two paths from here:'); }); it('sets timing honesty about second-session memory injection', () => { expect(installSource).toContain('Memory injection starts on your second session in a project.'); }); it('addresses privacy: everything stays local', () => { expect(installSource).toContain('Everything stays in '); expect(installSource).toContain("pc.cyan('~/.claude-mem')"); }); it('keeps /learn-codebase as the optional front-load path', () => { expect(installSource).toContain('/learn-codebase'); }); it('demotes the uninstall caveat into a dim footer', () => { expect(installSource).toContain('close all Claude Code sessions before uninstalling'); }); it('does not advertise /mem-search in the post-install Next Steps', () => { const nextStepsRegion = installSource.slice( installSource.indexOf('const nextSteps = '), installSource.indexOf("p.note(nextSteps.join"), ); expect(nextStepsRegion).not.toContain('/mem-search'); }); it('does not advertise /knowledge-agent in the post-install Next Steps', () => { const nextStepsRegion = installSource.slice( installSource.indexOf('const nextSteps = '), installSource.indexOf("p.note(nextSteps.join"), ); expect(nextStepsRegion).not.toContain('/knowledge-agent'); }); }); });