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
+177
View File
@@ -5,6 +5,7 @@ import { provideRouter } from '@angular/router';
import { vi } from 'vitest';
import { Issues } from './issues';
import { IssueEntity, IssuesStore } from './issues.store';
import { MilestoneEntity, MilestonesStore } from '../milestones/milestones.store';
const makeIssue = (overrides: Partial<IssueEntity> = {}): IssueEntity => ({
id: 99,
@@ -88,19 +89,63 @@ 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);
}
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();
}
}
describe('Issues', () => {
let component: Issues;
let fixture: ComponentFixture<Issues>;
let store: FakeIssuesStore;
let milestonesStore: FakeMilestonesStore;
let router: Router;
beforeEach(async () => {
store = new FakeIssuesStore();
milestonesStore = new FakeMilestonesStore();
await TestBed.configureTestingModule({
imports: [Issues],
providers: [
provideRouter([]),
{ provide: IssuesStore, useValue: store },
{ provide: MilestonesStore, useValue: milestonesStore },
],
}).compileComponents();
@@ -259,6 +304,138 @@ describe('Issues', () => {
});
});
describe('getMilestoneForIssue', () => {
it('returns the milestone that contains the issue', () => {
milestonesStore.seed([makeMilestone({ id: 10, name: 'Sprint A', issueIds: [1] })]);
const m = (component as any).getMilestoneForIssue(1);
expect(m?.id).toBe(10);
});
it('returns undefined when no milestone contains the issue', () => {
milestonesStore.seed([makeMilestone({ id: 10, name: 'Sprint A', issueIds: [99] })]);
expect((component as any).getMilestoneForIssue(1)).toBeUndefined();
});
});
describe('filteredIssues — milestone filter', () => {
beforeEach(() => {
milestonesStore.seed([
makeMilestone({ id: 10, name: 'Sprint A', issueIds: [1] }),
makeMilestone({ id: 20, name: 'Sprint B', issueIds: [2] }),
]);
});
it('shows all issues when no milestone filter is active', () => {
expect((component as any).filteredIssues.length).toBe(3);
});
it('shows only issues of the selected milestone', () => {
(component as any).selectedMilestoneIds = new Set([10]);
const filtered: IssueEntity[] = (component as any).filteredIssues;
expect(filtered.length).toBe(1);
expect(filtered[0].id).toBe(1);
});
it('shows issues from multiple selected milestones', () => {
(component as any).selectedMilestoneIds = new Set([10, 20]);
const filtered: IssueEntity[] = (component as any).filteredIssues;
expect(filtered.map((i) => i.id).sort()).toEqual([1, 2]);
});
it('shows only issues without milestone when showNoMilestone is true', () => {
(component as any).showNoMilestone = true;
const filtered: IssueEntity[] = (component as any).filteredIssues;
expect(filtered.length).toBe(1);
expect(filtered[0].id).toBe(3);
});
it('combines milestone selection and no-milestone option as OR', () => {
(component as any).selectedMilestoneIds = new Set([10]);
(component as any).showNoMilestone = true;
const filtered: IssueEntity[] = (component as any).filteredIssues;
expect(filtered.map((i) => i.id).sort()).toEqual([1, 3]);
});
});
describe('toggleMilestone', () => {
it('adds a milestone id when not already selected', () => {
(component as any).toggleMilestone(10, mockEvent);
expect((component as any).selectedMilestoneIds.has(10)).toBe(true);
});
it('removes a milestone id when already selected', () => {
(component as any).selectedMilestoneIds = new Set([10]);
(component as any).toggleMilestone(10, mockEvent);
expect((component as any).selectedMilestoneIds.has(10)).toBe(false);
});
});
describe('toggleNoMilestone', () => {
it('sets showNoMilestone to true when false', () => {
(component as any).showNoMilestone = false;
(component as any).toggleNoMilestone(mockEvent);
expect((component as any).showNoMilestone).toBe(true);
});
it('sets showNoMilestone to false when true', () => {
(component as any).showNoMilestone = true;
(component as any).toggleNoMilestone(mockEvent);
expect((component as any).showNoMilestone).toBe(false);
});
});
describe('clearMilestones', () => {
it('clears selected milestone ids and showNoMilestone', () => {
(component as any).selectedMilestoneIds = new Set([10, 20]);
(component as any).showNoMilestone = true;
(component as any).clearMilestones(mockEvent);
expect((component as any).selectedMilestoneIds.size).toBe(0);
expect((component as any).showNoMilestone).toBe(false);
});
});
describe('milestoneDropdownLabel', () => {
it('returns "Milestone" when nothing is selected', () => {
expect((component as any).milestoneDropdownLabel()).toBe('Milestone');
});
it('returns "Sans milestone" when only showNoMilestone is true', () => {
(component as any).showNoMilestone = true;
expect((component as any).milestoneDropdownLabel()).toBe('Sans milestone');
});
it('returns the milestone name when exactly one milestone is selected', () => {
milestonesStore.seed([makeMilestone({ id: 10, name: 'Sprint A', issueIds: [] })]);
(component as any).selectedMilestoneIds = new Set([10]);
expect((component as any).milestoneDropdownLabel()).toBe('Sprint A');
});
it('returns a count when multiple filters are active', () => {
milestonesStore.seed([
makeMilestone({ id: 10, name: 'Sprint A', issueIds: [] }),
makeMilestone({ id: 20, name: 'Sprint B', issueIds: [] }),
]);
(component as any).selectedMilestoneIds = new Set([10, 20]);
expect((component as any).milestoneDropdownLabel()).toBe('Milestone (2)');
});
});
describe('milestoneFilterActive', () => {
it('is false when nothing is selected', () => {
expect((component as any).milestoneFilterActive).toBe(false);
});
it('is true when a milestone id is selected', () => {
(component as any).selectedMilestoneIds = new Set([10]);
expect((component as any).milestoneFilterActive).toBe(true);
});
it('is true when showNoMilestone is true', () => {
(component as any).showNoMilestone = true;
expect((component as any).milestoneFilterActive).toBe(true);
});
});
describe('typeBadgeClass', () => {
it('maps Bug to text-bg-danger', () => {
expect((component as any).typeBadgeClass('Bug')).toBe('text-bg-danger');