Ajoute date debut et date de fin
Signed-off-by: Gato <cedric@goutailler-olivier.fr>
This commit is contained in:
@@ -11,6 +11,8 @@ const makeIssue = (overrides: Partial<IssueEntity> = {}): IssueEntity => ({
|
||||
assignee: '',
|
||||
epic: '',
|
||||
name: 'Test Issue',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
dueDate: '',
|
||||
description: '',
|
||||
estimatedTime: null,
|
||||
|
||||
@@ -170,6 +170,8 @@ export class IssueComments {
|
||||
name,
|
||||
assignee: '',
|
||||
epic: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
dueDate: '',
|
||||
description: '',
|
||||
estimatedTime: null,
|
||||
|
||||
@@ -153,6 +153,33 @@
|
||||
<label class="field-label">Assignee</label>
|
||||
<input aria-label="Assignee" class="form-control form-control-sm" type="text" [(ngModel)]="issue.assignee" (blur)="saveIssue()" />
|
||||
</div>
|
||||
<div class="row g-2">
|
||||
<div class="col-6">
|
||||
<label class="field-label">Date de début</label>
|
||||
<input
|
||||
aria-label="Date de début"
|
||||
class="form-control form-control-sm"
|
||||
[class.is-invalid]="!!dateValidationError"
|
||||
type="date"
|
||||
[(ngModel)]="issue.startDate"
|
||||
(blur)="saveIssue()"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label class="field-label">Date de fin</label>
|
||||
<input
|
||||
aria-label="Date de fin"
|
||||
class="form-control form-control-sm"
|
||||
[class.is-invalid]="!!dateValidationError"
|
||||
type="date"
|
||||
[(ngModel)]="issue.endDate"
|
||||
(blur)="saveIssue()"
|
||||
/>
|
||||
</div>
|
||||
@if (dateValidationError) {
|
||||
<div class="col-12 text-danger small">{{ dateValidationError }}</div>
|
||||
}
|
||||
</div>
|
||||
<div>
|
||||
<label class="field-label">Date d'échéance</label>
|
||||
<input aria-label="Date d'échéance" class="form-control form-control-sm" type="date" [(ngModel)]="issue.dueDate" (blur)="saveIssue()" />
|
||||
|
||||
@@ -14,6 +14,8 @@ const makeIssue = (overrides: Partial<IssueEntity> = {}): IssueEntity => ({
|
||||
assignee: '',
|
||||
epic: '',
|
||||
name: 'Test Issue',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
dueDate: '',
|
||||
description: '',
|
||||
estimatedTime: null,
|
||||
@@ -213,6 +215,86 @@ describe('IssueDetail — existing issue', () => {
|
||||
(component as any).saveIssue();
|
||||
expect(store.issues().length).toBe(countBefore);
|
||||
});
|
||||
|
||||
it('does not persist when dateValidationError is set', async () => {
|
||||
(component as any).issue.name = 'Has Dates';
|
||||
(component as any).issue.startDate = '2026-02-01';
|
||||
(component as any).issue.endDate = '2026-01-01';
|
||||
await (component as any).saveIssue();
|
||||
expect(store.getById(1)?.name).not.toBe('Has Dates');
|
||||
});
|
||||
});
|
||||
|
||||
describe('dateValidationError', () => {
|
||||
it('returns null when both dates are empty', () => {
|
||||
(component as any).issue.startDate = '';
|
||||
(component as any).issue.endDate = '';
|
||||
expect((component as any).dateValidationError).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null when only startDate is set', () => {
|
||||
(component as any).issue.startDate = '2026-01-01';
|
||||
(component as any).issue.endDate = '';
|
||||
expect((component as any).dateValidationError).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null when only endDate is set', () => {
|
||||
(component as any).issue.startDate = '';
|
||||
(component as any).issue.endDate = '2026-01-31';
|
||||
expect((component as any).dateValidationError).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null when startDate equals endDate', () => {
|
||||
(component as any).issue.startDate = '2026-01-15';
|
||||
(component as any).issue.endDate = '2026-01-15';
|
||||
expect((component as any).dateValidationError).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null when startDate is before endDate', () => {
|
||||
(component as any).issue.startDate = '2026-01-01';
|
||||
(component as any).issue.endDate = '2026-01-31';
|
||||
expect((component as any).dateValidationError).toBeNull();
|
||||
});
|
||||
|
||||
it('returns an error when startDate is after endDate', () => {
|
||||
(component as any).issue.startDate = '2026-02-01';
|
||||
(component as any).issue.endDate = '2026-01-01';
|
||||
expect((component as any).dateValidationError).toContain('supérieure à la date de fin');
|
||||
});
|
||||
|
||||
it('returns null when dependency has no endDate and startDate is set', async () => {
|
||||
store.upsert(makeIssue({ id: 10, startDate: '2026-01-01', endDate: '' }));
|
||||
(component as any).issue.dependsOnIds = [10];
|
||||
(component as any).issue.startDate = '2025-12-01';
|
||||
expect((component as any).dateValidationError).toBeNull();
|
||||
});
|
||||
|
||||
it('returns an error when startDate is before dependency endDate (Finish-to-Start)', async () => {
|
||||
store.upsert(makeIssue({ id: 10, endDate: '2026-02-01' }));
|
||||
(component as any).issue.dependsOnIds = [10];
|
||||
(component as any).issue.startDate = '2026-01-15';
|
||||
expect((component as any).dateValidationError).toContain('#10');
|
||||
});
|
||||
|
||||
it('returns null when startDate equals dependency endDate', async () => {
|
||||
store.upsert(makeIssue({ id: 10, endDate: '2026-02-01' }));
|
||||
(component as any).issue.dependsOnIds = [10];
|
||||
(component as any).issue.startDate = '2026-02-01';
|
||||
expect((component as any).dateValidationError).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null when startDate is after dependency endDate', async () => {
|
||||
store.upsert(makeIssue({ id: 10, endDate: '2026-02-01' }));
|
||||
(component as any).issue.dependsOnIds = [10];
|
||||
(component as any).issue.startDate = '2026-02-15';
|
||||
expect((component as any).dateValidationError).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null when there are no dependencies', () => {
|
||||
(component as any).issue.dependsOnIds = [];
|
||||
(component as any).issue.startDate = '2026-01-01';
|
||||
expect((component as any).dateValidationError).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteIssue', () => {
|
||||
|
||||
@@ -170,6 +170,8 @@ export class IssueDetail {
|
||||
assignee: '',
|
||||
epic: this.issue.name,
|
||||
name,
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
dueDate: '',
|
||||
description: '',
|
||||
estimatedTime: null,
|
||||
@@ -235,6 +237,22 @@ export class IssueDetail {
|
||||
return !!this.issue.epic;
|
||||
}
|
||||
|
||||
protected get dateValidationError(): string | null {
|
||||
const { startDate, endDate } = this.issue;
|
||||
if (startDate && endDate && startDate > endDate) {
|
||||
return 'La date de début ne peut pas être supérieure à la date de fin.';
|
||||
}
|
||||
if (startDate && this.issue.dependsOnIds.length > 0) {
|
||||
for (const depId of this.issue.dependsOnIds) {
|
||||
const dep = this.issuesStore.getById(depId);
|
||||
if (dep?.endDate && startDate < dep.endDate) {
|
||||
return `La date de début ne peut pas être antérieure à la date de fin de la dépendance #${depId}.`;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected startEditDescription(): void {
|
||||
this._descriptionBeforeEdit = this.issue.description;
|
||||
this.editingDescription = true;
|
||||
@@ -358,6 +376,7 @@ export class IssueDetail {
|
||||
protected async saveIssue(explicit = false): Promise<void> {
|
||||
if (this.isNewIssueRoute && !explicit) return;
|
||||
if (!this.issue.name.trim()) return;
|
||||
if (this.dateValidationError) return;
|
||||
const saved = await this.issuesStore.upsert(this.issue);
|
||||
this.issue = { ...saved };
|
||||
if (this.isNewIssueRoute) {
|
||||
@@ -413,6 +432,8 @@ export class IssueDetail {
|
||||
assignee: '',
|
||||
epic: '',
|
||||
name: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
dueDate: '',
|
||||
description: '',
|
||||
estimatedTime: null,
|
||||
@@ -434,6 +455,8 @@ export class IssueDetail {
|
||||
assignee: '',
|
||||
epic: '',
|
||||
name: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
dueDate: '',
|
||||
description: '',
|
||||
estimatedTime: null,
|
||||
|
||||
@@ -13,6 +13,8 @@ const makeIssue = (overrides: Partial<IssueEntity> = {}): IssueEntity => ({
|
||||
assignee: '',
|
||||
epic: '',
|
||||
name: 'Test Issue',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
dueDate: '',
|
||||
description: '',
|
||||
estimatedTime: null,
|
||||
|
||||
@@ -10,6 +10,8 @@ const makeIssue = (overrides: Partial<IssueEntity> = {}): IssueEntity => ({
|
||||
assignee: '',
|
||||
epic: '',
|
||||
name: 'Test Issue',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
dueDate: '',
|
||||
description: '',
|
||||
estimatedTime: null,
|
||||
@@ -156,6 +158,31 @@ describe('IssuesStore', () => {
|
||||
await p;
|
||||
expect(store.getById(994)?.estimatedTime).toBeNull();
|
||||
});
|
||||
|
||||
it('defaults startDate to empty string when missing in API response', async () => {
|
||||
const issue = { ...makeIssue({ id: 0 }), startDate: undefined } as any;
|
||||
const p = store.upsert(issue);
|
||||
httpMock.expectOne({ method: 'POST', url: `${API_BASE_URL}/issues` }).flush({ ...makeIssue({ id: 993 }), startDate: undefined });
|
||||
await p;
|
||||
expect(store.getById(993)?.startDate).toBe('');
|
||||
});
|
||||
|
||||
it('defaults endDate to empty string when missing in API response', async () => {
|
||||
const issue = { ...makeIssue({ id: 0 }), endDate: undefined } as any;
|
||||
const p = store.upsert(issue);
|
||||
httpMock.expectOne({ method: 'POST', url: `${API_BASE_URL}/issues` }).flush({ ...makeIssue({ id: 992 }), endDate: undefined });
|
||||
await p;
|
||||
expect(store.getById(992)?.endDate).toBe('');
|
||||
});
|
||||
|
||||
it('preserves startDate and endDate when provided', async () => {
|
||||
const issue = makeIssue({ id: 0, startDate: '2026-01-01', endDate: '2026-01-31' });
|
||||
const p = store.upsert(issue);
|
||||
httpMock.expectOne({ method: 'POST', url: `${API_BASE_URL}/issues` }).flush(makeIssue({ id: 991, startDate: '2026-01-01', endDate: '2026-01-31' }));
|
||||
await p;
|
||||
expect(store.getById(991)?.startDate).toBe('2026-01-01');
|
||||
expect(store.getById(991)?.endDate).toBe('2026-01-31');
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteById', () => {
|
||||
|
||||
@@ -20,6 +20,8 @@ export type IssueEntity = {
|
||||
assignee: string;
|
||||
epic: string;
|
||||
name: string;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
dueDate: string;
|
||||
description: string;
|
||||
estimatedTime: number | null;
|
||||
@@ -110,6 +112,8 @@ export class IssuesStore {
|
||||
return {
|
||||
...issue,
|
||||
type: issue.type ?? 'Story',
|
||||
startDate: issue.startDate ?? '',
|
||||
endDate: issue.endDate ?? '',
|
||||
estimatedTime: issue.estimatedTime ?? null,
|
||||
dependsOnIds: normalizedDependencies,
|
||||
comments: Array.isArray(issue.comments)
|
||||
|
||||
Reference in New Issue
Block a user