Merge setup/test-infrastructure: vitest config, shared fixtures, 30 tests passing
This commit is contained in:
313
test/fixtures/graphs.ts
vendored
Normal file
313
test/fixtures/graphs.ts
vendored
Normal file
@@ -0,0 +1,313 @@
|
||||
/**
|
||||
* Shared test fixtures for graph construction.
|
||||
*
|
||||
* Provides:
|
||||
* - TaskInput type matching the architecture spec (inline until schema module is implemented)
|
||||
* - createTaskGraph() helper for one-liner graph setup from TaskInput[]
|
||||
* - Pre-built graph fixtures for common test patterns
|
||||
*
|
||||
* When the schema and graph modules are implemented, these fixtures will work
|
||||
* with TaskGraph.fromTasks() directly. Until then, createTaskGraph() builds
|
||||
* a graphology DirectedGraph with the same semantics.
|
||||
*/
|
||||
|
||||
import Graph from 'graphology';
|
||||
import { hasCycle } from 'graphology-dag';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Types — mirrors src/schema/task.ts and src/schema/graph.ts architecture
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Categorical enum values matching DB and frontmatter conventions */
|
||||
export type TaskScope = 'single' | 'narrow' | 'moderate' | 'broad' | 'system';
|
||||
export type TaskRisk = 'trivial' | 'low' | 'medium' | 'high' | 'critical';
|
||||
export type TaskImpact = 'isolated' | 'component' | 'phase' | 'project';
|
||||
export type TaskLevel = 'planning' | 'decomposition' | 'implementation' | 'review' | 'research';
|
||||
export type TaskPriority = 'low' | 'medium' | 'high' | 'critical';
|
||||
export type TaskStatus = 'pending' | 'in-progress' | 'completed' | 'failed' | 'blocked';
|
||||
|
||||
/**
|
||||
* Universal input shape for a task.
|
||||
* Mirrors the TaskInput schema from docs/architecture/schemas.md.
|
||||
* Categorical fields are optional and nullable (null = "not yet assessed").
|
||||
*/
|
||||
export interface TaskInput {
|
||||
id: string;
|
||||
name: string;
|
||||
dependsOn: string[];
|
||||
status?: TaskStatus | null;
|
||||
scope?: TaskScope | null;
|
||||
risk?: TaskRisk | null;
|
||||
impact?: TaskImpact | null;
|
||||
level?: TaskLevel | null;
|
||||
priority?: TaskPriority | null;
|
||||
tags?: string[];
|
||||
assignee?: string | null;
|
||||
due?: string | null;
|
||||
created?: string | null;
|
||||
modified?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Node attributes stored on the graphology graph.
|
||||
* Mirrors TaskGraphNodeAttributes from docs/architecture/schemas.md.
|
||||
* After construction, null values are stripped to undefined (absent = "not assessed").
|
||||
*/
|
||||
export interface TaskGraphNodeAttributes {
|
||||
name: string;
|
||||
scope?: TaskScope;
|
||||
risk?: TaskRisk;
|
||||
impact?: TaskImpact;
|
||||
level?: TaskLevel;
|
||||
priority?: TaskPriority;
|
||||
status?: TaskStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Edge attributes on graph edges.
|
||||
* qualityRetention defaults to 0.9 if not specified.
|
||||
*/
|
||||
export interface TaskGraphEdgeAttributes {
|
||||
qualityRetention?: number;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: strip null → undefined for node attributes
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function stripNulls(input: TaskInput): TaskGraphNodeAttributes {
|
||||
const attrs: TaskGraphNodeAttributes = { name: input.name };
|
||||
if (input.scope != null) attrs.scope = input.scope;
|
||||
if (input.risk != null) attrs.risk = input.risk;
|
||||
if (input.impact != null) attrs.impact = input.impact;
|
||||
if (input.level != null) attrs.level = input.level;
|
||||
if (input.priority != null) attrs.priority = input.priority;
|
||||
if (input.status != null) attrs.status = input.status;
|
||||
return attrs;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: create a graphology DirectedGraph from TaskInput[]
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Build a graphology DirectedGraph from an array of TaskInputobjects.
|
||||
*
|
||||
* Uses graph.import(serializedData) for bulk construction (faster than N
|
||||
* individual addNode/addEdge calls per architecture recommendation).
|
||||
*
|
||||
* Edges use deterministic `${source}->${target}` keys per ADR-006.
|
||||
* Edge qualityRetention defaults to 0.9 (matching fromTasks convention).
|
||||
*
|
||||
* @param tasks - Array of TaskInput objects
|
||||
* @returns A graphology DirectedGraph with nodes and edges populated
|
||||
*/
|
||||
export function createTaskGraph(tasks: TaskInput[]): Graph<TaskGraphNodeAttributes, TaskGraphEdgeAttributes> {
|
||||
const graph = new Graph<TaskGraphNodeAttributes, TaskGraphEdgeAttributes>({ type: 'directed' });
|
||||
|
||||
// Build node map for lookups
|
||||
const taskMap = new Map<string, TaskInput>();
|
||||
for (const task of tasks) {
|
||||
taskMap.set(task.id, task);
|
||||
}
|
||||
|
||||
// Build serialized format for bulk import
|
||||
const nodes: Array<{ key: string; attributes: TaskGraphNodeAttributes }> = [];
|
||||
const edges: Array<{ key: string; source: string; target: string; attributes: TaskGraphEdgeAttributes }> = [];
|
||||
|
||||
// Edge set to prevent duplicates
|
||||
const edgeSet = new Set<string>();
|
||||
|
||||
for (const task of tasks) {
|
||||
nodes.push({ key: task.id, attributes: stripNulls(task) });
|
||||
|
||||
for (const dep of task.dependsOn) {
|
||||
const edgeKey = `${dep}->${task.id}`;
|
||||
if (!edgeSet.has(edgeKey)) {
|
||||
edgeSet.add(edgeKey);
|
||||
edges.push({
|
||||
key: edgeKey,
|
||||
source: dep,
|
||||
target: task.id,
|
||||
attributes: { qualityRetention: 0.9 },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Import in bulk
|
||||
graph.import({ nodes, edges });
|
||||
|
||||
// Handle dangling references: fromTasks silently creates orphan nodes
|
||||
// for dependsOn targets not in the tasks array (per architecture spec).
|
||||
// We need to add those nodes now.
|
||||
for (const task of tasks) {
|
||||
for (const dep of task.dependsOn) {
|
||||
if (!graph.hasNode(dep)) {
|
||||
graph.addNode(dep, { name: dep });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Fixture Graphs
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Simple linear chain graph: A → B → C → D
|
||||
*
|
||||
* All tasks have medium risk, narrow scope, isolated impact.
|
||||
* Useful for testing topological ordering, basic path traversal.
|
||||
*/
|
||||
export const linearChainTasks: TaskInput[] = [
|
||||
{ id: 'A', name: 'Task A', dependsOn: [] },
|
||||
{ id: 'B', name: 'Task B', dependsOn: ['A'] },
|
||||
{ id: 'C', name: 'Task C', dependsOn: ['B'] },
|
||||
{ id: 'D', name: 'Task D', dependsOn: ['C'] },
|
||||
];
|
||||
|
||||
/** Pre-built linear chain graph */
|
||||
export const linearChain = createTaskGraph(linearChainTasks);
|
||||
|
||||
/**
|
||||
* Diamond dependency graph:
|
||||
* A
|
||||
* / \
|
||||
* B C
|
||||
* \ /
|
||||
* D
|
||||
*
|
||||
* A → B, A → C, B → D, C → D
|
||||
* Useful for testing parallel groups, bottleneck detection, merge points.
|
||||
*/
|
||||
export const diamondTasks: TaskInput[] = [
|
||||
{ id: 'A', name: 'Task A', dependsOn: [] },
|
||||
{ id: 'B', name: 'Task B', dependsOn: ['A'] },
|
||||
{ id: 'C', name: 'Task C', dependsOn: ['A'] },
|
||||
{ id: 'D', name: 'Task D', dependsOn: ['B', 'C'] },
|
||||
];
|
||||
|
||||
/** Pre-built diamond graph */
|
||||
export const diamond = createTaskGraph(diamondTasks);
|
||||
|
||||
/**
|
||||
* Mixed categorical fields graph:
|
||||
* Some tasks have assessed fields, some have null (not assessed).
|
||||
* Useful for testing resolveDefaults, riskDistribution, and other
|
||||
* analysis functions that handle nullable categorical fields.
|
||||
*/
|
||||
export const mixedCategoryTasks: TaskInput[] = [
|
||||
{ id: 'auth', name: 'Auth module', dependsOn: [], risk: 'high', scope: 'broad', impact: 'phase', status: 'pending' },
|
||||
{ id: 'db', name: 'Database setup', dependsOn: [], risk: 'medium', scope: 'moderate', impact: null, status: 'completed' },
|
||||
{ id: 'api', name: 'API layer', dependsOn: ['auth', 'db'], risk: null, scope: null, impact: 'component', status: null },
|
||||
{ id: 'tests', name: 'Test suite', dependsOn: ['api'], risk: 'low', scope: null, impact: null, status: null },
|
||||
{ id: 'deploy', name: 'Deploy pipeline', dependsOn: ['tests'], risk: 'critical', scope: 'system', impact: 'project', status: 'blocked' },
|
||||
];
|
||||
|
||||
/** Pre-built mixed category graph */
|
||||
export const mixedCategory = createTaskGraph(mixedCategoryTasks);
|
||||
|
||||
/**
|
||||
* Graph with cycles for testing cycle detection:
|
||||
* A → B → C → A (cycle)
|
||||
* A → D (non-cyclic branch)
|
||||
*
|
||||
* Useful for testing hasCycles(), findCycles(), and that
|
||||
* topologicalOrder() throws CircularDependencyError.
|
||||
*/
|
||||
export const cyclicTasks: TaskInput[] = [
|
||||
{ id: 'A', name: 'Task A', dependsOn: ['C'] }, // A depends on C (creates cycle)
|
||||
{ id: 'B', name: 'Task B', dependsOn: ['A'] }, // B depends on A
|
||||
{ id: 'C', name: 'Task C', dependsOn: ['B'] }, // C depends on B
|
||||
{ id: 'D', name: 'Task D', dependsOn: ['A'] }, // D depends on A (non-cyclic)
|
||||
];
|
||||
|
||||
/** Pre-built cyclic graph */
|
||||
export const cyclic = createTaskGraph(cyclicTasks);
|
||||
|
||||
/**
|
||||
* Larger graph (20+ nodes) for performance and bottleneck testing.
|
||||
* Represents a realistic project structure with multiple parallel
|
||||
* workstreams converging.
|
||||
*
|
||||
* Structure:
|
||||
* - 3 foundation tasks (no deps)
|
||||
* - 3 core services (depend on foundation)
|
||||
* - 5 feature tasks (depend on core services)
|
||||
* - 3 integration tasks (merge features)
|
||||
* - 2 testing tasks
|
||||
* - 5 polish tasks
|
||||
* - 1 release task (depends on everything)
|
||||
*/
|
||||
export const largeGraphTasks: TaskInput[] = [
|
||||
// Foundation (3 tasks)
|
||||
{ id: 'infra-setup', name: 'Infrastructure setup', dependsOn: [], scope: 'broad', risk: 'high', impact: 'project', level: 'implementation' },
|
||||
{ id: 'db-schema', name: 'Database schema design', dependsOn: [], scope: 'moderate', risk: 'medium', impact: 'phase', level: 'planning' },
|
||||
{ id: 'auth-design', name: 'Auth system design', dependsOn: [], scope: 'moderate', risk: 'high', impact: 'component', level: 'planning' },
|
||||
|
||||
// Core services (3 tasks)
|
||||
{ id: 'auth-impl', name: 'Auth implementation', dependsOn: ['infra-setup', 'auth-design'], scope: 'broad', risk: 'high', impact: 'phase', level: 'implementation' },
|
||||
{ id: 'data-layer', name: 'Data access layer', dependsOn: ['db-schema', 'infra-setup'], scope: 'moderate', risk: 'medium', impact: 'component', level: 'implementation' },
|
||||
{ id: 'api-gateway', name: 'API gateway', dependsOn: ['auth-impl', 'data-layer'], scope: 'broad', risk: 'medium', impact: 'phase', level: 'implementation' },
|
||||
|
||||
// Feature tasks (4 tasks)
|
||||
{ id: 'feature-users', name: 'User management', dependsOn: ['auth-impl', 'data-layer'], scope: 'narrow', risk: 'low', impact: 'component', level: 'implementation' },
|
||||
{ id: 'feature-notifications', name: 'Notification system', dependsOn: ['api-gateway', 'data-layer'], scope: 'narrow', risk: 'low', impact: 'isolated', level: 'implementation' },
|
||||
{ id: 'feature-search', name: 'Search functionality', dependsOn: ['data-layer'], scope: 'moderate', risk: 'medium', impact: 'component', level: 'implementation' },
|
||||
{ id: 'feature-permissions', name: 'Permissions system', dependsOn: ['auth-impl'], scope: 'moderate', risk: 'high', impact: 'phase', level: 'implementation' },
|
||||
{ id: 'feature-analytics', name: 'Analytics dashboard', dependsOn: ['data-layer', 'api-gateway'], scope: 'moderate', risk: 'medium', impact: 'component', level: 'implementation' },
|
||||
|
||||
// Integration tasks (3 tasks)
|
||||
{ id: 'integrate-auth', name: 'Auth integration test', dependsOn: ['feature-users', 'feature-permissions'], scope: 'narrow', risk: 'medium', impact: 'component', level: 'review' },
|
||||
{ id: 'integrate-api', name: 'API integration test', dependsOn: ['feature-notifications', 'feature-search', 'api-gateway'], scope: 'moderate', risk: 'medium', impact: 'phase', level: 'review' },
|
||||
{ id: 'integrate-e2e', name: 'End-to-end integration', dependsOn: ['integrate-auth', 'integrate-api'], scope: 'broad', risk: 'high', impact: 'project', level: 'review' },
|
||||
|
||||
// Testing tasks (2 tasks)
|
||||
{ id: 'perf-tests', name: 'Performance testing', dependsOn: ['integrate-e2e'], scope: 'moderate', risk: 'medium', impact: 'component', level: 'review' },
|
||||
{ id: 'security-audit', name: 'Security audit', dependsOn: ['auth-impl', 'integrate-auth'], scope: 'broad', risk: 'critical', impact: 'project', level: 'review' },
|
||||
|
||||
// Polish tasks (3 tasks)
|
||||
{ id: 'docs-api', name: 'API documentation', dependsOn: ['api-gateway'], scope: 'moderate', risk: 'trivial', impact: 'isolated', level: 'implementation' },
|
||||
{ id: 'docs-user', name: 'User documentation', dependsOn: ['feature-users'], scope: 'narrow', risk: 'trivial', impact: 'isolated', level: 'implementation' },
|
||||
{ id: 'i18n', name: 'Internationalization', dependsOn: ['feature-users', 'feature-notifications'], scope: 'moderate', risk: 'low', impact: 'component', level: 'implementation' },
|
||||
{ id: 'accessibility', name: 'Accessibility compliance', dependsOn: ['feature-users', 'feature-analytics'], scope: 'moderate', risk: 'low', impact: 'component', level: 'implementation' },
|
||||
{ id: 'error-handling', name: 'Error handling polish', dependsOn: ['api-gateway', 'data-layer'], scope: 'narrow', risk: 'low', impact: 'isolated', level: 'implementation' },
|
||||
{ id: 'config-system', name: 'Configuration system', dependsOn: ['data-layer'], scope: 'narrow', risk: 'low', impact: 'isolated', level: 'implementation' },
|
||||
|
||||
// Release (1 task)
|
||||
{ id: 'release', name: 'Production release', dependsOn: ['perf-tests', 'security-audit', 'docs-api', 'docs-user', 'i18n', 'accessibility', 'error-handling', 'config-system'], scope: 'system', risk: 'critical', impact: 'project', level: 'implementation' },
|
||||
];
|
||||
|
||||
/** Pre-built large graph (23 nodes) */
|
||||
export const largeGraph = createTaskGraph(largeGraphTasks);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Re-exports for convenience
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* All fixture graphs as a record for iteration in tests.
|
||||
* Keys: 'linearChain', 'diamond', 'mixedCategory', 'cyclic', 'large'
|
||||
*/
|
||||
export const allGraphs: Record<string, Graph<TaskGraphNodeAttributes, TaskGraphEdgeAttributes>> = {
|
||||
linearChain,
|
||||
diamond,
|
||||
mixedCategory,
|
||||
cyclic,
|
||||
large: largeGraph,
|
||||
};
|
||||
|
||||
/**
|
||||
* All fixture TaskInput arrays as a record for iteration in tests.
|
||||
* Keys: 'linearChain', 'diamond', 'mixedCategory', 'cyclic', 'large'
|
||||
*/
|
||||
export const allTasks: Record<string, TaskInput[]> = {
|
||||
linearChain: linearChainTasks,
|
||||
diamond: diamondTasks,
|
||||
mixedCategory: mixedCategoryTasks,
|
||||
cyclic: cyclicTasks,
|
||||
large: largeGraphTasks,
|
||||
};
|
||||
@@ -1,7 +1,171 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { hasCycle } from 'graphology-dag';
|
||||
import {
|
||||
createTaskGraph,
|
||||
linearChainTasks,
|
||||
linearChain,
|
||||
diamondTasks,
|
||||
diamond,
|
||||
mixedCategoryTasks,
|
||||
mixedCategory,
|
||||
cyclicTasks,
|
||||
cyclic,
|
||||
largeGraphTasks,
|
||||
largeGraph,
|
||||
allGraphs,
|
||||
allTasks,
|
||||
} from './fixtures/graphs.js';
|
||||
|
||||
describe('TaskGraph', () => {
|
||||
it('placeholder — construction and queries', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test Fixtures', () => {
|
||||
describe('linearChain', () => {
|
||||
it('has 4 nodes', () => {
|
||||
expect(linearChain.order).toBe(4);
|
||||
});
|
||||
|
||||
it('has 3 edges (A→B, B→C, C→D)', () => {
|
||||
expect(linearChain.size).toBe(3);
|
||||
});
|
||||
|
||||
it('has correct task IDs', () => {
|
||||
expect(linearChainTasks.map(t => t.id)).toEqual(['A', 'B', 'C', 'D']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('diamond', () => {
|
||||
it('has 4 nodes', () => {
|
||||
expect(diamond.order).toBe(4);
|
||||
});
|
||||
|
||||
it('has 4 edges (A→B, A→C, B→D, C→D)', () => {
|
||||
expect(diamond.size).toBe(4);
|
||||
});
|
||||
|
||||
it('A has two dependents (B, C)', () => {
|
||||
expect(diamond.outNeighbors('A')).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('D has two prerequisites (B, C)', () => {
|
||||
expect(diamond.inNeighbors('D')).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mixedCategory', () => {
|
||||
it('has 5 nodes', () => {
|
||||
expect(mixedCategory.order).toBe(5);
|
||||
});
|
||||
|
||||
it('stores assessed categorical fields', () => {
|
||||
const authAttrs = mixedCategory.getNodeAttributes('auth');
|
||||
expect(authAttrs.risk).toBe('high');
|
||||
expect(authAttrs.scope).toBe('broad');
|
||||
});
|
||||
|
||||
it('strips null categorical fields (absent = not assessed)', () => {
|
||||
const apiAttrs = mixedCategory.getNodeAttributes('api');
|
||||
expect(apiAttrs.risk).toBeUndefined();
|
||||
expect(apiAttrs.scope).toBeUndefined();
|
||||
});
|
||||
|
||||
it('preserves non-null optional fields', () => {
|
||||
const apiAttrs = mixedCategory.getNodeAttributes('api');
|
||||
expect(apiAttrs.impact).toBe('component');
|
||||
});
|
||||
});
|
||||
|
||||
describe('cyclic', () => {
|
||||
it('has 4 nodes', () => {
|
||||
expect(cyclic.order).toBe(4);
|
||||
});
|
||||
|
||||
it('has 4 edges (C→A, A→B, B→C, A→D)', () => {
|
||||
expect(cyclic.size).toBe(4);
|
||||
});
|
||||
|
||||
it('contains a cycle', () => {
|
||||
// graphology-dag hasCycle check
|
||||
expect(hasCycle(cyclic)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('largeGraph', () => {
|
||||
it('has 23 nodes (20+ for performance testing)', () => {
|
||||
expect(largeGraph.order).toBe(23);
|
||||
});
|
||||
|
||||
it('has more than 20 edges', () => {
|
||||
expect(largeGraph.size).toBeGreaterThan(20);
|
||||
});
|
||||
|
||||
it('release node has 8 prerequisites', () => {
|
||||
expect(largeGraph.inNeighbors('release')).toHaveLength(8);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createTaskGraph helper', () => {
|
||||
it('builds a graph from TaskInput[]', () => {
|
||||
const tasks = [
|
||||
{ id: 'x', name: 'Task X', dependsOn: [] },
|
||||
{ id: 'y', name: 'Task Y', dependsOn: ['x'] },
|
||||
];
|
||||
const graph = createTaskGraph(tasks);
|
||||
expect(graph.order).toBe(2);
|
||||
expect(graph.size).toBe(1);
|
||||
});
|
||||
|
||||
it('handles empty task array', () => {
|
||||
const graph = createTaskGraph([]);
|
||||
expect(graph.order).toBe(0);
|
||||
expect(graph.size).toBe(0);
|
||||
});
|
||||
|
||||
it('uses deterministic edge keys', () => {
|
||||
const graph = createTaskGraph([
|
||||
{ id: 'a', name: 'A', dependsOn: [] },
|
||||
{ id: 'b', name: 'B', dependsOn: ['a'] },
|
||||
]);
|
||||
expect(graph.hasEdge('a->b')).toBe(true);
|
||||
});
|
||||
|
||||
it('sets default qualityRetention 0.9 on edges', () => {
|
||||
const graph = createTaskGraph([
|
||||
{ id: 'a', name: 'A', dependsOn: [] },
|
||||
{ id: 'b', name: 'B', dependsOn: ['a'] },
|
||||
]);
|
||||
const edgeAttrs = graph.getEdgeAttributes('a->b');
|
||||
expect(edgeAttrs.qualityRetention).toBe(0.9);
|
||||
});
|
||||
|
||||
it('deduplicates edges when same dependency appears twice', () => {
|
||||
const tasks = [
|
||||
{ id: 'a', name: 'A', dependsOn: [] },
|
||||
{ id: 'b', name: 'B', dependsOn: ['a', 'a'] },
|
||||
];
|
||||
const graph = createTaskGraph(tasks);
|
||||
expect(graph.size).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('allGraphs / allTasks convenience exports', () => {
|
||||
it('allGraphs contains 5 fixtures', () => {
|
||||
expect(Object.keys(allGraphs)).toHaveLength(5);
|
||||
});
|
||||
|
||||
it('allTasks contains 5 task arrays', () => {
|
||||
expect(Object.keys(allTasks)).toHaveLength(5);
|
||||
});
|
||||
|
||||
it('each graph has matching task array', () => {
|
||||
expect(allGraphs.linearChain.order).toBe(linearChainTasks.length);
|
||||
expect(allGraphs.diamond.order).toBe(diamondTasks.length);
|
||||
expect(allGraphs.mixedCategory.order).toBe(mixedCategoryTasks.length);
|
||||
expect(allGraphs.cyclic.order).toBe(cyclicTasks.length);
|
||||
expect(allGraphs.large.order).toBe(largeGraphTasks.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user