Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ export const routes: Routes = [
},
{
path: 'decisions/:decisionId/edit',
redirectTo: 'decisions',
pathMatch: 'full'
loadComponent: () => import('./components/decision-form/decision-form.component').then(m => m.DecisionFormComponent),
canActivate: [authGuard]
},
{ path: '', redirectTo: 'decisions', pathMatch: 'full' }
]
Expand Down
74 changes: 74 additions & 0 deletions src/app/components/decision-form/decision-form.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DecisionFormComponent } from './decision-form.component';
import { DecisionService } from '../../services/decision.service';
import { of } from 'rxjs';
import { RouterTestingModule } from '@angular/router/testing';
import { ActivatedRoute, convertToParamMap } from '@angular/router';
import { ReactiveFormsModule } from '@angular/forms';

describe('DecisionFormComponent', () => {
let component: DecisionFormComponent;
let fixture: ComponentFixture<DecisionFormComponent>;
let mockDecisionService: any;

beforeEach(async () => {
mockDecisionService = {
getDecision: jasmine.createSpy('getDecision').and.returnValue(of({
id: '1',
title: 'Loaded Decision',
description: 'Loaded Description',
status: 'OPEN',
workspaceId: '10'
})),
updateDecision: jasmine.createSpy('updateDecision').and.returnValue(of({
id: '1',
title: 'Updated Decision',
status: 'CLOSED'
})),
createDecision: jasmine.createSpy('createDecision').and.returnValue(of({}))
};

await TestBed.configureTestingModule({
imports: [DecisionFormComponent, RouterTestingModule, ReactiveFormsModule],
providers: [
{ provide: DecisionService, useValue: mockDecisionService },
{
provide: ActivatedRoute,
useValue: {
paramMap: of(convertToParamMap({ decisionId: '1' })),
snapshot: { paramMap: convertToParamMap({ id: '10' }) },
pathFromRoot: []
}
}
]
}).compileComponents();

fixture = TestBed.createComponent(DecisionFormComponent);
component = fixture.componentInstance;
// Mock resolveWorkspaceId to return '10'
spyOn<any>(component, 'resolveWorkspaceId').and.returnValue('10');
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});

it('should enter edit mode when decisionId is present', () => {
expect(component.isEditMode).toBeTrue();
expect(component.decisionId).toBe('1');
expect(mockDecisionService.getDecision).toHaveBeenCalledWith('10', '1');
});

it('should populate form with decision data', () => {
expect(component.decisionForm.value.title).toBe('Loaded Decision');
expect(component.decisionForm.value.description).toBe('Loaded Description');
expect(component.decisionForm.value.status).toBe('OPEN');
});

it('should call updateDecision on submit in edit mode', () => {
component.decisionForm.patchValue({ title: 'Updated Title' });
component.onSubmit();
expect(mockDecisionService.updateDecision).toHaveBeenCalled();
});
});
18 changes: 11 additions & 7 deletions src/app/components/decision-form/decision-form.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { CommonModule } from '@angular/common';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { Subject, Subscription, takeUntil, timeout } from 'rxjs';
import { finalize, Subject, Subscription, takeUntil, timeout } from 'rxjs';
import { Decision } from '../../models/decision.model';
import { DecisionService } from '../../services/decision.service';

Expand Down Expand Up @@ -251,10 +251,12 @@ export class DecisionFormComponent implements OnInit, OnDestroy {
this.loadSubscription = this.decisionService.getDecision(this.workspaceId, id).pipe(
timeout(this.requestTimeoutMs),
takeUntil(this.destroy$),
finalize(() => {
this.isLoading = false;
this.clearLoadGuardTimer();
})
).subscribe({
next: (decision) => {
this.clearActiveLoad();
this.isLoading = false;
if (!decision) {
this.submitError = 'Decision not found.';
return;
Expand All @@ -268,8 +270,6 @@ export class DecisionFormComponent implements OnInit, OnDestroy {
});
},
error: (error) => {
this.clearActiveLoad();
this.isLoading = false;
this.submitError = this.resolveSubmitError(error, 'Unable to load decision. Please try again.');
}
});
Expand All @@ -283,13 +283,17 @@ export class DecisionFormComponent implements OnInit, OnDestroy {
}

private resolveWorkspaceId(): string | null {
for (const route of this.route.pathFromRoot) {
const id = route.snapshot.paramMap.get('id');
// Try to get from route hierarchy first
let currentRoute: ActivatedRoute | null = this.route;
while (currentRoute) {
const id = currentRoute.snapshot.paramMap.get('id');
if (id) {
return id;
}
currentRoute = currentRoute.parent;
}

// Fallback to URL parsing if route hierarchy fails
const urlMatch = this.router.url.match(/\/workspaces\/([^/]+)/);
if (urlMatch?.[1]) {
return urlMatch[1];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ <h3>{{ decision.title }}</h3>
</span>
</div>
<div class="decision-actions">
<a [routerLink]="[decision.id, 'edit']" class="btn-secondary">Edit</a>
<button (click)="deleteDecision(decision.id)" class="btn-danger">Delete</button>
</div>
</div>
Expand Down
73 changes: 73 additions & 0 deletions src/app/components/decision-list/decision-list.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DecisionListComponent } from './decision-list.component';
import { DecisionService } from '../../services/decision.service';
import { of } from 'rxjs';
import { RouterTestingModule } from '@angular/router/testing';
import { ActivatedRoute } from '@angular/router';
import { Decision } from '../../models/decision.model';
import { By } from '@angular/platform-browser';

describe('DecisionListComponent', () => {
let component: DecisionListComponent;
let fixture: ComponentFixture<DecisionListComponent>;
let mockDecisionService: any;

const mockDecisions: Decision[] = [
{
id: '1',
workspaceId: '10',
userId: '3',
title: 'Test Decision',
description: 'Test Description',
status: 'OPEN',
createdAt: new Date(),
updatedAt: new Date(),
isDeleted: false
}
];

beforeEach(async () => {
mockDecisionService = {
getDecisions: jasmine.createSpy('getDecisions').and.returnValue(of(mockDecisions)),
deleteDecision: jasmine.createSpy('deleteDecision').and.returnValue(of(void 0))
};

await TestBed.configureTestingModule({
imports: [DecisionListComponent, RouterTestingModule],
providers: [
{ provide: DecisionService, useValue: mockDecisionService },
{
provide: ActivatedRoute,
useValue: {
pathFromRoot: [
{ snapshot: { paramMap: { get: () => '10' } } }
]
}
}
]
}).compileComponents();

fixture = TestBed.createComponent(DecisionListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});

it('should render an edit button for each decision', () => {
const compiled = fixture.nativeElement as HTMLElement;
const editButtons = compiled.querySelectorAll('.btn-secondary');
expect(editButtons.length).toBe(1);
expect(editButtons[0].textContent).toContain('Edit');
});

it('should have correct edit link', () => {
const editButton = fixture.debugElement.query(By.css('.btn-secondary'));
expect(editButton).toBeTruthy();
// Since it's a relative link [decision.id, 'edit'], we check if the attribute is present or just trust the binding
const link = editButton.nativeElement as HTMLAnchorElement;
expect(link.textContent).toContain('Edit');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ export class WorkspaceDetailsComponent implements OnInit {
) { }

ngOnInit(): void {
let currentId: string | null = null;
this.route.paramMap.subscribe(params => {
const id = params.get('id');
if (id) {
if (id && id !== currentId) {
currentId = id;
this.workspace$ = this.workspaceService.getWorkspace(id);
}
});
Expand Down
Loading