Skip to content
Merged
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
2 changes: 2 additions & 0 deletions docs/releases/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ Example:
```
## Fixed

- Keep agenda events chronological when a Base sort is configured; use Base order at the same calendar time. Follow-up to #1411, reported by @ky1ejs.

- (#768) Fixed calendar view appearing empty in week and day views due to invalid time configuration values
- Added time validation in settings UI with proper error messages and debouncing
- Prevents "Cannot read properties of null (reading 'years')" error from FullCalendar
Expand Down
4 changes: 3 additions & 1 deletion src/bases/CalendarView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,9 @@ export function getTaskNotesCalendarEventOrder(sortConfig: unknown): string {
if (!hasBasesCalendarSortConfig(sortConfig)) {
return DEFAULT_CALENDAR_EVENT_ORDER;
}
return `${TASKNOTES_CALENDAR_SORT_INDEX},${DEFAULT_CALENDAR_EVENT_ORDER}`;
// Bases ranks break ties at the same placement. They must not put a timed
// task ahead of an earlier appointment that has no Bases result index.
return `start,allDay,${TASKNOTES_CALENDAR_SORT_INDEX},-duration,title`;
}

function getCalendarEventSortPath(event: EventInput): string | null {
Expand Down
36 changes: 34 additions & 2 deletions tests/unit/issues/issue-1411-agenda-bases-sort.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { EventInput } from "@fullcalendar/core";
import { execFileSync } from "node:child_process";
import {
applyBasesSortIndexesToCalendarEvents,
getTaskNotesCalendarEventOrder,
Expand All @@ -13,15 +14,46 @@ describe("Issue #1411: Agenda Calendar Bases respect Bases sort order", () => {
expect(getTaskNotesCalendarEventOrder(undefined)).toBe("start,-duration,allDay,title");
});

it("puts the TaskNotes sort index first when the Base has a sort config", () => {
it("uses Bases order after event time, before duration and title", () => {
const sortConfig = [{ column: "note.status", direction: "ASC" }];

expect(hasBasesCalendarSortConfig(sortConfig)).toBe(true);
expect(getTaskNotesCalendarEventOrder(sortConfig)).toBe(
`${TASKNOTES_CALENDAR_SORT_INDEX},start,-duration,allDay,title`
`start,allDay,${TASKNOTES_CALENDAR_SORT_INDEX},-duration,title`
);
});

it("interleaves timed tasks with appointments while sorting untimed tasks by Bases rank", () => {
const order = getTaskNotesCalendarEventOrder([{ column: "status" }]);
const events = [
{ title: "Evening task", start: 20, tasknotesSortIndex: 0 },
{ title: "Morning appointment", start: 9 },
{ title: "Morning task", start: 8, tasknotesSortIndex: 3 },
{ title: "Ready", start: 0, tasknotesSortIndex: 2 },
{ title: "Doing", start: 0, tasknotesSortIndex: 1 },
];
// The suite mocks FullCalendar. Exercise its real comparator in Node.
const sorted = JSON.parse(
execFileSync(
process.execPath,
[
"-e",
"const {parseFieldSpecs,compareByFieldSpecs}=require('@fullcalendar/core/internal'); const events=JSON.parse(process.argv[2]); const specs=parseFieldSpecs(process.argv[1]); console.log(JSON.stringify(events.sort((a,b)=>compareByFieldSpecs(a,b,specs)).map(e=>e.title)));",
order,
JSON.stringify(events),
],
{ encoding: "utf8" }
)
);
expect(sorted).toEqual([
"Doing",
"Ready",
"Morning task",
"Morning appointment",
"Evening task",
]);
});

it("adds Bases result indexes to task and property-based events", () => {
const events: EventInput[] = [
{
Expand Down
Loading