diff --git a/frontend/src/app/assignment/modules/edit-assignment/task-markdown.service.spec.ts b/frontend/src/app/assignment/modules/edit-assignment/task-markdown.service.spec.ts new file mode 100644 index 00000000..13fabc02 --- /dev/null +++ b/frontend/src/app/assignment/modules/edit-assignment/task-markdown.service.spec.ts @@ -0,0 +1,44 @@ +import {TestBed} from '@angular/core/testing'; +import {TaskMarkdownService} from './task-markdown.service'; +import Task from '../../model/task'; +import {TaskService} from '../../services/task.service'; + +describe(TaskMarkdownService.name, () => { + let md: TaskMarkdownService; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [TaskService, TaskMarkdownService], + }); + md = TestBed.inject(TaskMarkdownService); + }); + + const tasks: Task[] = [ + { + _id: 'A', description: 'A', points: 1, children: [ + {_id: 'A.1', description: 'A.1', points: 2, children: []}, + ], + }, + // See https://github.com/fujaba/fulib.org/issues/441. + // This must output as a ## headline. + {_id: 'B', description: 'B', points: -1, children: []}, + // And to avoid C becoming a child of B, it must also output as a ## headline. + {_id: 'C', description: 'C', points: 3, children: []}, + ]; + const markdown = `\ +## A (1P) +- A.1 (2P) +## B (-1P) +## C (3P) +`; + + it('should render markdown', () => { + const actual = md.renderTasks(tasks); + expect(actual).toBe(markdown); + }); + + it('should parse markdown', () => { + const actual = md.parseTasks(markdown); + expect(actual).toEqual(tasks); + }); +}); diff --git a/frontend/src/app/assignment/modules/edit-assignment/task-markdown.service.ts b/frontend/src/app/assignment/modules/edit-assignment/task-markdown.service.ts index 083159af..a787614f 100644 --- a/frontend/src/app/assignment/modules/edit-assignment/task-markdown.service.ts +++ b/frontend/src/app/assignment/modules/edit-assignment/task-markdown.service.ts @@ -11,8 +11,8 @@ export class TaskMarkdownService { } parseTasks(markdown: string): Task[] { - // # Assignment 1 (xP/100P) - // ## Task 1 (xP/30P) + // # Assignment 1 (100P) + // ## Task 1 (30P) // - Something wrong (-1P) const taskStack: Task[][] = [[]]; for (const line of markdown.split('\n')) { @@ -28,7 +28,6 @@ export class TaskMarkdownService { _id: _id || this.taskService.generateID(), points: +points, children: [], - collapsed: true, }; switch (prefix) { case '-': @@ -47,20 +46,29 @@ export class TaskMarkdownService { } renderTasks(tasks: Task[], depth = 0) { - return tasks.map(t => this.renderTask(t, depth)).join(''); + let result = ''; + let lastTaskWasHeadline = false; + for (const task of tasks) { + const line = this.renderTask(task, depth, lastTaskWasHeadline); + result += line; + lastTaskWasHeadline = line.startsWith('#'); + } + return result; } - private renderTask(task: Task, depth: number) { + private renderTask(task: Task, depth: number, asHeadline: boolean) { const {children, deleted, description, points, ...rest} = task; if (deleted) { return ''; } const extra = JSON.stringify(rest); - if (points < 0 || !children || children.length === 0) { + // If the previous task had children, we need to write this as a new headline. + // Otherwise, it would end up as a subtask of the previous task without a way to distinguish it. + if (!asHeadline && (points < 0 || !children || children.length === 0)) { return `- ${description} (${points}P)\n`; } const childrenMd = this.renderTasks(children, depth + 1); const headlinePrefix = '#'.repeat(depth + 2); - return `${headlinePrefix} ${description} (x/${points}P)\n${childrenMd}`; + return `${headlinePrefix} ${description} (${points}P)\n${childrenMd}`; } }