Skip to content

Commit 654ea45

Browse files
authored
Merge pull request #3509 from justise/create-task-line-modal-alltasks
fix: Enable adding dependencies from modal created via API's createTaskLineModal()
2 parents 86ada53 + 520279f commit 654ea45

File tree

9 files changed

+123
-104
lines changed

9 files changed

+123
-104
lines changed

docs/Advanced/Tasks Api.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,6 @@ This can be used, for example, to display the Auto-Suggest on non-task lines. [S
151151

152152
- Editing tasks:
153153
- It is not yet possible to use the API to edit an *existing task line* with Tasks [[Create or edit Task|Create or edit task UI]]. We are tracking this in [issue #1945](https://github.com/obsidian-tasks-group/obsidian-tasks/issues/1945).
154-
- It is not yet possible to use the API to add [[Task Dependencies|dependencies]] with the Tasks [[Create or edit Task|Create or edit task UI]]. We are tracking this in [issue #2993](https://github.com/obsidian-tasks-group/obsidian-tasks/issues/2993).
155154
- Auto Suggest:
156155
- It is not yet possible for [[auto-suggest]] to add [[Task Dependencies|dependencies]] when Auto-Suggest is used in [[Kanban plugin]] cards - or any other plugins that use the [[Tasks Api#Auto-Suggest Integration|Auto-Suggest Integration]]. We are tracking this in [issue #3274](https://github.com/obsidian-tasks-group/obsidian-tasks/issues/3274).
157156
- Searching tasks:

src/Api/createTaskLineModal.ts

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,17 @@
11
import type { App } from 'obsidian';
22
import type { Task } from '../Task/Task';
3-
4-
/**
5-
* Interface to remove all references to {TaskModal} in this file.
6-
* This is necessary to make {createTaskLineModal} testable.
7-
* Once Jest is configured to work with Svelte, this can be removed.
8-
*/
9-
export interface ITaskModal {
10-
open(): void;
11-
}
12-
13-
/**
14-
* Signature of the factory method for {@link TaskModal}.
15-
* This is necessary to make {@link createTaskLineModal} testable.
16-
* Once Jest is configured to work with Svelte, this can be removed.
17-
*/
18-
export type taskModalFactory = {
19-
(app: App, onSubmit: (updatedTasks: Task[]) => void): ITaskModal;
20-
};
3+
import { taskFromLine } from '../Commands/CreateOrEditTaskParser';
4+
import { TaskModal } from '../Obsidian/TaskModal';
215

226
/**
237
* Opens the Tasks UI and returns the Markdown string for the task entered.
248
*
259
* @param app - The Obsidian App
26-
* @param taskModalFactory - Factory method to instantiate {@link TaskModal}. Default value is {@link defaultTaskModalFactory}.
27-
* Used only for testing.
2810
*
2911
* @returns {Promise<string>} A promise that contains the Markdown string for the task entered or
3012
* an empty string, if data entry was cancelled.
3113
*/
32-
export const createTaskLineModal = (app: App, taskModalFactory: taskModalFactory): Promise<string> => {
14+
export const createTaskLineModal = (app: App, allTasks: Task[]): Promise<string> => {
3315
let resolvePromise: (input: string) => void;
3416
const waitForClose = new Promise<string>((resolve, _) => {
3517
resolvePromise = resolve;
@@ -40,7 +22,10 @@ export const createTaskLineModal = (app: App, taskModalFactory: taskModalFactory
4022
resolvePromise(line);
4123
};
4224

43-
const taskModal = taskModalFactory(app, onSubmit);
25+
const task = taskFromLine({ line: '', path: '' });
26+
const taskModal = new TaskModal({ app, task, onSubmit, allTasks });
27+
4428
taskModal.open();
29+
4530
return waitForClose;
4631
};

src/Api/createTaskLineModalHelper.ts

Lines changed: 0 additions & 30 deletions
This file was deleted.

src/Api/index.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
1-
import type { App } from 'obsidian';
1+
import type TasksPlugin from '../main';
22
import { toggleLine } from '../Commands/ToggleDone';
33
import { createTaskLineModal } from './createTaskLineModal';
44
import type { TasksApiV1 } from './TasksApiV1';
5-
import { defaultTaskModalFactory } from './createTaskLineModalHelper';
65

76
/**
87
* Factory method for API v1
98
*
109
* @param app - The Obsidian App
1110
*/
12-
export const tasksApiV1 = (app: App): TasksApiV1 => {
11+
export const tasksApiV1 = (plugin: TasksPlugin): TasksApiV1 => {
12+
const app = plugin.app;
13+
1314
return {
1415
createTaskLineModal: (): Promise<string> => {
15-
return createTaskLineModal(app, defaultTaskModalFactory);
16+
return createTaskLineModal(app, plugin.getTasks());
1617
},
1718
executeToggleTaskDoneCommand: (line: string, path: string) => toggleLine(line, path).text,
1819
};

src/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export default class TasksPlugin extends Plugin {
2626
public queryRenderer: QueryRenderer | undefined;
2727

2828
get apiV1() {
29-
return tasksApiV1(this.app);
29+
return tasksApiV1(this);
3030
}
3131

3232
async onload() {

tests/Api/createTaskLineModal.test.ts

Lines changed: 43 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,79 +2,78 @@ import type { App } from 'obsidian';
22
import type { Task } from '../../src/Task/Task';
33
import { taskFromLine } from '../../src/Commands/CreateOrEditTaskParser';
44
import { createTaskLineModal } from '../../src/Api/createTaskLineModal';
5-
import type { ITaskModal, taskModalFactory } from '../../src/Api/createTaskLineModal';
5+
import { TaskModal } from '../__mocks__/TaskModal';
66

77
const app = {} as App;
88

9-
class TaskModalMock implements ITaskModal {
10-
public task: Task;
11-
public onSubmit: (updatedTasks: Task[]) => void;
12-
public openWasCalled: boolean = false;
13-
14-
constructor() {
15-
this.task = createNewTask();
16-
this.onSubmit = (_: Task[]) => {};
17-
}
18-
19-
public open() {
20-
this.openWasCalled = true;
21-
}
22-
23-
// helper method to simulate pressing the submit button
24-
public submit() {
25-
this.onSubmit([this.task]);
26-
}
27-
28-
// helper method to simulate pressing the cancel button
29-
public cancel() {
30-
this.onSubmit([]);
31-
}
32-
}
33-
349
const createNewTask = (line = ''): Task => {
3510
return taskFromLine({ line, path: '' });
3611
};
3712

38-
function mockModalFactory(modalMock: TaskModalMock) {
39-
return (_: App, onSubmit: (updatedTasks: Task[]) => void): ITaskModal => {
40-
modalMock.onSubmit = onSubmit;
41-
return modalMock;
13+
jest.mock('../../src/Obsidian/TaskModal', () => {
14+
return {
15+
TaskModal: jest.fn(
16+
({
17+
app,
18+
task,
19+
onSubmit,
20+
allTasks,
21+
}: {
22+
app: App;
23+
task: Task;
24+
onSubmit: (updatedTasks: Task[]) => void;
25+
allTasks: Task[];
26+
}) => {
27+
return new TaskModal({ app, task, onSubmit, allTasks });
28+
},
29+
),
4230
};
43-
}
31+
});
4432

4533
describe('APIv1 - createTaskLineModal', () => {
46-
let modalMock: TaskModalMock;
47-
let modalFactory: taskModalFactory;
48-
4934
beforeEach(() => {
50-
modalMock = new TaskModalMock();
51-
modalFactory = mockModalFactory(modalMock);
35+
jest.clearAllMocks();
5236
});
5337

38+
/**
39+
* When we ask to create the task line modal, it should call open() on the TaskModal instance.
40+
*/
5441
it('TaskModal.open() should be called', () => {
55-
createTaskLineModal(app, modalFactory);
56-
expect(modalMock.openWasCalled).toBeTruthy();
42+
createTaskLineModal(app, []);
43+
44+
expect(TaskModal.instance.open).toHaveBeenCalledTimes(1);
5745
});
5846

47+
/**
48+
* If the Modal returns the expected text, the api function createTaskLineModal() returns that text
49+
*/
5950
it('should return the Markdown for a task if submitted', async () => {
60-
const taskLinePromise = createTaskLineModal(app, modalFactory);
51+
const taskLinePromise = createTaskLineModal(app, []);
6152
const expected = '- [ ] test';
6253

63-
modalMock.task = createNewTask(expected);
64-
modalMock.submit();
54+
TaskModal.instance.onSubmit([createNewTask(expected)]);
6555
const result = await taskLinePromise;
6656

6757
expect(result).toEqual(expected);
6858
});
6959

60+
/**
61+
* If the Modal is cancelled, the api function createTaskLineModal() should return an empty string
62+
*/
7063
it('should return an empty string if cancelled', async () => {
71-
const taskLinePromise = createTaskLineModal(app, mockModalFactory(modalMock));
64+
const taskLinePromise = createTaskLineModal(app, []);
7265
const expected = '';
7366

74-
modalMock.task = createNewTask(expected);
75-
modalMock.cancel();
67+
TaskModal.instance.cancel();
7668

7769
const result = await taskLinePromise;
7870
expect(result).toEqual(expected);
7971
});
72+
73+
it('should pass allTasks to TaskModal', async () => {
74+
const allTasks = [createNewTask('- [ ] test')];
75+
createTaskLineModal(app, allTasks);
76+
77+
expect(TaskModal.instance.allTasks).toEqual(allTasks);
78+
});
8079
});

tests/Api/executeToggleTaskDoneCommand.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44

55
import moment from 'moment';
6-
import type { App } from 'obsidian';
6+
import type TasksPlugin from '../../src/main';
77
import { tasksApiV1 } from '../../src/Api';
88

99
// This needs to be mocked because the API imports TaskModal which extends Obsidian's Modal
@@ -24,7 +24,7 @@ describe('APIv1 - executeToggleTaskDoneCommand', () => {
2424
jest.useRealTimers();
2525
});
2626

27-
const api = tasksApiV1({} as App);
27+
const api = tasksApiV1({} as TasksPlugin);
2828

2929
// This is a simple smoke test to make sure executeToggleTaskDoneCommand is working. Its core
3030
// functionality is covered by other tests

tests/Api/index.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type { App } from 'obsidian';
2+
import type TasksPlugin from '../../src/main';
3+
import type { Task } from '../../src/Task/Task';
4+
import { createTaskLineModal } from '../../src/Api/createTaskLineModal';
5+
import { tasksApiV1 } from '../../src/Api/index';
6+
7+
jest.mock('../../src/Api/createTaskLineModal', () => ({
8+
createTaskLineModal: jest.fn(),
9+
}));
10+
11+
describe('definition of public Api', () => {
12+
it('should call createTaskLineModal with the app and allTasks', async () => {
13+
const task = jest.fn();
14+
const app = {} as App; // Mock the app object
15+
const tasks = [task as Partial<Task>];
16+
const mockPlugin = {
17+
getTasks: () => tasks,
18+
app,
19+
} as Partial<TasksPlugin> as TasksPlugin;
20+
21+
const publicApi = tasksApiV1(mockPlugin);
22+
23+
await publicApi.createTaskLineModal();
24+
expect(createTaskLineModal).toHaveBeenCalledWith(app, tasks);
25+
});
26+
});

tests/__mocks__/TaskModal.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import type { App } from 'obsidian';
2+
import type { Task } from '../../src/Task/Task';
3+
4+
/**
5+
* {@link TaskModal} needs to be mocked, because it depends on {@link obsidian.Modal}, which is not available.
6+
*/
7+
export class TaskModal {
8+
public static instance: TaskModal;
9+
public readonly app: App;
10+
public readonly task: Task;
11+
public readonly onSubmit: (updateTasks: Task[]) => void;
12+
public readonly allTasks: Task[];
13+
14+
public readonly open: () => void;
15+
16+
constructor({
17+
app,
18+
task,
19+
onSubmit,
20+
allTasks,
21+
}: {
22+
app: App;
23+
task: Task;
24+
onSubmit: (updatedTasks: Task[]) => void;
25+
allTasks?: Task[];
26+
}) {
27+
this.app = app;
28+
this.task = task;
29+
this.onSubmit = onSubmit;
30+
this.open = jest.fn();
31+
this.allTasks = allTasks || [];
32+
33+
TaskModal.instance = this;
34+
}
35+
36+
public cancel(): void {
37+
this.onSubmit([]);
38+
}
39+
}

0 commit comments

Comments
 (0)