calendar and schedule testing and tweaks

This commit is contained in:
gabrielburnworth 2017-12-15 20:41:45 -08:00
parent 507246b46e
commit 3b6b290528
4 changed files with 110 additions and 73 deletions

View file

@ -1,11 +1,13 @@
import { mapStateToProps } from "../map_state_to_props";
import { mapStateToProps, mapResourcesToCalendar } from "../map_state_to_props";
import { fakeState } from "../../../__test_support__/fake_state";
import {
fakeSequence,
fakeRegimen,
fakeFarmEvent
} from "../../../__test_support__/fake_state/resources";
import { buildResourceIndex } from "../../../__test_support__/resource_index_builder";
import {
buildResourceIndex
} from "../../../__test_support__/resource_index_builder";
import * as moment from "moment";
describe("mapStateToProps()", () => {
@ -23,6 +25,7 @@ describe("mapStateToProps()", () => {
const getFutureTime =
(t: number, value: number, label: string) =>
// tslint:disable-next-line:no-any
moment(t).add(value as any, label).toISOString();
const sequenceFarmEvent = fakeFarmEvent("Sequence", 1);
@ -53,54 +56,52 @@ describe("mapStateToProps()", () => {
const testTime = moment().startOf("hour").valueOf();
const { calendarRows, push } = mapStateToProps(testState(testTime));
// Day 1: Sequence Farm Event
const day1 = calendarRows[0];
const day1Time = moment(testTime).add(1, "day");
expect(day1.year).toEqual(day1Time.year() - 2000);
expect(day1.month).toEqual(day1Time.format("MMM"));
expect(day1.day).toEqual(day1Time.date());
const day1ItemTime = day1Time.add(2, "minutes");
expect(day1.sortKey).toEqual(day1ItemTime.unix());
expect(day1.items).toEqual([{
executableId: 1,
heading: "fake",
id: 1,
mmddyy: day1ItemTime.format("MMDDYY"),
sortKey: day1ItemTime.unix(),
timeStr: day1ItemTime.format("hh:mma")
}]);
// Day 2: Regimen Farm Event
const day2 = calendarRows[1];
const day2Time = moment(testTime).add(2, "days");
expect(day2.year).toEqual(day2Time.year() - 2000);
expect(day2.month).toEqual(day2Time.format("MMM"));
expect(day2.day).toEqual(day2Time.date());
// Regimen
const regimenStartTime = day2Time.clone().add(1, "minutes");
expect(day2.sortKey).toEqual(regimenStartTime.unix());
expect(day2.items[0]).toEqual({
executableId: 1,
heading: "Foo",
id: 2,
mmddyy: regimenStartTime.format("MMDDYY"),
sortKey: regimenStartTime.unix(),
subheading: "",
timeStr: regimenStartTime.format("hh:mma")
});
// Regimen Item
const regimenItemTime = day2Time.clone().add(10, "minutes");
expect(day2.items[1]).toEqual({
executableId: 1,
heading: "Foo",
id: 2,
mmddyy: regimenItemTime.format("MMDDYY"),
sortKey: regimenItemTime.unix(),
subheading: "fake",
timeStr: regimenItemTime.format("hh:mma")
});
expect(calendarRows).toEqual([
{
day: day1Time.date(),
items: [
{
executableId: 1,
heading: "fake",
id: 1,
mmddyy: day1ItemTime.format("MMDDYY"),
sortKey: day1ItemTime.unix(),
timeStr: day1ItemTime.format("hh:mma")
}],
month: day1Time.format("MMM"),
sortKey: day1Time.unix(),
year: day1Time.year() - 2000
},
{
day: day2Time.date(),
items: [
{
executableId: 1,
heading: "Foo",
id: 2,
mmddyy: regimenStartTime.format("MMDDYY"),
sortKey: regimenStartTime.unix(),
subheading: "",
timeStr: regimenStartTime.format("hh:mma")
},
{
executableId: 1,
heading: "Foo",
id: 2,
mmddyy: regimenItemTime.format("MMDDYY"),
sortKey: regimenItemTime.unix(),
subheading: "fake",
timeStr: regimenItemTime.format("hh:mma")
}],
month: day2Time.format("MMM"),
sortKey: regimenStartTime.unix(),
year: day2Time.year() - 2000
}]);
expect(push).toBeTruthy();
});

View file

@ -49,9 +49,12 @@ describe("scheduler", () => {
});
function testSchedule(
description: string, fakeEvent: TimeLine, expected: Moment[]) {
description: string,
fakeEvent: TimeLine,
timeNow: Moment,
expected: Moment[]) {
it(description, () => {
const result = scheduleForFarmEvent(fakeEvent);
const result = scheduleForFarmEvent(fakeEvent, timeNow);
expect(result.length).toEqual(expected.length);
expected.map((expectation, index) => {
expect(result[index]).toBeSameTimeAs(expectation);
@ -59,14 +62,21 @@ describe("scheduler", () => {
});
}
const singleFarmEvent: TimeLine = {
"start_time": "2017-08-01T17:00:00.000Z",
"end_time": "2017-08-01T18:00:00.000Z",
"repeat": 1,
"time_unit": "never"
};
testSchedule("schedules a FarmEvent",
{
"start_time": "2017-08-01T17:30:00.000Z",
"end_time": "2017-08-07T05:00:00.000Z",
"repeat": 2,
"time_unit": "daily",
current_time: "2017-08-01T16:30:00.000Z"
"time_unit": "daily"
},
moment("2017-08-01T16:30:00.000Z"),
[
moment("2017-08-01T17:30:00.000Z"),
moment("2017-08-03T17:30:00.000Z"),
@ -78,9 +88,9 @@ describe("scheduler", () => {
"start_time": "2017-08-01T17:30:00.000Z",
"end_time": "2017-08-07T05:00:00.000Z",
"repeat": 0,
"time_unit": "daily",
current_time: "2017-08-01T16:30:00.000Z"
"time_unit": "daily"
},
moment("2017-08-01T16:30:00.000Z"),
[moment("2017-08-01T17:30:00.000Z")]);
testSchedule("handles start_time in the past",
@ -88,36 +98,51 @@ describe("scheduler", () => {
"start_time": "2017-08-01T17:30:00.000Z",
"end_time": "2017-08-09T05:00:00.000Z",
"repeat": 2,
"time_unit": "daily",
current_time: "2017-08-03T18:30:00.000Z"
"time_unit": "daily"
},
moment("2017-08-03T18:30:00.000Z"),
[
moment("2017-08-05T17:30:00.000Z"),
moment("2017-08-07T17:30:00.000Z")
]);
testSchedule("handles start_time in the past: no repeat",
singleFarmEvent,
moment("2017-08-01T17:30:00.000Z"),
[moment("2017-08-01T17:00:00.000Z")]);
testSchedule("uses grace period",
{
"start_time": "2017-08-01T17:30:00.000Z",
"end_time": "2017-08-02T05:00:00.000Z",
"repeat": 4,
"time_unit": "hourly",
current_time: "2017-08-01T17:30:30.000Z"
"time_unit": "hourly"
},
moment("2017-08-01T17:30:30.000Z"),
[
moment("2017-08-01T17:30:00.000Z"),
moment("2017-08-01T21:30:00.000Z"),
moment("2017-08-02T01:30:00.000Z")
]);
testSchedule("uses grace period: no repeat",
singleFarmEvent,
moment("2017-08-01T17:00:30.000Z"),
[moment("2017-08-01T17:00:00.000Z")]);
testSchedule("farm event over",
{
"start_time": "2017-08-01T17:30:00.000Z",
"end_time": "2017-08-02T05:00:00.000Z",
"repeat": 4,
"time_unit": "hourly",
current_time: "2017-08-03T17:30:30.000Z"
"time_unit": "hourly"
},
moment("2017-08-03T17:30:30.000Z"),
[]);
testSchedule("farm event over: no repeat",
singleFarmEvent,
moment("2017-08-01T19:00:00.000Z"),
[]);
testSchedule("first 60 items",
@ -125,9 +150,9 @@ describe("scheduler", () => {
"start_time": "2017-08-02T17:00:00.000Z",
"end_time": "2017-08-02T19:00:00.000Z",
"repeat": 1,
"time_unit": "minutely",
current_time: "2017-08-01T15:30:00.000Z"
"time_unit": "minutely"
},
moment("2017-08-01T15:30:00.000Z"),
range(0, 60)
.map((x: number) =>
moment(`2017-08-02T17:${padStart("" + x, 2, "0")}:00.000Z`)));
@ -137,9 +162,9 @@ describe("scheduler", () => {
"start_time": "2017-08-02T16:00:00.000Z",
"end_time": "2017-08-02T21:00:00.000Z",
"repeat": 1,
"time_unit": "minutely",
current_time: "2017-08-02T17:01:00.000Z"
"time_unit": "minutely"
},
moment("2017-08-02T17:01:00.000Z"),
range(0, 60)
.map((x: number) =>
moment(`2017-08-02T17:${padStart("" + x, 2, "0")}:00.000Z`)));

View file

@ -68,18 +68,23 @@ export interface TimeLine {
}
/** Takes a subset of FarmEvent<Sequence> data and generates a list of dates. */
export function scheduleForFarmEvent(
{ start_time, end_time, repeat, time_unit, current_time = moment() }: TimeLine
{ start_time, end_time, repeat, time_unit }: TimeLine, timeNow = moment()
): Moment[] {
const interval = repeat && farmEventIntervalSeconds(repeat, time_unit);
if (interval && (time_unit !== NEVER)) {
const schedule = scheduler({
startTime: moment(start_time),
currentTime: moment(current_time),
currentTime: timeNow,
endTime: end_time ? moment(end_time) : nextYear(),
intervalSeconds: interval
});
return schedule;
} else {
return [moment(start_time)];
const gracePeriod = timeNow.clone().subtract(1, "minute");
if (moment(end_time).isSameOrAfter(gracePeriod)) {
return [moment(start_time)];
} else {
return [];
}
}
}

View file

@ -12,31 +12,35 @@ import { scheduleForFarmEvent } from "./calendar/scheduler";
/** Prepares a FarmEvent[] for use with <FBSelect /> */
export function mapStateToProps(state: Everything): FarmEventProps {
const push = (state && state.router && state.router.push) || (() => { });
const calendar = mapResourcesToCalendar(state.resources.index, moment.now());
const calendar = mapResourcesToCalendar(state.resources.index, moment());
const calendarRows = calendar.getAll();
return { calendarRows, push };
}
/** TODO: Reduce complexity, but write *good* unit tests *before* refactoring.*/
export function mapResourcesToCalendar(ri: ResourceIndex, unixNow = moment.now()): Calendar {
export function mapResourcesToCalendar(
ri: ResourceIndex, now = moment()): Calendar {
const x = joinFarmEventsToExecutable(ri);
const calendar = new Calendar();
const addRegimenToCalendar = regimenCalendarAdder(ri);
x.map(function (fe) {
switch (fe.executable_type) {
case "Regimen": return addRegimenToCalendar(fe, calendar);
case "Sequence": return addSequenceToCalendar(fe, calendar);
default: throw new Error(`Bad fe: ${JSON.stringify(fe)}`);
case "Regimen":
return addRegimenToCalendar(
fe, calendar, now);
case "Sequence":
return addSequenceToCalendar(fe, calendar, now);
default:
throw new Error(`Bad fe: ${JSON.stringify(fe)}`);
}
});
return calendar;
}
export let regimenCalendarAdder = (index: ResourceIndex) =>
(f: FarmEventWithRegimen, c: Calendar) => {
(f: FarmEventWithRegimen, c: Calendar, now = moment()) => {
const { regimen_items } = f.executable;
const now = moment();
const fromEpoch = (ms: number) => moment(f.start_time)
.startOf("day")
.add(ms, "ms");
@ -46,7 +50,9 @@ export let regimenCalendarAdder = (index: ResourceIndex) =>
c.insert(o);
regimen_items.map(ri => {
const time = fromEpoch(ri.time_offset);
if (time.isAfter(now) && time.isAfter(moment(f.start_time))) {
const gracePeriod = now.clone().subtract(1, "minute");
if (time.isSameOrAfter(gracePeriod)
&& time.isSameOrAfter(moment(f.start_time))) {
const oo = occurrence(time, f);
const seq = findSequenceById(index, ri.sequence_id);
oo.heading = f.executable.name;
@ -58,6 +64,6 @@ export let regimenCalendarAdder = (index: ResourceIndex) =>
export let addSequenceToCalendar =
(f: FarmEventWithSequence, c: Calendar, now = moment()) => {
scheduleForFarmEvent(f)
scheduleForFarmEvent(f, now)
.map(m => c.insert(occurrence(m, f)));
};