nx/graph/client/src/app/feature-projects/machines/project-graph.spec.ts

457 lines
11 KiB
TypeScript

/* eslint-disable @nx/enforce-module-boundaries */
// nx-ignore-next-line
import type {
ProjectGraphDependency,
ProjectGraphProjectNode,
} from '@nx/devkit';
/* eslint-enable @nx/enforce-module-boundaries */
import { interpret } from 'xstate';
import { projectGraphMachine } from './project-graph.machine';
import { AppConfig } from '../../interfaces';
export const mockProjects: ProjectGraphProjectNode[] = [
{
name: 'app1',
type: 'app',
data: {
root: 'apps/app1',
},
},
{
name: 'app2',
type: 'app',
data: {
root: 'apps/app2',
},
},
{
name: 'ui-lib',
type: 'lib',
data: {
root: 'libs/ui-lib',
},
},
{
name: 'feature-lib1',
type: 'lib',
data: {
root: 'libs/feature/lib1',
},
},
{
name: 'feature-lib2',
type: 'lib',
data: {
root: 'libs/feature/lib2',
},
},
{
name: 'auth-lib',
type: 'lib',
data: {
root: 'libs/auth-lib',
},
},
];
export const mockDependencies: Record<string, ProjectGraphDependency[]> = {
app1: [
{
type: 'static',
source: 'app1',
target: 'auth-lib',
},
{
type: 'static',
source: 'app1',
target: 'feature-lib1',
},
],
app2: [
{
type: 'static',
source: 'app2',
target: 'auth-lib',
},
{
type: 'static',
source: 'app2',
target: 'feature-lib2',
},
],
'feature-lib1': [
{
type: 'static',
source: 'feature-lib1',
target: 'ui-lib',
},
],
'feature-lib2': [
{
type: 'static',
source: 'feature-lib2',
target: 'ui-lib',
},
],
'ui-lib': [],
'auth-lib': [],
};
const mockAppConfig: AppConfig = {
showDebugger: false,
showExperimentalFeatures: false,
workspaces: [
{
id: 'local',
label: 'local',
projectGraphUrl: 'assets/project-graphs/e2e.json',
taskGraphUrl: 'assets/task-graphs/e2e.json',
},
],
defaultWorkspaceId: 'local',
};
describe('dep-graph machine', () => {
beforeEach(() => {
window.appConfig = mockAppConfig;
window.environment = 'release';
});
describe('initGraph', () => {
it('should set projects, dependencies, and workspaceLayout', () => {
const result = projectGraphMachine.transition(
projectGraphMachine.initialState,
{
type: 'setProjects',
projects: mockProjects,
dependencies: mockDependencies,
affectedProjects: [],
fileMap: {},
workspaceLayout: { appsDir: 'apps', libsDir: 'libs' },
}
);
expect(result.context.projects).toEqual(mockProjects);
expect(result.context.dependencies).toEqual(mockDependencies);
expect(result.context.workspaceLayout).toEqual({
appsDir: 'apps',
libsDir: 'libs',
});
});
it('should start with no projects selected', () => {
const result = projectGraphMachine.transition(
projectGraphMachine.initialState,
{
type: 'setProjects',
projects: mockProjects,
dependencies: mockDependencies,
affectedProjects: [],
fileMap: {},
workspaceLayout: { appsDir: 'apps', libsDir: 'libs' },
}
);
expect(result.value).toEqual('unselected');
expect(result.context.selectedProjects).toEqual([]);
});
});
describe('selecting projects', () => {
it('should select projects', (done) => {
let service = interpret(projectGraphMachine).onTransition((state) => {
if (
state.matches('customSelected') &&
state.context.selectedProjects.includes('app1') &&
state.context.selectedProjects.includes('app2')
) {
done();
}
});
service.start();
service.send({
type: 'setProjects',
projects: mockProjects,
dependencies: mockDependencies,
affectedProjects: [],
fileMap: {},
workspaceLayout: { appsDir: 'apps', libsDir: 'libs' },
});
service.send({
type: 'selectProject',
projectName: 'app1',
});
service.send({
type: 'selectProject',
projectName: 'app2',
});
});
});
describe('deselecting projects', () => {
it('should deselect projects', (done) => {
let service = interpret(projectGraphMachine).onTransition((state) => {
if (
state.matches('customSelected') &&
!state.context.selectedProjects.includes('app1') &&
state.context.selectedProjects.includes('app2')
) {
done();
}
});
service.start();
service.send({
type: 'setProjects',
projects: mockProjects,
dependencies: mockDependencies,
affectedProjects: [],
fileMap: {},
workspaceLayout: { appsDir: 'apps', libsDir: 'libs' },
});
service.send({
type: 'selectProject',
projectName: 'app1',
});
service.send({
type: 'selectProject',
projectName: 'app2',
});
service.send({
type: 'deselectProject',
projectName: 'app1',
});
});
it('should go to unselected when last project is deselected', () => {
let result = projectGraphMachine.transition(
projectGraphMachine.initialState,
{
type: 'setProjects',
projects: mockProjects,
dependencies: mockDependencies,
affectedProjects: [],
fileMap: {},
workspaceLayout: { appsDir: 'apps', libsDir: 'libs' },
}
);
result = projectGraphMachine.transition(result, {
type: 'selectProject',
projectName: 'app1',
});
result = projectGraphMachine.transition(result, {
type: 'selectProject',
projectName: 'app2',
});
result = projectGraphMachine.transition(result, {
type: 'deselectProject',
projectName: 'app1',
});
result = projectGraphMachine.transition(result, {
type: 'deselectProject',
projectName: 'app2',
});
expect(result.value).toEqual('unselected');
expect(result.context.selectedProjects).toEqual([]);
});
});
describe('focusing projects', () => {
it('should set the focused project', () => {
let result = projectGraphMachine.transition(
projectGraphMachine.initialState,
{
type: 'setProjects',
projects: mockProjects,
dependencies: mockDependencies,
affectedProjects: [],
fileMap: {},
workspaceLayout: { appsDir: 'apps', libsDir: 'libs' },
}
);
result = projectGraphMachine.transition(result, {
type: 'focusProject',
projectName: 'app1',
});
expect(result.value).toEqual('focused');
expect(result.context.focusedProject).toEqual('app1');
});
it('should select the projects by the focused project', (done) => {
let service = interpret(projectGraphMachine).onTransition((state) => {
if (
state.matches('focused') &&
state.context.selectedProjects.includes('app1') &&
state.context.selectedProjects.includes('ui-lib') &&
state.context.selectedProjects.includes('feature-lib1') &&
state.context.selectedProjects.includes('auth-lib')
) {
done();
}
});
service.start();
service.send({
type: 'setProjects',
projects: mockProjects,
dependencies: mockDependencies,
affectedProjects: [],
fileMap: {},
workspaceLayout: { appsDir: 'apps', libsDir: 'libs' },
});
service.send({
type: 'setSearchDepthEnabled',
searchDepthEnabled: false,
});
service.send({ type: 'focusProject', projectName: 'app1' });
});
it('should select no projects on unfocus', () => {
let result = projectGraphMachine.transition(
projectGraphMachine.initialState,
{
type: 'setProjects',
projects: mockProjects,
dependencies: mockDependencies,
affectedProjects: [],
fileMap: {},
workspaceLayout: { appsDir: 'apps', libsDir: 'libs' },
}
);
result = projectGraphMachine.transition(result, {
type: 'focusProject',
projectName: 'app1',
});
result = projectGraphMachine.transition(result, {
type: 'unfocusProject',
});
expect(result.value).toEqual('unselected');
expect(result.context.selectedProjects).toEqual([]);
});
});
describe('search depth', () => {
it('should not decrement search depth below 1', () => {
let result = projectGraphMachine.transition(
projectGraphMachine.initialState,
{
type: 'setProjects',
projects: mockProjects,
dependencies: mockDependencies,
affectedProjects: [],
fileMap: {},
workspaceLayout: { appsDir: 'apps', libsDir: 'libs' },
}
);
result = projectGraphMachine.transition(result, {
type: 'filterByText',
search: 'app1',
});
expect(result.context.searchDepth).toEqual(1);
result = projectGraphMachine.transition(result, {
type: 'incrementSearchDepth',
});
expect(result.context.searchDepth).toEqual(2);
result = projectGraphMachine.transition(result, {
type: 'incrementSearchDepth',
});
result = projectGraphMachine.transition(result, {
type: 'incrementSearchDepth',
});
expect(result.context.searchDepth).toEqual(4);
result = projectGraphMachine.transition(result, {
type: 'decrementSearchDepth',
});
result = projectGraphMachine.transition(result, {
type: 'decrementSearchDepth',
});
expect(result.context.searchDepth).toEqual(2);
result = projectGraphMachine.transition(result, {
type: 'decrementSearchDepth',
});
expect(result.context.searchDepth).toEqual(1);
result = projectGraphMachine.transition(result, {
type: 'decrementSearchDepth',
});
expect(result.context.searchDepth).toEqual(1);
result = projectGraphMachine.transition(result, {
type: 'decrementSearchDepth',
});
expect(result.context.searchDepth).toEqual(1);
});
it('should activate search depth if incremented or decremented', () => {
let result = projectGraphMachine.transition(
projectGraphMachine.initialState,
{
type: 'setProjects',
projects: mockProjects,
dependencies: mockDependencies,
affectedProjects: [],
fileMap: {},
workspaceLayout: { appsDir: 'apps', libsDir: 'libs' },
}
);
result = projectGraphMachine.transition(result, {
type: 'setSearchDepthEnabled',
searchDepthEnabled: false,
});
expect(result.context.searchDepthEnabled).toBe(false);
result = projectGraphMachine.transition(result, {
type: 'incrementSearchDepth',
});
expect(result.context.searchDepthEnabled).toBe(true);
result = projectGraphMachine.transition(result, {
type: 'setSearchDepthEnabled',
searchDepthEnabled: false,
});
expect(result.context.searchDepthEnabled).toBe(false);
result = projectGraphMachine.transition(result, {
type: 'decrementSearchDepth',
});
expect(result.context.searchDepthEnabled).toBe(true);
});
});
});