milestone dans tableau issue
This commit is contained in:
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user