Skip to content

Commit

Permalink
try adding tests
Browse files Browse the repository at this point in the history
  • Loading branch information
davish committed Dec 6, 2022
1 parent bb68c28 commit 9073fd1
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 18 deletions.
8 changes: 8 additions & 0 deletions src/cache.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Cache from "./cache";
import { CalendarInfo } from "./types";

const makeMockPlugin = (sources: CalendarInfo[]) => {
settings: {
calendarSources: sources;
}
};
96 changes: 80 additions & 16 deletions src/cache.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,56 @@
import { EventInput, EventSourceInput } from "@fullcalendar/core";
import { App } from "obsidian";
import { App, TFile } from "obsidian";
import { Calendar, ID_SEPARATOR } from "./calendars/Calendar";
import { EditableCalendar } from "./calendars/EditableCalendar";
import { toEventInput } from "./fullcalendar_interop";
import FullCalendarPlugin from "./main";
import { getColors } from "./models/util";
import { CalendarInfo, OFCEvent } from "./types";
import { FullCalendarSettings } from "./ui/settings";

type CalendarInitializerMap = Record<
CalendarInfo["type"],
(
app: App,
plugin: FullCalendarPlugin,
info: CalendarInfo
) => Calendar | null
(app: App, info: CalendarInfo) => Calendar | null
>;

const removeNulls = <T>(e: T | null): T[] => (e ? [e] : []);

type CacheEventEntry = { event: OFCEvent; id: string };
type CacheEntry = {
calendar: Calendar;
events: { event: OFCEvent; id: string }[];
events: CacheEventEntry[];
};

type ViewEventEntry = {
event: EventInput;
id: string;
};

type EventModifiedCallback = (id: string, event: EventInput) => void;
type UpdateViewCallback = (info: {
toRemove: EventInput[];
toAdd: EventInput[];
}) => void;

export default class EventCache {
private app: App;
private plugin: FullCalendarPlugin;
private settings: FullCalendarSettings;

private calendars: CalendarInitializerMap;

private cache: Record<string, CacheEntry> = {};
// Map directory paths to cache entry IDs.
private directories: Record<string, string> = {};

private pkCounter = 0;

private updateViewCallbacks: UpdateViewCallback[] = [];

constructor(
app: App,
plugin: FullCalendarPlugin,
settings: FullCalendarSettings,
calendars: CalendarInitializerMap
) {
this.app = app;
this.plugin = plugin;
this.settings = settings;
this.calendars = calendars;
}

Expand All @@ -58,18 +68,22 @@ export default class EventCache {
});
}

generateId(calendar: Calendar): string {
return `${calendar.id}${ID_SEPARATOR}${this.pkCounter++}`;
}

initialize(): void {
const calendars = this.plugin.settings.calendarSources
.map((s) => this.calendars[s.type](this.app, this.plugin, s))
const calendars = this.settings.calendarSources
.map((s) => this.calendars[s.type](this.app, s))
.flatMap(removeNulls);

this.cache = {};
for (const calendar of calendars) {
this.cache[calendar.id] = {
calendar,
events: calendar.getEvents().map((event, idx) => ({
events: calendar.getEvents().map((event) => ({
event,
id: `${calendar.id}${ID_SEPARATOR}${idx}`,
id: this.generateId(calendar),
})),
};

Expand All @@ -83,6 +97,56 @@ export default class EventCache {
this.cache = {};
}

updateViews(toRemove: CacheEventEntry[], toAdd: CacheEventEntry[]) {
const payload = {
toRemove: toRemove
.map(({ event, id }) => toEventInput(id, event))
.flatMap(removeNulls),
toAdd: toAdd
.map(({ event, id }) => toEventInput(id, event))
.flatMap(removeNulls),
};

for (const callback of this.updateViewCallbacks) {
callback(payload);
}
}

async fileUpdated(file: TFile): Promise<void> {
const fileCache = this.app.metadataCache.getFileCache(file);
if (!fileCache) {
return;
}
const directory = file.parent;

const calendarDirectory = Object.keys(this.directories)
.filter((calDir) => directory.path.startsWith(calDir))
.sort((a, b) => b.length - a.length)[0];

const entry = this.cache[this.directories[calendarDirectory]];
if (!(entry.calendar instanceof EditableCalendar)) {
console.warn(
"File is associated with a non-editable calendar",
entry.calendar.id,
file.path
);
return;
}
const contents = await this.app.vault.cachedRead(file);

// TODO: Figure out how to re-use primary keys for events rather than creating new ones every time.
const newEvents = entry.calendar
.getEventsInFile(fileCache, contents)
.map((event) => ({
event,
id: this.generateId(entry.calendar),
}));

const oldEvents = entry.events;
entry.events = newEvents;
this.updateViews(oldEvents, newEvents);
}

getEventFromId(s: string): OFCEvent | null {
const [id, idx] = s.split(ID_SEPARATOR);
return this.cache[id]?.events[parseInt(idx)]?.event;
Expand Down
7 changes: 7 additions & 0 deletions src/calendars/EditableCalendar.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { CachedMetadata } from "obsidian";
import { OFCEvent } from "src/types";
import { Calendar } from "./Calendar";

export abstract class EditableCalendar extends Calendar {
abstract get directory(): string;

abstract getEventsInFile(
fileCache: CachedMetadata,
contents: string
): OFCEvent[];
}
4 changes: 2 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { MarkdownView, Plugin } from "obsidian";
import { CalendarView, FULL_CALENDAR_VIEW_TYPE } from "./ui/view";
import { renderCalendar } from "./ui/calendar";

import { EventModal } from "./ui/modal";
import { toEventInput } from "./fullcalendar_interop";
import {
DEFAULT_SETTINGS,
FullCalendarSettings,
FullCalendarSettingTab,
} from "./ui/settings";
import { PLUGIN_SLUG } from "./types";
import { EventModal } from "./ui/modal";
import { CalendarView, FULL_CALENDAR_VIEW_TYPE } from "./ui/view";

export default class FullCalendarPlugin extends Plugin {
settings: FullCalendarSettings = DEFAULT_SETTINGS;
Expand Down

0 comments on commit 9073fd1

Please sign in to comment.