diff --git a/src/app/issues/issue-comments/issue-comments.spec.ts b/src/app/issues/issue-comments/issue-comments.spec.ts index 74f3ae8..1989613 100644 --- a/src/app/issues/issue-comments/issue-comments.spec.ts +++ b/src/app/issues/issue-comments/issue-comments.spec.ts @@ -4,6 +4,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { afterEach, vi } from 'vitest'; import { IssueComments } from './issue-comments'; import { IssueEntity, IssuesStore } from '../issues.store'; +import { MilestoneEntity, MilestonesStore } from '../../milestones/milestones.store'; const makeIssue = (overrides: Partial = {}): IssueEntity => ({ id: 1, @@ -81,16 +82,60 @@ class FakeIssuesStore { } } +const makeMilestone = (overrides: Partial = {}): MilestoneEntity => ({ + id: 1, + name: 'Sprint 1', + description: '', + startDate: '', + endDate: '', + dueDate: '', + issueIds: [], + dependsOnIds: [], + ...overrides, +}); + +class FakeMilestonesStore { + private _data = signal([]); + + readonly milestones = this._data.asReadonly(); + readonly loading = signal(false); + readonly loaded = signal(true); + + getById(id: number): MilestoneEntity | undefined { + return this._data().find((m) => m.id === id); + } + + load(): Promise { + return Promise.resolve(); + } + + upsert(milestone: MilestoneEntity): Promise { + this._data.update((list) => { + const idx = list.findIndex((m) => m.id === milestone.id); + if (idx === -1) return [...list, milestone]; + const copy = [...list]; + copy[idx] = milestone; + return copy; + }); + return Promise.resolve(milestone); + } +} + describe('IssueComments', () => { let component: IssueComments; let fixture: ComponentFixture; let store: FakeIssuesStore; + let milestonesStore: FakeMilestonesStore; beforeEach(async () => { store = new FakeIssuesStore(); + milestonesStore = new FakeMilestonesStore(); await TestBed.configureTestingModule({ imports: [IssueComments, RouterTestingModule], - providers: [{ provide: IssuesStore, useValue: store }], + providers: [ + { provide: IssuesStore, useValue: store }, + { provide: MilestonesStore, useValue: milestonesStore }, + ], }).compileComponents(); fixture = TestBed.createComponent(IssueComments); @@ -394,6 +439,46 @@ describe('IssueComments', () => { expect(store.issues().filter((i) => i.type === 'Task').length).toBe(0); }); + + it('inherits the epic from the parent issue', async () => { + await store.upsert(makeIssue({ id: 1, epic: 'Mon Epic', comments: store.getById(1)!.comments })); + const commentId = store.getById(1)!.comments[0].id; + (component as any).newTaskName = 'Tâche avec epic'; + await (component as any).createTaskForComment(commentId); + + const task = store.issues().find((i) => i.type === 'Task'); + expect(task?.epic).toBe('Mon Epic'); + }); + + it('does not set epic when parent issue has none', async () => { + const commentId = store.getById(1)!.comments[0].id; + (component as any).newTaskName = 'Tâche sans epic'; + await (component as any).createTaskForComment(commentId); + + const task = store.issues().find((i) => i.type === 'Task'); + expect(task?.epic).toBe(''); + }); + + it('adds the new task to the same milestone as the parent issue', async () => { + await milestonesStore.upsert(makeMilestone({ id: 10, name: 'Release 1', issueIds: [1] })); + const commentId = store.getById(1)!.comments[0].id; + (component as any).newTaskName = 'Tâche dans milestone'; + await (component as any).createTaskForComment(commentId); + + const task = store.issues().find((i) => i.type === 'Task')!; + expect(milestonesStore.getById(10)!.issueIds).toContain(task.id); + }); + + it('does not modify any milestone when parent issue is not in one', async () => { + await milestonesStore.upsert(makeMilestone({ id: 10, name: 'Release 1', issueIds: [99] })); + const commentId = store.getById(1)!.comments[0].id; + (component as any).newTaskName = 'Tâche hors milestone'; + await (component as any).createTaskForComment(commentId); + + expect(milestonesStore.getById(10)!.issueIds).not.toContain( + store.issues().find((i) => i.type === 'Task')!.id, + ); + }); }); describe('linkIssueToComment', () => { diff --git a/src/app/issues/issue-comments/issue-comments.ts b/src/app/issues/issue-comments/issue-comments.ts index 5861332..1f9e920 100644 --- a/src/app/issues/issue-comments/issue-comments.ts +++ b/src/app/issues/issue-comments/issue-comments.ts @@ -6,6 +6,7 @@ import { marked } from 'marked'; import { handleImagePaste, insertAtSelection } from '../paste-image.util'; import { IssueComment, IssueEntity, IssuesStore } from '../issues.store'; import { StatusEntity, StatusesStore } from '../../settings/statuses/statuses.store'; +import { MilestonesStore } from '../../milestones/milestones.store'; @Component({ selector: 'app-issue-comments', @@ -15,6 +16,7 @@ import { StatusEntity, StatusesStore } from '../../settings/statuses/statuses.st }) export class IssueComments { private readonly issuesStore = inject(IssuesStore); + private readonly milestonesStore = inject(MilestonesStore); private readonly sanitizer = inject(DomSanitizer); private readonly statusesStore = inject(StatusesStore); @@ -169,7 +171,7 @@ export class IssueComments { type: 'Task', name, assignee: '', - epic: '', + epic: issue.epic, startDate: '', startDateMode: 'forced', endDate: '', @@ -189,6 +191,12 @@ export class IssueComments { return { ...c, linkedIssueIds: [...c.linkedIssueIds, created.id] }; }); await this.issuesStore.upsert({ ...issue, comments: updatedComments }); + + const milestone = this.milestonesStore.milestones().find((m) => m.issueIds.includes(issue.id)); + if (milestone) { + await this.milestonesStore.upsert({ ...milestone, issueIds: [...milestone.issueIds, created.id] }); + } + this.creatingTaskForCommentId = null; this.newTaskName = ''; }