concurrently
Version:
Run commands concurrently
289 lines (288 loc) • 10.3 kB
JavaScript
import fs from 'node:fs';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { ExpandWildcard } from './expand-wildcard.js';
let parser;
let readPackage;
let readDeno;
const createCommandInfo = (command) => ({
command,
name: '',
});
beforeEach(() => {
readDeno = vi.fn();
readPackage = vi.fn();
parser = new ExpandWildcard(readDeno, readPackage);
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('#readDeno()', () => {
it('can read deno.json', () => {
const expectedDeno = {
name: 'deno',
version: '1.14.0',
};
vi.spyOn(fs, 'existsSync').mockImplementation((path) => {
return path === 'deno.json';
});
vi.spyOn(fs, 'readFileSync').mockImplementation((path) => {
if (path === 'deno.json') {
return JSON.stringify(expectedDeno);
}
return '';
});
const actualReadDeno = ExpandWildcard.readDeno();
expect(actualReadDeno).toEqual(expectedDeno);
});
it('can read deno.jsonc', () => {
const expectedDeno = {
name: 'deno',
version: '1.14.0',
};
vi.spyOn(fs, 'existsSync').mockImplementation((path) => {
return path === 'deno.jsonc';
});
vi.spyOn(fs, 'readFileSync').mockImplementation((path) => {
if (path === 'deno.jsonc') {
return `/* comment */\n${JSON.stringify(expectedDeno)}`;
}
return '';
});
const actualReadDeno = ExpandWildcard.readDeno();
expect(actualReadDeno).toEqual(expectedDeno);
});
it('prefers deno.json over deno.jsonc', () => {
const expectedDeno = {
name: 'deno',
version: '1.14.0',
};
vi.spyOn(fs, 'existsSync').mockImplementation((path) => {
return path === 'deno.json' || path === 'deno.jsonc';
});
vi.spyOn(fs, 'readFileSync').mockImplementation((path) => {
if (path === 'deno.json') {
return JSON.stringify(expectedDeno);
}
return '';
});
const actualReadDeno = ExpandWildcard.readDeno();
expect(actualReadDeno).toEqual(expectedDeno);
});
it('can handle errors reading deno', () => {
vi.spyOn(fs, 'existsSync').mockReturnValue(true);
vi.spyOn(fs, 'readFileSync').mockImplementation(() => {
throw new Error('Error reading deno');
});
expect(() => ExpandWildcard.readDeno()).not.toThrow();
expect(ExpandWildcard.readDeno()).toEqual({});
});
});
describe('#readPackage()', () => {
it('can read package', () => {
const expectedPackage = {
name: 'concurrently',
version: '6.4.0',
};
vi.spyOn(fs, 'readFileSync').mockImplementation((path) => {
if (path === 'package.json') {
return JSON.stringify(expectedPackage);
}
return '';
});
const actualReadPackage = ExpandWildcard.readPackage();
expect(actualReadPackage).toEqual(expectedPackage);
});
it('can handle errors reading package', () => {
vi.spyOn(fs, 'readFileSync').mockImplementation(() => {
throw new Error('Error reading package');
});
expect(() => ExpandWildcard.readPackage()).not.toThrow();
expect(ExpandWildcard.readPackage()).toEqual({});
});
});
it('returns same command if not an npm run command', () => {
const commandInfo = createCommandInfo('npm test');
expect(readDeno).not.toHaveBeenCalled();
expect(readPackage).not.toHaveBeenCalled();
expect(parser.parse(commandInfo)).toBe(commandInfo);
});
it('returns same command if not a deno task command', () => {
const commandInfo = createCommandInfo('deno run');
expect(readDeno).not.toHaveBeenCalled();
expect(readPackage).not.toHaveBeenCalled();
expect(parser.parse(commandInfo)).toBe(commandInfo);
});
it('returns same command if no wildcard present', () => {
const commandInfo = createCommandInfo('npm run foo bar');
expect(readPackage).not.toHaveBeenCalled();
expect(parser.parse(commandInfo)).toBe(commandInfo);
});
it('expands to nothing if no scripts exist in package.json', () => {
readPackage.mockReturnValue({});
expect(parser.parse(createCommandInfo('npm run foo-*-baz qux'))).toEqual([]);
});
it('expands to nothing if no tasks exist in Deno config and no scripts exist in NodeJS config', () => {
readDeno.mockReturnValue({});
readPackage.mockReturnValue({});
expect(parser.parse(createCommandInfo('deno task foo-*-baz qux'))).toEqual([]);
});
describe.each(['npm run', 'yarn run', 'pnpm run', 'bun run', 'node --run'])(`with a '%s' prefix`, (command) => {
it('expands to all scripts matching pattern', () => {
readPackage.mockReturnValue({
scripts: {
'foo-bar-baz': '',
'foo--baz': '',
},
});
expect(parser.parse(createCommandInfo(`${command} foo-*-baz qux`))).toEqual([
{ name: 'bar', command: `${command} foo-bar-baz qux` },
{ name: '', command: `${command} foo--baz qux` },
]);
});
it('uses wildcard match of script as command name', () => {
readPackage.mockReturnValue({
scripts: {
'watch-js': '',
'watch-css': '',
},
});
expect(parser.parse({
name: 'watch-*',
command: `${command} watch-*`,
})).toEqual([
{ name: 'js', command: `${command} watch-js` },
{ name: 'css', command: `${command} watch-css` },
]);
});
it('uses existing command name as prefix to the wildcard match', () => {
readPackage.mockReturnValue({
scripts: {
'watch-js': '',
'watch-css': '',
},
});
expect(parser.parse({
name: 'w:',
command: `${command} watch-*`,
})).toEqual([
{ name: 'w:js', command: `${command} watch-js` },
{ name: 'w:css', command: `${command} watch-css` },
]);
});
it('allows negation', () => {
readPackage.mockReturnValue({
scripts: {
'lint:js': '',
'lint:ts': '',
'lint:fix:js': '',
'lint:fix:ts': '',
},
});
expect(parser.parse(createCommandInfo(`${command} lint:*(!fix)`))).toEqual([
{ name: 'js', command: `${command} lint:js` },
{ name: 'ts', command: `${command} lint:ts` },
]);
});
it('caches scripts upon calls', () => {
readPackage.mockReturnValue({});
parser.parse(createCommandInfo(`${command} foo-*-baz qux`));
parser.parse(createCommandInfo(`${command} foo-*-baz qux`));
expect(readPackage).toHaveBeenCalledTimes(1);
});
it("doesn't read Deno config", () => {
readPackage.mockReturnValue({});
parser.parse(createCommandInfo(`${command} foo-*-baz qux`));
expect(readDeno).not.toHaveBeenCalled();
});
});
describe(`with a 'deno task' prefix`, () => {
it('expands to all scripts matching pattern', () => {
readDeno.mockReturnValue({
tasks: {
'foo-bar-baz': '',
'foo--baz': '',
},
});
readPackage.mockReturnValue({
scripts: {
'foo-foo-baz': '',
},
});
expect(parser.parse(createCommandInfo(`deno task foo-*-baz qux`))).toEqual([
{ name: 'bar', command: `deno task foo-bar-baz qux` },
{ name: '', command: `deno task foo--baz qux` },
{ name: 'foo', command: `deno task foo-foo-baz qux` },
]);
});
it('uses wildcard match of script as command name', () => {
readDeno.mockReturnValue({
tasks: {
'watch-sass': '',
},
});
readPackage.mockReturnValue({
scripts: {
'watch-js': '',
'watch-css': '',
},
});
expect(parser.parse({
name: '',
command: `deno task watch-*`,
})).toEqual([
{ name: 'sass', command: `deno task watch-sass` },
{ name: 'js', command: `deno task watch-js` },
{ name: 'css', command: `deno task watch-css` },
]);
});
it('uses existing command name as prefix to the wildcard match', () => {
readDeno.mockReturnValue({
tasks: {
'watch-sass': '',
},
});
readPackage.mockReturnValue({
scripts: {
'watch-js': '',
'watch-css': '',
},
});
expect(parser.parse({
name: 'w:',
command: `deno task watch-*`,
})).toEqual([
{ name: 'w:sass', command: `deno task watch-sass` },
{ name: 'w:js', command: `deno task watch-js` },
{ name: 'w:css', command: `deno task watch-css` },
]);
});
it('allows negation', () => {
readDeno.mockReturnValue({
tasks: {
'lint:sass': '',
'lint:fix:sass': '',
},
});
readPackage.mockReturnValue({
scripts: {
'lint:js': '',
'lint:ts': '',
'lint:fix:js': '',
'lint:fix:ts': '',
},
});
expect(parser.parse(createCommandInfo(`deno task lint:*(!fix)`))).toEqual([
{ name: 'sass', command: `deno task lint:sass` },
{ name: 'js', command: `deno task lint:js` },
{ name: 'ts', command: `deno task lint:ts` },
]);
});
it('caches scripts upon calls', () => {
readDeno.mockReturnValue({});
readPackage.mockReturnValue({});
parser.parse(createCommandInfo(`deno task foo-*-baz qux`));
parser.parse(createCommandInfo(`deno task foo-*-baz qux`));
expect(readDeno).toHaveBeenCalledTimes(1);
expect(readPackage).toHaveBeenCalledTimes(1);
});
});