test(frontend): enhance AdminMembershipsComponent tests and fix deleteUserTariff compilation error
- Expand admin-memberships spec from 19 to 42 tests covering: approve/reject API calls, isProcessing guards, error handling, loading states, tab switching, card/table views, empty states - Fix deleteUserTariff missing userId argument in user-tariff.ts (required after backend API change)
This commit is contained in:
parent
71ecf47948
commit
43f3bb6d8a
@ -57,89 +57,315 @@ describe('AdminMembershipsComponent', () => {
|
||||
httpMock.verify();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
fixture.detectChanges();
|
||||
function flushPendingRequest(): void {
|
||||
const req = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending'));
|
||||
req.flush([]);
|
||||
}
|
||||
|
||||
it('should create', () => {
|
||||
fixture.detectChanges();
|
||||
flushPendingRequest();
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should default activeTab to pending', () => {
|
||||
fixture.detectChanges();
|
||||
const req = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending'));
|
||||
req.flush([]);
|
||||
expect(component.activeTab()).toBe('pending');
|
||||
describe('initial state', () => {
|
||||
it('should default activeTab to pending', () => {
|
||||
fixture.detectChanges();
|
||||
flushPendingRequest();
|
||||
expect(component.activeTab()).toBe('pending');
|
||||
});
|
||||
|
||||
it('should initialize allMemberships as empty array', () => {
|
||||
fixture.detectChanges();
|
||||
flushPendingRequest();
|
||||
expect(component.allMemberships()).toEqual([]);
|
||||
});
|
||||
|
||||
it('should initialize pendingMemberships as empty array', () => {
|
||||
fixture.detectChanges();
|
||||
flushPendingRequest();
|
||||
expect(component.pendingMemberships()).toEqual([]);
|
||||
});
|
||||
|
||||
it('should set isLoading to false after init completes', () => {
|
||||
fixture.detectChanges();
|
||||
flushPendingRequest();
|
||||
expect(component.isLoading()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should initialize allMemberships as empty array', () => {
|
||||
fixture.detectChanges();
|
||||
const req = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending'));
|
||||
req.flush([]);
|
||||
expect(component.allMemberships()).toEqual([]);
|
||||
describe('loadPendingMemberships', () => {
|
||||
it('should load pending memberships on init', () => {
|
||||
fixture.detectChanges();
|
||||
const req = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending'));
|
||||
expect(req.request.method).toBe('GET');
|
||||
req.flush([mockPendingMembership]);
|
||||
expect(component.pendingMemberships().length).toBe(1);
|
||||
expect(component.pendingMemberships()[0].firstName).toBe('Max');
|
||||
});
|
||||
|
||||
it('should set isLoading to true during loading and false after', () => {
|
||||
fixture.detectChanges();
|
||||
const req = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending'));
|
||||
expect(component.isLoading()).toBe(true);
|
||||
req.flush([]);
|
||||
expect(component.isLoading()).toBe(false);
|
||||
});
|
||||
|
||||
it('should set isLoading to false on failure', () => {
|
||||
fixture.detectChanges();
|
||||
const req = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending'));
|
||||
req.flush('Error', { status: 500, statusText: 'Server Error' });
|
||||
expect(component.isLoading()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should load pending memberships on init', () => {
|
||||
fixture.detectChanges();
|
||||
const req = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending'));
|
||||
expect(req.request.method).toBe('GET');
|
||||
req.flush([mockPendingMembership]);
|
||||
expect(component.pendingMemberships().length).toBe(1);
|
||||
expect(component.pendingMemberships()[0].firstName).toBe('Max');
|
||||
describe('loadAllMemberships', () => {
|
||||
it('should load all memberships when switching to active tab', () => {
|
||||
fixture.detectChanges();
|
||||
flushPendingRequest();
|
||||
|
||||
component.switchTab('active');
|
||||
|
||||
const allReq = httpMock.expectOne(
|
||||
(r) => r.url.includes('/admin/memberships') && !r.url.includes('/pending'),
|
||||
);
|
||||
expect(allReq.request.method).toBe('GET');
|
||||
allReq.flush([mockPendingMembership, mockActiveMembership, mockInactiveMembership]);
|
||||
|
||||
expect(component.allMemberships().length).toBe(3);
|
||||
});
|
||||
|
||||
it('should load all memberships when switching to all tab', () => {
|
||||
fixture.detectChanges();
|
||||
flushPendingRequest();
|
||||
|
||||
component.switchTab('all');
|
||||
|
||||
const allReq = httpMock.expectOne(
|
||||
(r) => r.url.includes('/admin/memberships') && !r.url.includes('/pending'),
|
||||
);
|
||||
allReq.flush([mockPendingMembership, mockActiveMembership]);
|
||||
|
||||
expect(component.allMemberships().length).toBe(2);
|
||||
});
|
||||
|
||||
it('should not reload if allMemberships already loaded', () => {
|
||||
fixture.detectChanges();
|
||||
flushPendingRequest();
|
||||
|
||||
component.switchTab('active');
|
||||
const firstReq = httpMock.expectOne(
|
||||
(r) => r.url.includes('/admin/memberships') && !r.url.includes('/pending'),
|
||||
);
|
||||
firstReq.flush([mockActiveMembership]);
|
||||
|
||||
component.switchTab('pending');
|
||||
component.switchTab('active');
|
||||
|
||||
const additionalReqs = httpMock.match(
|
||||
(r) => r.url.includes('/admin/memberships') && !r.url.includes('/pending'),
|
||||
);
|
||||
expect(additionalReqs.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should set isLoading to false after loading completes', () => {
|
||||
fixture.detectChanges();
|
||||
flushPendingRequest();
|
||||
|
||||
component.switchTab('all');
|
||||
|
||||
const allReq = httpMock.expectOne(
|
||||
(r) => r.url.includes('/admin/memberships') && !r.url.includes('/pending'),
|
||||
);
|
||||
expect(component.isLoading()).toBe(true);
|
||||
allReq.flush([]);
|
||||
expect(component.isLoading()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should load all memberships when switching to active tab', () => {
|
||||
fixture.detectChanges();
|
||||
const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending'));
|
||||
pendingReq.flush([]);
|
||||
describe('switchTab', () => {
|
||||
it('should switch activeTab signal', () => {
|
||||
fixture.detectChanges();
|
||||
flushPendingRequest();
|
||||
|
||||
component.switchTab('active');
|
||||
component.switchTab('active');
|
||||
const activeReq = httpMock.expectOne(
|
||||
(r) => r.url.includes('/admin/memberships') && !r.url.includes('/pending'),
|
||||
);
|
||||
activeReq.flush([]);
|
||||
expect(component.activeTab()).toBe('active');
|
||||
|
||||
const allReq = httpMock.expectOne(
|
||||
(r) => r.url.includes('/admin/memberships') && !r.url.includes('/pending'),
|
||||
);
|
||||
expect(allReq.request.method).toBe('GET');
|
||||
allReq.flush([mockPendingMembership, mockActiveMembership, mockInactiveMembership]);
|
||||
component.switchTab('pending');
|
||||
expect(component.activeTab()).toBe('pending');
|
||||
|
||||
expect(component.allMemberships().length).toBe(3);
|
||||
component.switchTab('all');
|
||||
const allReq = httpMock.expectOne(
|
||||
(r) => r.url.includes('/admin/memberships') && !r.url.includes('/pending'),
|
||||
);
|
||||
allReq.flush([]);
|
||||
expect(component.activeTab()).toBe('all');
|
||||
});
|
||||
|
||||
it('should only call API for all memberships once per load', () => {
|
||||
fixture.detectChanges();
|
||||
flushPendingRequest();
|
||||
|
||||
component.switchTab('active');
|
||||
const allReq = httpMock.expectOne(
|
||||
(r) => r.url.includes('/admin/memberships') && !r.url.includes('/pending'),
|
||||
);
|
||||
allReq.flush([mockActiveMembership]);
|
||||
fixture.detectChanges();
|
||||
|
||||
component.switchTab('pending');
|
||||
expect(component.activeTab()).toBe('pending');
|
||||
|
||||
component.switchTab('all');
|
||||
expect(component.activeTab()).toBe('all');
|
||||
const allReqs = httpMock.match(
|
||||
(r) => r.url.includes('/admin/memberships') && !r.url.includes('/pending'),
|
||||
);
|
||||
expect(allReqs.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('should load all memberships when switching to all tab', () => {
|
||||
fixture.detectChanges();
|
||||
const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending'));
|
||||
pendingReq.flush([]);
|
||||
describe('approveMembership', () => {
|
||||
it('should call approve API and reload pending memberships', () => {
|
||||
fixture.detectChanges();
|
||||
flushPendingRequest();
|
||||
|
||||
component.switchTab('all');
|
||||
component.approveMembership('1');
|
||||
|
||||
const allReq = httpMock.expectOne(
|
||||
(r) => r.url.includes('/admin/memberships') && !r.url.includes('/pending'),
|
||||
);
|
||||
expect(allReq.request.method).toBe('GET');
|
||||
allReq.flush([mockPendingMembership, mockActiveMembership]);
|
||||
const approveReq = httpMock.expectOne(
|
||||
(r) => r.url.includes('/admin/memberships/1/approve'),
|
||||
);
|
||||
expect(approveReq.request.method).toBe('PUT');
|
||||
approveReq.flush({});
|
||||
|
||||
expect(component.allMemberships().length).toBe(2);
|
||||
const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending'));
|
||||
pendingReq.flush([]);
|
||||
});
|
||||
|
||||
it('should set isProcessing while approving', () => {
|
||||
fixture.detectChanges();
|
||||
flushPendingRequest();
|
||||
|
||||
expect(component.isProcessing()).toBeNull();
|
||||
component.approveMembership('1');
|
||||
expect(component.isProcessing()).toBe('1');
|
||||
|
||||
const approveReq = httpMock.expectOne(
|
||||
(r) => r.url.includes('/admin/memberships/1/approve'),
|
||||
);
|
||||
approveReq.flush({});
|
||||
|
||||
const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending'));
|
||||
pendingReq.flush([]);
|
||||
|
||||
expect(component.isProcessing()).toBeNull();
|
||||
});
|
||||
|
||||
it('should not send approve request if already processing', () => {
|
||||
fixture.detectChanges();
|
||||
flushPendingRequest();
|
||||
|
||||
component.approveMembership('1');
|
||||
component.approveMembership('1');
|
||||
|
||||
const approveReqs = httpMock.match(
|
||||
(r) => r.url.includes('/admin/memberships/1/approve'),
|
||||
);
|
||||
expect(approveReqs.length).toBe(1);
|
||||
|
||||
approveReqs[0].flush({});
|
||||
|
||||
const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending'));
|
||||
pendingReq.flush([]);
|
||||
});
|
||||
|
||||
it('should reset isProcessing on error', () => {
|
||||
fixture.detectChanges();
|
||||
flushPendingRequest();
|
||||
|
||||
component.approveMembership('1');
|
||||
|
||||
const approveReq = httpMock.expectOne(
|
||||
(r) => r.url.includes('/admin/memberships/1/approve'),
|
||||
);
|
||||
approveReq.flush({ message: 'Fehler' }, { status: 500, statusText: 'Server Error' });
|
||||
|
||||
expect(component.isProcessing()).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it('should switch activeTab signal', () => {
|
||||
fixture.detectChanges();
|
||||
const req = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending'));
|
||||
req.flush([]);
|
||||
describe('rejectMembership', () => {
|
||||
it('should call reject API and reload pending memberships', () => {
|
||||
fixture.detectChanges();
|
||||
flushPendingRequest();
|
||||
|
||||
component.switchTab('active');
|
||||
const activeReq = httpMock.expectOne(
|
||||
(r) => r.url.includes('/admin/memberships') && !r.url.includes('/pending'),
|
||||
);
|
||||
activeReq.flush([]);
|
||||
expect(component.activeTab()).toBe('active');
|
||||
component.rejectMembership('1');
|
||||
|
||||
component.switchTab('pending');
|
||||
expect(component.activeTab()).toBe('pending');
|
||||
const rejectReq = httpMock.expectOne(
|
||||
(r) => r.url.includes('/admin/memberships/1/reject'),
|
||||
);
|
||||
expect(rejectReq.request.method).toBe('PUT');
|
||||
rejectReq.flush({});
|
||||
|
||||
component.switchTab('all');
|
||||
const allReq = httpMock.expectOne(
|
||||
(r) => r.url.includes('/admin/memberships') && !r.url.includes('/pending'),
|
||||
);
|
||||
allReq.flush([]);
|
||||
expect(component.activeTab()).toBe('all');
|
||||
const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending'));
|
||||
pendingReq.flush([]);
|
||||
});
|
||||
|
||||
it('should set isProcessing while rejecting', () => {
|
||||
fixture.detectChanges();
|
||||
flushPendingRequest();
|
||||
|
||||
expect(component.isProcessing()).toBeNull();
|
||||
component.rejectMembership('1');
|
||||
expect(component.isProcessing()).toBe('1');
|
||||
|
||||
const rejectReq = httpMock.expectOne(
|
||||
(r) => r.url.includes('/admin/memberships/1/reject'),
|
||||
);
|
||||
rejectReq.flush({});
|
||||
|
||||
const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending'));
|
||||
pendingReq.flush([]);
|
||||
|
||||
expect(component.isProcessing()).toBeNull();
|
||||
});
|
||||
|
||||
it('should not send reject request if already processing', () => {
|
||||
fixture.detectChanges();
|
||||
flushPendingRequest();
|
||||
|
||||
component.rejectMembership('1');
|
||||
component.rejectMembership('1');
|
||||
|
||||
const rejectReqs = httpMock.match(
|
||||
(r) => r.url.includes('/admin/memberships/1/reject'),
|
||||
);
|
||||
expect(rejectReqs.length).toBe(1);
|
||||
|
||||
rejectReqs[0].flush({});
|
||||
|
||||
const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending'));
|
||||
pendingReq.flush([]);
|
||||
});
|
||||
|
||||
it('should reset isProcessing on error', () => {
|
||||
fixture.detectChanges();
|
||||
flushPendingRequest();
|
||||
|
||||
component.rejectMembership('1');
|
||||
|
||||
const rejectReq = httpMock.expectOne(
|
||||
(r) => r.url.includes('/admin/memberships/1/reject'),
|
||||
);
|
||||
rejectReq.flush({ message: 'Fehler' }, { status: 500, statusText: 'Server Error' });
|
||||
|
||||
expect(component.isProcessing()).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getStatusLabel', () => {
|
||||
@ -177,8 +403,7 @@ describe('AdminMembershipsComponent', () => {
|
||||
describe('template', () => {
|
||||
it('should render three tabs', () => {
|
||||
fixture.detectChanges();
|
||||
const req = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending'));
|
||||
req.flush([]);
|
||||
flushPendingRequest();
|
||||
fixture.detectChanges();
|
||||
|
||||
const tabs = fixture.nativeElement.querySelectorAll('[role="tab"]');
|
||||
@ -190,8 +415,7 @@ describe('AdminMembershipsComponent', () => {
|
||||
|
||||
it('should show pending tab as active by default', () => {
|
||||
fixture.detectChanges();
|
||||
const req = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending'));
|
||||
req.flush([]);
|
||||
flushPendingRequest();
|
||||
fixture.detectChanges();
|
||||
|
||||
const activeTab = fixture.nativeElement.querySelector('[role="tab"][aria-selected="true"]');
|
||||
@ -208,10 +432,30 @@ describe('AdminMembershipsComponent', () => {
|
||||
expect(cards.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should show approve and reject buttons for pending membership cards', () => {
|
||||
fixture.detectChanges();
|
||||
const req = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending'));
|
||||
req.flush([mockPendingMembership]);
|
||||
fixture.detectChanges();
|
||||
|
||||
const buttons: NodeListOf<HTMLButtonElement> = fixture.nativeElement.querySelectorAll('button');
|
||||
const buttonTexts = Array.from(buttons).map((b) => b.textContent?.trim());
|
||||
expect(buttonTexts).toContain('Genehmigen');
|
||||
expect(buttonTexts).toContain('Ablehnen');
|
||||
});
|
||||
|
||||
it('should show empty state for pending tab when no pending memberships', () => {
|
||||
fixture.detectChanges();
|
||||
flushPendingRequest();
|
||||
fixture.detectChanges();
|
||||
|
||||
const emptyMsg = fixture.nativeElement.querySelector('.bg-gray-50 p');
|
||||
expect(emptyMsg?.textContent).toContain('Keine wartenden Mitgliedschaftsanträge');
|
||||
});
|
||||
|
||||
it('should show table view for active tab', () => {
|
||||
fixture.detectChanges();
|
||||
const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending'));
|
||||
pendingReq.flush([]);
|
||||
flushPendingRequest();
|
||||
|
||||
component.switchTab('active');
|
||||
const allReq = httpMock.expectOne(
|
||||
@ -228,8 +472,7 @@ describe('AdminMembershipsComponent', () => {
|
||||
|
||||
it('should show table view for all tab', () => {
|
||||
fixture.detectChanges();
|
||||
const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending'));
|
||||
pendingReq.flush([]);
|
||||
flushPendingRequest();
|
||||
|
||||
component.switchTab('all');
|
||||
const allReq = httpMock.expectOne(
|
||||
@ -243,5 +486,89 @@ describe('AdminMembershipsComponent', () => {
|
||||
const rows = fixture.nativeElement.querySelectorAll('tbody tr');
|
||||
expect(rows.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should show empty state for active tab when no active memberships', () => {
|
||||
fixture.detectChanges();
|
||||
flushPendingRequest();
|
||||
|
||||
component.switchTab('active');
|
||||
const allReq = httpMock.expectOne(
|
||||
(r) => r.url.includes('/admin/memberships') && !r.url.includes('/pending'),
|
||||
);
|
||||
allReq.flush([]);
|
||||
fixture.detectChanges();
|
||||
|
||||
const emptyMsg = fixture.nativeElement.querySelector('.bg-gray-50 p');
|
||||
expect(emptyMsg?.textContent).toContain('Keine aktiven Mitgliedschaften');
|
||||
});
|
||||
|
||||
it('should show empty state for all tab when no memberships exist', () => {
|
||||
fixture.detectChanges();
|
||||
flushPendingRequest();
|
||||
|
||||
component.switchTab('all');
|
||||
const allReq = httpMock.expectOne(
|
||||
(r) => r.url.includes('/admin/memberships') && !r.url.includes('/pending'),
|
||||
);
|
||||
allReq.flush([]);
|
||||
fixture.detectChanges();
|
||||
|
||||
const emptyMsg = fixture.nativeElement.querySelector('.bg-gray-50 p');
|
||||
expect(emptyMsg?.textContent).toContain('Keine Mitgliedschaften vorhanden');
|
||||
});
|
||||
|
||||
it('should show status badge in table row', () => {
|
||||
fixture.detectChanges();
|
||||
flushPendingRequest();
|
||||
|
||||
component.switchTab('all');
|
||||
const allReq = httpMock.expectOne(
|
||||
(r) => r.url.includes('/admin/memberships') && !r.url.includes('/pending'),
|
||||
);
|
||||
allReq.flush([mockActiveMembership]);
|
||||
fixture.detectChanges();
|
||||
|
||||
const badge = fixture.nativeElement.querySelector('span.rounded-full');
|
||||
expect(badge).toBeTruthy();
|
||||
expect(badge?.textContent?.trim()).toBe('Aktiv');
|
||||
});
|
||||
|
||||
it('should show status badge in all tab table', () => {
|
||||
fixture.detectChanges();
|
||||
flushPendingRequest();
|
||||
|
||||
component.switchTab('all');
|
||||
const allReq = httpMock.expectOne(
|
||||
(r) => r.url.includes('/admin/memberships') && !r.url.includes('/pending'),
|
||||
);
|
||||
allReq.flush([mockActiveMembership]);
|
||||
fixture.detectChanges();
|
||||
|
||||
const badge = fixture.nativeElement.querySelector('span.rounded-full');
|
||||
expect(badge?.textContent?.trim()).toBe('Aktiv');
|
||||
});
|
||||
|
||||
it('should display user name and email in pending card', () => {
|
||||
fixture.detectChanges();
|
||||
const req = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending'));
|
||||
req.flush([mockPendingMembership]);
|
||||
fixture.detectChanges();
|
||||
|
||||
const cardText = fixture.nativeElement.querySelector('.bg-white.border')?.textContent;
|
||||
expect(cardText).toContain('Max Mustermann');
|
||||
expect(cardText).toContain('max@test.at');
|
||||
});
|
||||
|
||||
it('should display community, atNumber and priority in pending card', () => {
|
||||
fixture.detectChanges();
|
||||
const req = httpMock.expectOne((r) => r.url.includes('/admin/memberships/pending'));
|
||||
req.flush([mockPendingMembership]);
|
||||
fixture.detectChanges();
|
||||
|
||||
const cardText = fixture.nativeElement.querySelector('.bg-white.border')?.textContent;
|
||||
expect(cardText).toContain('Test Community');
|
||||
expect(cardText).toContain('AT0000000000000000001');
|
||||
expect(cardText).toContain('1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -189,7 +189,9 @@ export class UserTariffComponent implements OnInit, OnDestroy {
|
||||
onDelete(tariff: UserTariffResponse) {
|
||||
if (!confirm('Möchtest du diesen Tarif wirklich löschen?')) return;
|
||||
|
||||
this.userTariffService.deleteUserTariff(tariff.id!).subscribe({
|
||||
const userId = this.authService.getCurrentUserId();
|
||||
if (!userId) return;
|
||||
this.userTariffService.deleteUserTariff(tariff.id!, userId).subscribe({
|
||||
next: () => {
|
||||
this.toastService.success('Tarif erfolgreich gelöscht.');
|
||||
this.loadUserTariffs(this.selectedCommunityId());
|
||||
|
||||
Loading…
Reference in New Issue
Block a user