Skip to content

Commit 73b9895

Browse files
committed
refactor: s/mergeTaskParts/buildTask
1 parent f4fa35e commit 73b9895

File tree

4 files changed

+97
-89
lines changed

4 files changed

+97
-89
lines changed

src/data/__tests__/merge-task-parts.ts src/data/__tests__/build-task.ts

+33-33
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { describe, expect, it } from "vitest";
22
import { DateTime } from "luxon";
3-
import { mergeTaskParts } from "../merge-task-parts";
3+
import { buildTask } from "../build-task";
44

55
describe("Merging task parts", () => {
66
it("gives default values when called with nothing", () => {
7-
const task = mergeTaskParts();
7+
const task = buildTask();
88

99
expect(task.status.type).toEqual("UNKNOWN");
1010
expect(task.source.type).toEqual("UNKNOWN");
@@ -23,127 +23,127 @@ describe("Merging task parts", () => {
2323
});
2424

2525
it("skips empty descriptions", () => {
26-
const task = mergeTaskParts({ description: "" }, { description: "desc" });
26+
const task = buildTask({ description: "" }, { description: "desc" });
2727

2828
expect(task.description).toEqual("desc");
2929
});
3030

3131
it("keeps non-empty descriptions", () => {
32-
const task = mergeTaskParts({ description: "wow" }, { description: "uh-oh!" });
32+
const task = buildTask({ description: "wow" }, { description: "uh-oh!" });
3333

3434
expect(task.description).toEqual("wow");
3535
});
3636

3737
it("skips invalid dates", () => {
38-
const valid = DateTime.now();
39-
const invalid = DateTime.invalid("asdf");
38+
const valid = DateTime.fromISO("2025-01-01");
39+
const invalid = DateTime.invalid("unspecified date");
4040

41-
const task = mergeTaskParts({ dates: { done: invalid } }, { dates: { done: valid } });
41+
const task = buildTask({ dates: { done: invalid } }, { dates: { done: valid } });
4242

43-
expect(task.dates.done).toBe(valid);
43+
expect(task.dates.done).toEqual(valid);
4444
});
4545

4646
it("keeps valid dates", () => {
47-
const now = DateTime.now();
48-
const later = now.plus({ minutes: 10 });
47+
const validDate = DateTime.fromISO("2025-01-01");
48+
const anotherValidDate = DateTime.fromISO("2025-01-02");
4949

50-
const task = mergeTaskParts({ dates: { done: now } }, { dates: { done: later } });
50+
const task = buildTask({ dates: { done: validDate } }, { dates: { done: anotherValidDate } });
5151

52-
expect(task.dates.done).toBe(now);
52+
expect(task.dates.done).toEqual(validDate);
5353
});
5454

5555
it("skips invalid times", () => {
56-
const valid = DateTime.now();
57-
const invalid = DateTime.invalid("asdf");
56+
const valid = DateTime.fromISO("12:00:00Z");
57+
const invalid = DateTime.invalid("unspecified time");
5858

59-
const task = mergeTaskParts({ times: { start: invalid } }, { times: { start: valid } });
59+
const task = buildTask({ times: { start: invalid } }, { times: { start: valid } });
6060

61-
expect(task.times.start).toBe(valid);
61+
expect(task.times.start).toEqual(valid);
6262
});
6363

6464
it("keeps valid times", () => {
65-
const now = DateTime.now();
66-
const later = now.plus({ minutes: 10 });
65+
const validTime = DateTime.fromISO("12:00:00Z");
66+
const anotherValidTime = DateTime.fromISO("13:00:00Z");
6767

68-
const task = mergeTaskParts({ times: { start: now } }, { times: { start: later } });
68+
const task = buildTask({ times: { start: validTime } }, { times: { start: anotherValidTime } });
6969

70-
expect(task.times.start).toBe(now);
70+
expect(task.times.start).toEqual(validTime);
7171
});
7272

7373
it("takes union of tags", () => {
74-
const task = mergeTaskParts({ tags: new Set(["a", "b"]) }, { tags: new Set(["b", "c"]) });
74+
const task = buildTask({ tags: new Set(["a", "b"]) }, { tags: new Set(["b", "c"]) });
7575

7676
expect(task.tags).toEqual(new Set(["a", "b", "c"]));
7777
});
7878

7979
it("gives default priority value when unspecified", () => {
80-
const task = mergeTaskParts({});
80+
const task = buildTask({});
8181

8282
expect(task.priority).toEqual(3);
8383
});
8484

8585
it("skips missing priority value", () => {
86-
const task = mergeTaskParts({}, { priority: 1 });
86+
const task = buildTask({}, { priority: 1 });
8787

8888
expect(task.priority).toEqual(1);
8989
});
9090

9191
it("skips default priority value", () => {
92-
const task = mergeTaskParts({ priority: 3 }, { priority: 1 });
92+
const task = buildTask({ priority: 3 }, { priority: 1 });
9393

9494
expect(task.priority).toEqual(1);
9595
});
9696

9797
it("keeps non-default priority", () => {
98-
const task = mergeTaskParts({ priority: 1 }, { priority: 4 });
98+
const task = buildTask({ priority: 1 }, { priority: 4 });
9999

100100
expect(task.priority).toEqual(1);
101101
});
102102

103103
it("gives default status when unspecified", () => {
104-
const task = mergeTaskParts({});
104+
const task = buildTask({});
105105

106106
expect(task.status.type).toEqual("UNKNOWN");
107107
});
108108

109109
it("skips missing status", () => {
110-
const task = mergeTaskParts({}, { status: { type: "DONE" } });
110+
const task = buildTask({}, { status: { type: "DONE" } });
111111

112112
expect(task.status.type).toEqual("DONE");
113113
});
114114

115115
it("skips default status", () => {
116-
const task = mergeTaskParts({ status: { type: "UNKNOWN" } }, { status: { type: "DONE" } });
116+
const task = buildTask({ status: { type: "UNKNOWN" } }, { status: { type: "DONE" } });
117117

118118
expect(task.status.type).toEqual("DONE");
119119
});
120120

121121
it("keeps non-default status", () => {
122-
const task = mergeTaskParts({ status: { type: "DONE" } }, { status: { type: "UNKNOWN" } });
122+
const task = buildTask({ status: { type: "DONE" } }, { status: { type: "UNKNOWN" } });
123123

124124
expect(task.status.type).toEqual("DONE");
125125
});
126126

127127
it("gives default source when unspecified", () => {
128-
const task = mergeTaskParts({});
128+
const task = buildTask({});
129129

130130
expect(task.source.type).toEqual("UNKNOWN");
131131
});
132132

133133
it("skips missing source", () => {
134-
const task = mergeTaskParts({}, { source: { type: "PAGE" } });
134+
const task = buildTask({}, { source: { type: "PAGE" } });
135135

136136
expect(task.source.type).toEqual("PAGE");
137137
});
138138

139139
it("skips default source", () => {
140-
const task = mergeTaskParts({ source: { type: "UNKNOWN" } }, { source: { type: "PAGE" } });
140+
const task = buildTask({ source: { type: "UNKNOWN" } }, { source: { type: "PAGE" } });
141141

142142
expect(task.source.type).toEqual("PAGE");
143143
});
144144

145145
it("keeps non-default source", () => {
146-
const task = mergeTaskParts({ source: { type: "PAGE" } }, { source: { type: "UNKNOWN" } });
146+
const task = buildTask({ source: { type: "PAGE" } }, { source: { type: "UNKNOWN" } });
147147

148148
expect(task.source.type).toEqual("PAGE");
149149
});

src/data/build-task.ts

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { Task, TaskSource, TaskStatus } from "@/data/task";
2+
import { DateTime } from "luxon";
3+
import { DeepPartial } from "utility-types";
4+
import _ from "lodash";
5+
6+
/**
7+
* Builds a new {@link Task} using values from {@link parts}.
8+
*
9+
* Each property in the Task will use the first non-default value encountered in the list of parts.
10+
*
11+
* @see {@link TASK_WITH_DEFAULT_VALUES}
12+
*/
13+
export function buildTask(...parts: DeepPartial<Task>[]): Task {
14+
return parts.reduce(takeNonDefaultValues, { ...TASK_WITH_DEFAULT_VALUES });
15+
}
16+
17+
function takeNonDefaultValues(task: Task, part: DeepPartial<Task>): Task {
18+
return _.mergeWith(task, part, (taskValue, partValue, propName) => {
19+
if (propName === "type" && _.isString(taskValue) && _.isString(partValue)) {
20+
return taskValue !== DEFAULT_TYPE_VALUE ? taskValue : partValue;
21+
}
22+
if (propName === "priority" && _.isNumber(taskValue) && _.isNumber(partValue)) {
23+
return taskValue !== DEFAULT_PRIORITY_VALUE ? taskValue : partValue;
24+
}
25+
if (_.isSet(taskValue) && _.isSet(partValue)) {
26+
return new Set([...taskValue, ...partValue]);
27+
}
28+
if (DateTime.isDateTime(taskValue) && DateTime.isDateTime(partValue)) {
29+
return taskValue.isValid ? taskValue : partValue;
30+
}
31+
if (_.isString(taskValue) && _.isString(partValue)) {
32+
return taskValue !== "" ? taskValue : partValue;
33+
}
34+
});
35+
}
36+
37+
const DEFAULT_DATETIME_VALUE = DateTime.invalid("unspecified");
38+
const DEFAULT_PRIORITY_VALUE = 3 as const satisfies Task["priority"];
39+
const DEFAULT_TYPE_VALUE = "UNKNOWN" as const satisfies TaskSource["type"] & TaskStatus["type"];
40+
41+
const TASK_WITH_DEFAULT_VALUES = {
42+
status: { type: DEFAULT_TYPE_VALUE },
43+
source: { type: DEFAULT_TYPE_VALUE },
44+
dates: {
45+
cancelled: DEFAULT_DATETIME_VALUE,
46+
created: DEFAULT_DATETIME_VALUE,
47+
done: DEFAULT_DATETIME_VALUE,
48+
due: DEFAULT_DATETIME_VALUE,
49+
scheduled: DEFAULT_DATETIME_VALUE,
50+
start: DEFAULT_DATETIME_VALUE,
51+
},
52+
times: {
53+
start: DEFAULT_DATETIME_VALUE,
54+
end: DEFAULT_DATETIME_VALUE,
55+
},
56+
description: "",
57+
priority: DEFAULT_PRIORITY_VALUE,
58+
recurrenceRule: "",
59+
tags: new Set(),
60+
id: "",
61+
dependsOn: new Set(),
62+
} as const satisfies Task;

src/data/merge-task-parts.ts

-54
This file was deleted.

src/lib/obsidian-dataview/adapter.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { DataviewApi, SMarkdownPage, STask, getAPI, isPluginEnabled } from "@/li
22
import { EventRef, MetadataCache, Plugin, TAbstractFile, TFile } from "@/lib/obsidian/types";
33
import { ParametersFrom4Overloads } from "@/utils/type-utils";
44
import { Task } from "@/data/task";
5-
import { mergeTaskParts } from "@/data/merge-task-parts";
5+
import { buildTask } from "@/data/build-task";
66
import { parseTaskEmojis } from "@/data/parse-task-emojis";
77

88
type MetadataCacheOnFunctionParameters = ParametersFrom4Overloads<MetadataCache["on"]>;
@@ -69,7 +69,7 @@ export class Dataview {
6969
}
7070

7171
private extractTaskFields(page: SMarkdownPage, task: STask): Task {
72-
return mergeTaskParts(parseTaskEmojis(task.text), {
72+
return buildTask(parseTaskEmojis(task.text), {
7373
dates: {
7474
scheduled: page.file.day,
7575
},

0 commit comments

Comments
 (0)