milestone dans tableau issue

This commit is contained in:
2026-05-28 05:57:33 +02:00
parent 8c588ba492
commit e20a009882
8 changed files with 432 additions and 3 deletions
@@ -6,6 +6,7 @@ import { of } from 'rxjs';
import { vi } from 'vitest';
import { IssueDetail } from './issue-detail';
import { IssueEntity, IssuesStore } from '../issues.store';
import { MilestoneEntity, MilestonesStore } from '../../milestones/milestones.store';
const makeIssue = (overrides: Partial<IssueEntity> = {}): IssueEntity => ({
id: 99,
@@ -81,6 +82,51 @@ class FakeIssuesStore {
}
}
const makeMilestone = (overrides: Partial<MilestoneEntity> = {}): MilestoneEntity => ({
id: 1,
name: 'Sprint 1',
description: '',
dueDate: '',
issueIds: [],
...overrides,
});
class FakeMilestonesStore {
private _data = signal<MilestoneEntity[]>([]);
readonly milestones = this._data.asReadonly();
readonly loading = signal(false);
readonly loaded = signal(true);
seed(milestones: MilestoneEntity[]): void {
this._data.set(milestones);
}
getById(id: number): MilestoneEntity | undefined {
return this._data().find((m) => m.id === id);
}
load(): Promise<void> {
return Promise.resolve();
}
upsert(milestone: MilestoneEntity): Promise<MilestoneEntity> {
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);
}
deleteById(id: number): Promise<void> {
this._data.update((list) => list.filter((m) => m.id !== id));
return Promise.resolve();
}
}
function makeRoute(id = '1', path = 'issues/:id') {
return {
snapshot: {
@@ -96,16 +142,19 @@ describe('IssueDetail — existing issue', () => {
let component: IssueDetail;
let fixture: ComponentFixture<IssueDetail>;
let store: FakeIssuesStore;
let milestonesStore: FakeMilestonesStore;
let router: Router;
beforeEach(async () => {
store = new FakeIssuesStore();
milestonesStore = new FakeMilestonesStore();
await TestBed.configureTestingModule({
imports: [IssueDetail],
providers: [
provideRouter([]),
{ provide: ActivatedRoute, useValue: makeRoute('1') },
{ provide: IssuesStore, useValue: store },
{ provide: MilestonesStore, useValue: milestonesStore },
],
}).compileComponents();
@@ -512,6 +561,66 @@ describe('IssueDetail — existing issue', () => {
expect((component as any).descriptionHtml).toBeTruthy();
});
});
describe('milestone selection', () => {
it('currentMilestone returns the milestone that contains the current issue', () => {
milestonesStore.seed([makeMilestone({ id: 10, name: 'Sprint A', issueIds: [1] })]);
expect((component as any).currentMilestone?.id).toBe(10);
});
it('currentMilestone returns undefined when no milestone contains the issue', () => {
milestonesStore.seed([makeMilestone({ id: 10, name: 'Sprint A', issueIds: [99] })]);
expect((component as any).currentMilestone).toBeUndefined();
});
it('currentMilestoneId returns the id of the linked milestone', () => {
milestonesStore.seed([makeMilestone({ id: 10, name: 'Sprint A', issueIds: [1] })]);
expect((component as any).currentMilestoneId).toBe(10);
});
it('currentMilestoneId returns null when no milestone is linked', () => {
milestonesStore.seed([]);
expect((component as any).currentMilestoneId).toBeNull();
});
it('onMilestoneChange adds the issue to the selected milestone', async () => {
milestonesStore.seed([makeMilestone({ id: 10, name: 'Sprint A', issueIds: [] })]);
await (component as any).onMilestoneChange(10);
expect(milestonesStore.getById(10)?.issueIds).toContain(1);
});
it('onMilestoneChange removes the issue from the previous milestone', async () => {
milestonesStore.seed([
makeMilestone({ id: 10, name: 'Sprint A', issueIds: [1] }),
makeMilestone({ id: 20, name: 'Sprint B', issueIds: [] }),
]);
await (component as any).onMilestoneChange(20);
expect(milestonesStore.getById(10)?.issueIds).not.toContain(1);
expect(milestonesStore.getById(20)?.issueIds).toContain(1);
});
it('onMilestoneChange with null removes the issue from the current milestone', async () => {
milestonesStore.seed([makeMilestone({ id: 10, name: 'Sprint A', issueIds: [1] })]);
await (component as any).onMilestoneChange(null);
expect(milestonesStore.getById(10)?.issueIds).not.toContain(1);
});
});
describe('navigateToMilestone', () => {
it('navigates to the current milestone', () => {
milestonesStore.seed([makeMilestone({ id: 10, name: 'Sprint A', issueIds: [1] })]);
const spy = vi.spyOn(router, 'navigate').mockResolvedValue(true);
(component as any).navigateToMilestone();
expect(spy).toHaveBeenCalledWith(['/milestones', 10]);
});
it('does nothing when no milestone is linked', () => {
milestonesStore.seed([]);
const spy = vi.spyOn(router, 'navigate').mockResolvedValue(true);
(component as any).navigateToMilestone();
expect(spy).not.toHaveBeenCalled();
});
});
});
describe('IssueDetail — new issue route', () => {
@@ -538,6 +647,7 @@ describe('IssueDetail — new issue route', () => {
},
},
{ provide: IssuesStore, useValue: store },
{ provide: MilestonesStore, useValue: new FakeMilestonesStore() },
],
}).compileComponents();