Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions app/endpoints/ical.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,19 @@ const { EventsRange } = require("../models/calConst");
const dt = require("../util/dateTime");
const config = require("../config");

// shown in the description when the organizer didn't give a duration
// and the feed had to assume one. re: #498
const UNSPECIFIED_DURATION =
"The organizer didn't specify an end time, so this entry assumes 1 hour.";

module.exports = {
// endpoint export:
get,
// export for testing:
escapeBreak,
replace,
buildCalEntry,
UNSPECIFIED_DURATION,
};

// text|escapeBreak("HEADER") => HEADER:text
Expand Down Expand Up @@ -249,6 +256,15 @@ function buildCalEntry(evt, at) {
description: [
news,
evt.descr, evt.timedetails,
// a v-event needs an end time, so addDuration() invents one hour when the
// organizer didn't give a duration. say so: otherwise the entry looks just
// as definite as one with a real end time. re: #498
//
// only when there's no timedetails though: that's free text like
// "meet 7pm, roll 7:30" and it prints on the line above. saying the
// organizer gave no end time directly under their own note about the
// timing reads as a contradiction.
(!evt.hasDuration() && !evt.timedetails) ? UNSPECIFIED_DURATION : null,
evt.locend? "Ends at "+ evt.locend: null,
url
],
Expand Down
8 changes: 7 additions & 1 deletion app/models/calEvent.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,13 @@ const methods = {
// generates a 1 hour duration if none was specified.
addDuration(start) {
const len = this.eventduration;
return endTime = (len > 0) ? start.add(len, 'minute') : start.add(1, 'hour');
return (len > 0) ? start.add(len, 'minute') : start.add(1, 'hour');
},

// true when the organizer gave an explicit duration;
// false when addDuration() would have to invent one.
hasDuration() {
return this.eventduration > 0;
},

// remove this record and any associated caldaily(s) from the database.
Expand Down
52 changes: 52 additions & 0 deletions app/test/ical_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ const testdb = require("./testdb");
const { CalEvent } = require("../models/calEvent");
const { CalDaily } = require("../models/calDaily");
const { EventStatus } = require("../models/calConst");
const ical = require("../endpoints/ical");
const dt = require("../util/dateTime");
//
const { describe, it, before, after } = require("node:test");
const assert = require("node:assert/strict");
Expand Down Expand Up @@ -281,3 +283,53 @@ String.raw`BEGIN:VCALENDAR`,
String.raw`END:VCALENDAR`,
"" // trailing new line. i think.
].join("\r\n");

// buildCalEntry is a pure function, so these need no database.
// re: #498 -- and the "nil and zero" todo in "can handle a canceled event".
describe("ical duration", () => {
const day = dt.fromYMDString("2002-08-01");

// build the pieces buildCalEntry() needs for one occurrence.
function entryFor(eventduration, timedetails = null) {
const evt = CalEvent.wrap({
id: 1, title: "Test Ride", name: "organizer",
descr: "details", eventtime: "19:00:00", eventduration, timedetails,
created: day.toDate(), modified: day.toDate(), changes: 0,
});
const at = CalDaily.wrap({
pkid: 201, id: 1, eventdate: day.toDate(),
eventstatus: EventStatus.Active, newsflash: null,
});
return ical.buildCalEntry(evt, at);
}

it("notes the assumed duration when the organizer gave none", () => {
for (const none of [null, 0, undefined]) {
const entry = entryFor(none);
assert.ok(entry.description.includes(ical.UNSPECIFIED_DURATION),
`expected the note for eventduration ${JSON.stringify(none)}`);
// the invented end time is one hour after the start
assert.equal(entry.end.diff(entry.start, 'minute'), 60);
}
});

it("says nothing when the organizer gave a duration", () => {
const entry = entryFor(90);
assert.ok(!entry.description.includes(ical.UNSPECIFIED_DURATION),
"did not expect the note when a duration was supplied");
assert.equal(entry.end.diff(entry.start, 'minute'), 90);
});

// a ride without a duration may still describe its timing in free text,
// which prints just above this note; repeating it there reads as a
// contradiction. re: #498
it("says nothing when timedetails already covers the timing", () => {
const entry = entryFor(null, "meet 7pm, roll 7:30");
assert.ok(!entry.description.includes(ical.UNSPECIFIED_DURATION),
"did not expect the note alongside timedetails");
assert.ok(entry.description.includes("meet 7pm, roll 7:30"),
"expected the organizer's own timing note to still be there");
// the end time is still assumed; we just don't say so twice.
assert.equal(entry.end.diff(entry.start, 'minute'), 60);
});
});