UNPKG

rs-runner

Version:

RS is a CLI tool for quickly detecting package.json scripts, and running them.

270 lines (216 loc) 8.72 kB
import * as fs from 'fs'; import * as os from 'os'; import path from 'path'; import { detectRunner, getPackageJsonScripts } from '../src/lib/pm'; import { getConfig, writeConfig } from '../src/lib/config'; import { getGlobalScripts, getDirectoryScripts, addNewGlobalScript, removeGlobalScript, addNewDirectoryScript, removeDirectoryScript, } from '../src/lib/scripts'; import { Config } from '../src/types'; jest.mock('fs'); jest.mock('os'); jest.mock('../src/lib/output'); describe('integration tests', () => { const mockHomedir = '/home/testuser'; const mockCwd = '/home/testuser/projects/myapp'; const configDir = path.join(mockHomedir, '.rs-runner'); const configPath = path.join(configDir, 'config.json'); let cwdSpy: jest.SpyInstance; beforeEach(() => { jest.clearAllMocks(); (os.homedir as jest.Mock).mockReturnValue(mockHomedir); cwdSpy = jest.spyOn(process, 'cwd').mockReturnValue(mockCwd); }); afterEach(() => { cwdSpy.mockRestore(); }); describe('multi-package-manager scenarios', () => { it('should detect correct PM when multiple projects exist', () => { // Simulate being in an npm project (fs.existsSync as jest.Mock).mockImplementation( (file) => file === 'package-lock.json', ); expect(detectRunner()).toBe('npm'); // Switch to pnpm project (fs.existsSync as jest.Mock).mockImplementation( (file) => file === 'pnpm-lock.yaml', ); expect(detectRunner()).toBe('pnpm'); }); it('should handle package.json scripts across different PM contexts', () => { const npmProject = { scripts: { start: 'node index.js', test: 'jest' }, }; (fs.existsSync as jest.Mock).mockReturnValue(true); (fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(npmProject)); const scripts = getPackageJsonScripts(); expect(scripts).toEqual(npmProject.scripts); }); it('should prioritize npm over yarn when both lock files exist', () => { (fs.existsSync as jest.Mock).mockImplementation( (file) => file === 'package-lock.json' || file === 'yarn.lock', ); expect(detectRunner()).toBe('npm'); }); }); describe('directory script resolution', () => { it('should isolate scripts between different directories', () => { const project1Dir = '/home/testuser/projects/app1'; const project2Dir = '/home/testuser/projects/app2'; const fullConfig: Config = { globalScripts: {}, directoryScripts: { [project1Dir]: { dev: 'vite dev' }, [project2Dir]: { dev: 'next dev' }, }, }; (fs.existsSync as jest.Mock).mockReturnValue(true); (fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(fullConfig)); // In project1 cwdSpy.mockReturnValue(project1Dir); expect(getDirectoryScripts()).toEqual({ dev: 'vite dev' }); // In project2 cwdSpy.mockReturnValue(project2Dir); expect(getDirectoryScripts()).toEqual({ dev: 'next dev' }); // In unknown directory cwdSpy.mockReturnValue('/other/path'); expect(getDirectoryScripts()).toEqual({}); }); it('should maintain global scripts across directories', () => { const fullConfig: Config = { globalScripts: { lint: 'eslint .', format: 'prettier --write .' }, directoryScripts: {}, }; (fs.existsSync as jest.Mock).mockReturnValue(true); (fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(fullConfig)); cwdSpy.mockReturnValue('/any/directory'); expect(getGlobalScripts()).toEqual(fullConfig.globalScripts); cwdSpy.mockReturnValue('/another/path'); expect(getGlobalScripts()).toEqual(fullConfig.globalScripts); }); }); describe('config file edge cases', () => { it('should handle empty config file gracefully', () => { (fs.existsSync as jest.Mock).mockReturnValue(true); (fs.readFileSync as jest.Mock).mockReturnValue('{}'); expect(getConfig()).toBeNull(); }); it('should handle config with only globalScripts', () => { const config = { globalScripts: { test: 'jest' } }; (fs.existsSync as jest.Mock).mockReturnValue(true); (fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(config)); expect(getConfig()).toEqual(config); expect(getGlobalScripts()).toEqual({ test: 'jest' }); }); it('should preserve other directories when adding to one', () => { const existingConfig: Config = { globalScripts: {}, directoryScripts: { '/project/a': { build: 'npm run build' }, '/project/b': { test: 'npm test' }, }, }; cwdSpy.mockReturnValue('/project/c'); (fs.existsSync as jest.Mock).mockReturnValue(true); (fs.readFileSync as jest.Mock) .mockReturnValueOnce(JSON.stringify(existingConfig)) .mockReturnValueOnce( JSON.stringify({ ...existingConfig, directoryScripts: { ...existingConfig.directoryScripts, '/project/c': { dev: 'vite' }, }, }), ); addNewDirectoryScript('dev', 'vite'); expect(fs.writeFileSync).toHaveBeenCalledWith( configPath, expect.stringContaining('/project/a'), 'utf8', ); expect(fs.writeFileSync).toHaveBeenCalledWith( configPath, expect.stringContaining('/project/b'), 'utf8', ); }); it('should handle concurrent global and directory script operations', () => { let storedConfig: Config = { globalScripts: { existing: 'command' }, directoryScripts: {}, }; (fs.existsSync as jest.Mock).mockReturnValue(true); (fs.readFileSync as jest.Mock).mockImplementation(() => JSON.stringify(storedConfig), ); (fs.writeFileSync as jest.Mock).mockImplementation((_, content) => { storedConfig = JSON.parse(content as string); }); // Add global script addNewGlobalScript('lint', 'eslint .'); // Verify global script was added expect(storedConfig.globalScripts).toHaveProperty('lint'); expect(storedConfig.globalScripts.existing).toBe('command'); // Add directory script addNewDirectoryScript('dev', 'vite dev'); // Verify both are preserved expect(storedConfig.globalScripts).toHaveProperty('lint'); expect(storedConfig.directoryScripts[mockCwd]).toHaveProperty('dev'); }); it('should handle removal of last script in directory', () => { const config: Config = { globalScripts: {}, directoryScripts: { [mockCwd]: { onlyScript: 'echo test' }, }, }; (fs.existsSync as jest.Mock).mockReturnValue(true); (fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(config)); removeDirectoryScript('onlyScript'); const writeCall = (fs.writeFileSync as jest.Mock).mock.calls[0]; const writtenConfig = JSON.parse(writeCall[1]); expect(writtenConfig.directoryScripts[mockCwd]).toBeUndefined(); }); it('should handle special characters in script commands', () => { const config: Config = { globalScripts: {}, directoryScripts: {}, }; (fs.existsSync as jest.Mock).mockReturnValue(true); (fs.readFileSync as jest.Mock) .mockReturnValueOnce(JSON.stringify(config)) .mockReturnValueOnce( JSON.stringify({ ...config, globalScripts: { complex: 'echo "hello world" && npm run test -- --coverage', }, }), ); addNewGlobalScript( 'complex', 'echo "hello world" && npm run test -- --coverage', ); const writeCall = (fs.writeFileSync as jest.Mock).mock.calls[0]; const writtenConfig = JSON.parse(writeCall[1]); expect(writtenConfig.globalScripts.complex).toBe( 'echo "hello world" && npm run test -- --coverage', ); }); it('should handle config directory creation on first use', () => { (fs.existsSync as jest.Mock).mockImplementation((p) => { if (p === configDir) return false; if (p === configPath) return false; return true; }); getConfig(); expect(fs.mkdirSync).toHaveBeenCalledWith(configDir, { recursive: true }); }); }); });