// Shared project fixtures for level-core / level-blocks tests: a Mon–Fri // calendar, task/project builders, and run helpers mirroring the local // builders in serialSGS.test.ts (kept here so new suites stop copying them). import { expect } from "bun:test"; import { resolveCalendar } from "../../src/level-core/resolveCalendar.ts"; import { ScheduleStreamImpl } from "../../src/level-core/scheduleStream.ts"; import { serialSGS } from "../../src/level-core/search/serialSGS.ts"; import { ResourceType } from "../../src/model/types.ts"; import type { Constraint, Failure, Schedule, WorkUnit, } from "../../src/level-core/types.ts"; import type { Calendar } from "../../src/schema/calendar.ts"; import type { ProjectFile } from "../../src/schema/project.ts"; import type { Relation } from "../../src/schema/relation.ts"; import type { Task } from "../../src/schema/task.ts"; export function monFriCalendar(uniqueId = 1): Calendar { return { uniqueId, name: "Standard", weekDays: [ { dayType: 1, working: false, workingTimes: [] }, { dayType: 2, working: true, workingTimes: [] }, { dayType: 3, working: true, workingTimes: [] }, { dayType: 4, working: true, workingTimes: [] }, { dayType: 5, working: true, workingTimes: [] }, { dayType: 6, working: true, workingTimes: [] }, { dayType: 7, working: false, workingTimes: [] }, ], exceptions: [], }; } export function makeTask(args: { uniqueId: number; start: Date; finish: Date; milestone?: boolean; predecessors?: Relation[]; }): Task { return { id: null, uniqueId: args.uniqueId, name: `Task ${String(args.uniqueId)}`, wbs: null, outlineLevel: 0, start: args.start, finish: args.finish, duration: null, percentComplete: null, summary: false, milestone: args.milestone ?? false, critical: null, notes: null, priority: null, cost: null, work: null, actualStart: null, actualFinish: null, baselineStart: null, baselineFinish: null, baselineDuration: null, actualWork: null, constraintType: null, freeSlack: null, totalSlack: null, earlyStart: null, earlyFinish: null, lateStart: null, lateFinish: null, levelingDelay: null, deadline: null, splits: null, predecessors: args.predecessors ?? [], }; } export function makeProject( tasks: Task[], options: { calendars?: Calendar[]; resources?: ProjectFile["resources"]; assignments?: ProjectFile["assignments"]; } = {}, ): ProjectFile { const calendars = options.calendars ?? [monFriCalendar()]; return { properties: { title: null, author: null, startDate: null, finishDate: null, statusDate: null, defaultCalendarUniqueId: calendars[0]?.uniqueId ?? null, minutesPerDay: 480, minutesPerWeek: 2400, daysPerMonth: 20, saveVersion: null, }, tasks, resources: options.resources ?? [], assignments: options.assignments ?? [], calendars, }; } export function makeResource( uniqueId: number, name = `Resource ${String(uniqueId)}`, ) { return { id: uniqueId, uniqueId, name, type: ResourceType.Work, email: null, group: null, maxUnits: 1, cost: null, work: null, resourcePool: null, }; } export function makeAssignment( taskUniqueId: number, resourceUniqueId: number, units = 1, ) { return { taskUniqueId, resourceUniqueId, units, work: null, start: null, finish: null, actualWork: null, remainingWork: null, }; } export const MON_JAN_5 = new Date(2026, 0, 5); export const SAT_JAN_10 = new Date(2026, 0, 10); /** Run serialSGS expecting exactly one feasible schedule. */ export async function runOnce( project: ProjectFile, constraints: readonly Constraint[], workUnits?: readonly WorkUnit[], ): Promise { const resolved = resolveCalendar(project, { workUnits }); const stream = new ScheduleStreamImpl({ [Symbol.asyncIterator]: () => serialSGS.run(resolved, constraints), }); const collected = await stream.collect(); expect(collected).toHaveLength(1); return collected[0]!; } /** Run serialSGS expecting exhaustion — returns the Failure. */ export async function runExpectingFailure( project: ProjectFile, constraints: readonly Constraint[], workUnits?: readonly WorkUnit[], ): Promise { const resolved = resolveCalendar(project, { workUnits }); const gen = serialSGS.run(resolved, constraints); for (;;) { const result = await gen.next(); if (result.done) { const failure = result.value; if (!failure) throw new Error("expected the search to return a Failure"); expect(failure.kind).toBe("failure"); return failure; } } }