nx/packages/nx/src/utils/find-matching-projects.spec.ts
2024-01-18 19:08:15 +01:00

222 lines
5.4 KiB
TypeScript

import {
findMatchingProjects,
getMatchingStringsWithCache,
} from './find-matching-projects';
import type { ProjectGraphProjectNode } from '../config/project-graph';
import { minimatch } from 'minimatch';
describe('findMatchingProjects', () => {
let projectGraph: Record<string, ProjectGraphProjectNode> = {
'test-project': {
name: 'test-project',
type: 'lib',
data: {
root: 'lib/test-project',
tags: ['api', 'theme1'],
},
},
a: {
name: 'a',
type: 'lib',
data: {
root: 'lib/a',
tags: ['api', 'theme2'],
},
},
b: {
name: 'b',
type: 'lib',
data: {
root: 'lib/b',
tags: ['ui'],
},
},
c: {
name: 'c',
type: 'app',
data: {
root: 'apps/c',
tags: ['api'],
},
},
nested: {
name: 'nested',
type: 'lib',
data: {
root: 'lib/shared/nested',
tags: [],
},
},
};
it('should return no projects when passed no patterns', () => {
expect(findMatchingProjects([], projectGraph)).toEqual([]);
});
it('should return no projects when passed empty string', () => {
expect(findMatchingProjects([''], projectGraph)).toEqual([]);
});
it('should not throw when a pattern is empty string', () => {
expect(findMatchingProjects(['', 'a'], projectGraph)).toEqual(['a']);
});
it('should expand "*"', () => {
expect(findMatchingProjects(['*'], projectGraph)).toEqual([
'test-project',
'a',
'b',
'c',
'nested',
]);
});
it('should support negation "!"', () => {
expect(findMatchingProjects(['*', '!a'], projectGraph)).toEqual([
'test-project',
'b',
'c',
'nested',
]);
expect(findMatchingProjects(['a', '!*'], projectGraph)).toEqual([]);
});
it('should expand generic glob patterns', () => {
const projectGraphMod: typeof projectGraph = {
...projectGraph,
'b-1': {
name: 'b-1',
type: 'lib',
data: {
root: 'lib/b-1',
tags: [],
},
},
'b-2': {
name: 'b-2',
type: 'lib',
data: {
root: 'lib/b-2',
tags: [],
},
},
};
expect(findMatchingProjects(['b*'], projectGraphMod)).toEqual([
'b',
'b-1',
'b-2',
]);
});
it('should support projectNames', () => {
expect(findMatchingProjects(['a', 'b'], projectGraph)).toEqual(['a', 'b']);
});
it('should expand "*" for tags', () => {
expect(findMatchingProjects(['tag:*'], projectGraph)).toEqual([
'test-project',
'a',
'b',
'c',
'nested',
]);
});
it('should support negation "!" for tags', () => {
// Picks everything, except things tagged API, unless those also
// have the tag theme2 in which case we still want them.
const matches = findMatchingProjects(
['*', '!tag:api', 'tag:theme2'],
projectGraph
);
expect(matches).toEqual(expect.arrayContaining(['a', 'b', 'nested']));
expect(matches.length).toEqual(3);
});
it('should expand generic glob patterns for tags', () => {
expect(findMatchingProjects(['tag:theme*'], projectGraph)).toEqual([
'test-project',
'a',
]);
});
it('should support mixed projectNames and tags', () => {
expect(findMatchingProjects(['a', 'tag:ui'], projectGraph)).toEqual([
'a',
'b',
]);
expect(
findMatchingProjects(['tag:api', '!tag:theme2'], projectGraph)
).toEqual(['test-project', 'c']);
});
it('should support glob patterns for project roots', () => {
expect(findMatchingProjects(['lib/*'], projectGraph)).toEqual([
'test-project',
'a',
'b',
]);
expect(findMatchingProjects(['apps/*'], projectGraph)).toEqual(['c']);
expect(findMatchingProjects(['**/nested'], projectGraph)).toEqual([
'nested',
]);
});
});
const projects = [
'shop-client',
'shop-api',
'cart-api',
'cart-client',
'shop-ui',
'cart-ui',
'shop-e2e',
'cart-e2e',
];
const roots = projects
.map((x) => `apps/${x}`)
.concat(projects.map((x) => `libs/${x}`));
describe.each([
{
items: projects,
pattern: 'cart-*',
},
{
items: projects,
pattern: '*-ui',
},
{
items: roots,
pattern: 'libs/*',
},
])('getMatchingStringsWithCache', ({ items, pattern }) => {
it(`should be faster than using minimatch directly multiple times (${pattern})`, () => {
const iterations = 100;
const cacheTime = time(
() => getMatchingStringsWithCache(pattern, items),
iterations
);
const directTime = time(() => minimatch.match(items, pattern), iterations);
// Using minimatch directly is slower than using the cache.
expect(directTime / cacheTime).toBeGreaterThan(1);
});
it(`should be comparable to using minimatch a single time (${pattern})`, () => {
const cacheTime = time(() => getMatchingStringsWithCache(pattern, items));
const directTime = time(() => minimatch.match(items, pattern));
// We are dealing with really small file sets here, with such a small
// difference it time, the system variablility can make this flaky for
// smaller values. If we are within 1ms, we are good.
expect(cacheTime).toBeLessThan(directTime + 1);
});
});
function time(fn: () => void, iterations = 1) {
const start = performance.now();
for (let i = 0; i < iterations; i++) fn();
return performance.now() - start;
}