mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 16:03:02 +00:00
cc1c16ec34
Saving a start on a stop rewrote the whole day as the timed stops in time order followed by every untimed stop. Its own comment and the commit that added it promised that untimed stops keep their place, but a day of two untimed stops and a third one given 14:00 came back with the third one first. On a road trip the stop order is the drive, so the day was re-routed, its vias landed on other legs, and collaborators, who only got the one edited row, saw a third order until they reloaded. The intended rule, an untimed stop inheriting the time of the timed stop before it, now lives once as chronoOrder in @trek/shared, used by both the day plan (dayMerge) and AssignmentsService.updateTime. The server still reads a booked night's hour from its check-in, and a value without a colon still sorts after every clock time. The day is sorted only when the stop's start actually changed, so taking an End off or saving an unchanged start no longer snaps back a day somebody dragged out of time order. A sorted day is numbered from 0 like a drag. In the same transaction the day's road trip vias follow the stop they were drawn behind, the planner's own drag rule, and a via behind a last stop that stays last is kept, so the drive into the next day keeps its road. PUT /assignments/:id/time and the MCP update_assignment_time tool both broadcast assignment:reordered with the whole day, to the writer as well, and roadtripVia:changed when vias moved, so every client ends up with the same order. ASG-SVC-022, 023 and 027 pinned the old behaviour and now pin the intended one.
53 lines
2.0 KiB
TypeScript
53 lines
2.0 KiB
TypeScript
import { chronoOrder } from './chrono-order';
|
|
|
|
import { describe, it, expect } from 'vitest';
|
|
|
|
type Stop = { name: string; at: number | null };
|
|
|
|
const stop = (name: string, at: number | null = null): Stop => ({ name, at });
|
|
const order = (stops: Stop[]) => chronoOrder(stops, (s) => s.at).map((s) => s.name);
|
|
|
|
describe('chronoOrder', () => {
|
|
it('leaves untimed stops in front of the first timed one where they are', () => {
|
|
expect(order([stop('A'), stop('B'), stop('C', 14 * 60)])).toEqual(['A', 'B', 'C']);
|
|
});
|
|
|
|
it('keeps an untimed stop behind the timed stop it followed', () => {
|
|
expect(order([stop('A', 9 * 60), stop('B'), stop('C', 14 * 60)])).toEqual(['A', 'B', 'C']);
|
|
});
|
|
|
|
it('sorts timed stops among themselves and leaves the untimed head alone', () => {
|
|
expect(order([stop('A'), stop('B', 15 * 60), stop('C', 10 * 60)])).toEqual(['A', 'C', 'B']);
|
|
});
|
|
|
|
it('carries an untimed stop along with the timed stop in front of it', () => {
|
|
// B inherits 15:00 from A, so both go behind C at 10:00, and B stays behind A.
|
|
expect(order([stop('A', 15 * 60), stop('B'), stop('C', 10 * 60)])).toEqual(['C', 'A', 'B']);
|
|
});
|
|
|
|
it('keeps the incoming order for equal times', () => {
|
|
expect(order([stop('A', 600), stop('B', 600), stop('C', 540), stop('D', 540)])).toEqual(['C', 'D', 'A', 'B']);
|
|
});
|
|
|
|
it('treats undefined like null and orders midnight as a real time', () => {
|
|
const stops = [
|
|
{ name: 'A', at: undefined },
|
|
{ name: 'B', at: 60 },
|
|
{ name: 'C', at: 0 },
|
|
];
|
|
expect(chronoOrder(stops, (s) => s.at).map((s) => s.name)).toEqual(['A', 'C', 'B']);
|
|
});
|
|
|
|
it('returns a new array and leaves the input as it was', () => {
|
|
const stops = [stop('A', 600), stop('B', 540)];
|
|
const sorted = chronoOrder(stops, (s) => s.at);
|
|
expect(sorted).not.toBe(stops);
|
|
expect(stops.map((s) => s.name)).toEqual(['A', 'B']);
|
|
expect(sorted[0]).toBe(stops[1]);
|
|
});
|
|
|
|
it('handles an empty day', () => {
|
|
expect(chronoOrder([], () => null)).toEqual([]);
|
|
});
|
|
});
|