From 6f730e42dd49751388734b71d3786e548ef5475b Mon Sep 17 00:00:00 2001
From: sebo <sebastian.bergandy@external.mgm-cp.com>
Date: Tue, 11 Mar 2025 15:05:34 +0100
Subject: [PATCH] OZG-7473 add list component

Sub task: OZG-7883
---
 ...gregation-mapping-list-item.component.html |  10 ++
 ...gation-mapping-list-item.component.spec.ts |  21 +++
 ...aggregation-mapping-list-item.component.ts |  14 ++
 .../aggregation-mapping-list.component.html   |   7 +
 ...aggregation-mapping-list.component.spec.ts | 120 ++++++++++++++++++
 .../aggregation-mapping-list.component.ts     |  31 +++++
 .../statistik-container.component.html        |   2 +-
 .../statistik-container.component.spec.ts     |  32 ++++-
 .../statistik-container.component.ts          |   3 +-
 alfa-client/libs/test-utils/src/lib/helper.ts |   4 +-
 .../libs/test-utils/src/lib/jest.helper.ts    |  11 +-
 11 files changed, 247 insertions(+), 8 deletions(-)
 create mode 100644 alfa-client/libs/admin/statistik/src/lib/aggregation-mapping-list/aggregation-mapping-list-item/aggregation-mapping-list-item.component.html
 create mode 100644 alfa-client/libs/admin/statistik/src/lib/aggregation-mapping-list/aggregation-mapping-list-item/aggregation-mapping-list-item.component.spec.ts
 create mode 100644 alfa-client/libs/admin/statistik/src/lib/aggregation-mapping-list/aggregation-mapping-list-item/aggregation-mapping-list-item.component.ts
 create mode 100644 alfa-client/libs/admin/statistik/src/lib/aggregation-mapping-list/aggregation-mapping-list.component.html
 create mode 100644 alfa-client/libs/admin/statistik/src/lib/aggregation-mapping-list/aggregation-mapping-list.component.spec.ts
 create mode 100644 alfa-client/libs/admin/statistik/src/lib/aggregation-mapping-list/aggregation-mapping-list.component.ts

diff --git a/alfa-client/libs/admin/statistik/src/lib/aggregation-mapping-list/aggregation-mapping-list-item/aggregation-mapping-list-item.component.html b/alfa-client/libs/admin/statistik/src/lib/aggregation-mapping-list/aggregation-mapping-list-item/aggregation-mapping-list-item.component.html
new file mode 100644
index 0000000000..8bf3593a46
--- /dev/null
+++ b/alfa-client/libs/admin/statistik/src/lib/aggregation-mapping-list/aggregation-mapping-list-item/aggregation-mapping-list-item.component.html
@@ -0,0 +1,10 @@
+<ods-list-item [path]="" [attr.data-test-id]="(aggregationMapping.name | convertForDataTest)">
+  <div class="flex-1 basis-1/2">
+    <div class="mb-2 flex flex-wrap items-center gap-3">
+      <h3 class="text-md font-semibold" data-test-class="fullname">{{ aggregationMapping.name }}</h3>
+    </div>
+    <!-- Remove null safe check operator after backend provides correct data. -->
+    <div>{{aggregationMapping.formIdentifier?.formEngineName}}</div>
+    <div>{{aggregationMapping.formIdentifier?.formId}}</div>
+  </div>
+</ods-list-item>
diff --git a/alfa-client/libs/admin/statistik/src/lib/aggregation-mapping-list/aggregation-mapping-list-item/aggregation-mapping-list-item.component.spec.ts b/alfa-client/libs/admin/statistik/src/lib/aggregation-mapping-list/aggregation-mapping-list-item/aggregation-mapping-list-item.component.spec.ts
new file mode 100644
index 0000000000..7e48ac908d
--- /dev/null
+++ b/alfa-client/libs/admin/statistik/src/lib/aggregation-mapping-list/aggregation-mapping-list-item/aggregation-mapping-list-item.component.spec.ts
@@ -0,0 +1,21 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { AggregationMappingListItemComponent } from './aggregation-mapping-list-item.component';
+
+describe('AggregationMappingListItemComponent', () => {
+  let component: AggregationMappingListItemComponent;
+  let fixture: ComponentFixture<AggregationMappingListItemComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [AggregationMappingListItemComponent],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(AggregationMappingListItemComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/alfa-client/libs/admin/statistik/src/lib/aggregation-mapping-list/aggregation-mapping-list-item/aggregation-mapping-list-item.component.ts b/alfa-client/libs/admin/statistik/src/lib/aggregation-mapping-list/aggregation-mapping-list-item/aggregation-mapping-list-item.component.ts
new file mode 100644
index 0000000000..ca31dbc05b
--- /dev/null
+++ b/alfa-client/libs/admin/statistik/src/lib/aggregation-mapping-list/aggregation-mapping-list-item/aggregation-mapping-list-item.component.ts
@@ -0,0 +1,14 @@
+import { AggregationMappingResource } from '@admin-client/reporting-shared';
+import { ConvertForDataTestPipe } from '@alfa-client/tech-shared';
+import { Component, Input } from '@angular/core';
+import { ListItemComponent } from '@ods/system';
+
+@Component({
+  selector: 'admin-aggregation-mapping-list-item',
+  standalone: true,
+  templateUrl: './aggregation-mapping-list-item.component.html',
+  imports: [ConvertForDataTestPipe, ListItemComponent],
+})
+export class AggregationMappingListItemComponent {
+  @Input({ required: true }) aggregationMapping: AggregationMappingResource;
+}
diff --git a/alfa-client/libs/admin/statistik/src/lib/aggregation-mapping-list/aggregation-mapping-list.component.html b/alfa-client/libs/admin/statistik/src/lib/aggregation-mapping-list/aggregation-mapping-list.component.html
new file mode 100644
index 0000000000..2746dbebda
--- /dev/null
+++ b/alfa-client/libs/admin/statistik/src/lib/aggregation-mapping-list/aggregation-mapping-list.component.html
@@ -0,0 +1,7 @@
+<ods-spinner [stateResource]="listStateResource">
+  <ods-list data-test-id="aggregation-mapping-list">
+    @for (aggregationMapping of aggregationMappings; track $index) {
+      <admin-aggregation-mapping-list-item [aggregationMapping]="aggregationMapping" />
+    }
+  </ods-list>
+</ods-spinner>
\ No newline at end of file
diff --git a/alfa-client/libs/admin/statistik/src/lib/aggregation-mapping-list/aggregation-mapping-list.component.spec.ts b/alfa-client/libs/admin/statistik/src/lib/aggregation-mapping-list/aggregation-mapping-list.component.spec.ts
new file mode 100644
index 0000000000..dd1647f73a
--- /dev/null
+++ b/alfa-client/libs/admin/statistik/src/lib/aggregation-mapping-list/aggregation-mapping-list.component.spec.ts
@@ -0,0 +1,120 @@
+import { AggregationMappingListResource } from '@admin-client/reporting-shared';
+import {
+  createEmptyStateResource,
+  createLoadingStateResource,
+  createStateResource,
+  getEmbeddedResources,
+  StateResource,
+} from '@alfa-client/tech-shared';
+import { expectComponentExistsInTemplate, getElementFromFixtureByType } from '@alfa-client/test-utils';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { expect } from '@jest/globals';
+import { SpinnerComponent } from '@ods/component';
+import { ListComponent } from '@ods/system';
+import { MockComponent } from 'ng-mocks';
+import { AggregationMappingListLinkRel } from '../../../../reporting-shared/src/lib/aggregation-mapping.linkrel';
+import { createAggregationMappingListResource } from '../../../../reporting-shared/test/aggregation-mapping';
+import { AggregationMappingListItemComponent } from './aggregation-mapping-list-item/aggregation-mapping-list-item.component';
+import { AggregationMappingListComponent } from './aggregation-mapping-list.component';
+
+describe('AggregationMappingListComponent', () => {
+  let component: AggregationMappingListComponent;
+  let fixture: ComponentFixture<AggregationMappingListComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [
+        AggregationMappingListComponent,
+        MockComponent(SpinnerComponent),
+        MockComponent(ListComponent),
+        MockComponent(AggregationMappingListItemComponent),
+      ],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(AggregationMappingListComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+
+  describe('component', () => {
+    const aggregationMappingListStateResource: StateResource<AggregationMappingListResource> = createStateResource(
+      createAggregationMappingListResource(),
+    );
+
+    it('should have initial state', () => {
+      expect(component.listStateResource).toEqual(createEmptyStateResource());
+      expect(component.aggregationMappings).toEqual([]);
+    });
+
+    describe('_update', () => {
+      it('should set list state resource', () => {
+        component.aggregationMappingListStateResource = aggregationMappingListStateResource;
+
+        expect(component.listStateResource).toEqual(aggregationMappingListStateResource);
+      });
+
+      it('should set aggregation mappings if loaded and not nil', () => {
+        component.aggregationMappingListStateResource = aggregationMappingListStateResource;
+
+        expect(component.aggregationMappings).toEqual(
+          getEmbeddedResources(aggregationMappingListStateResource, AggregationMappingListLinkRel.LIST),
+        );
+      });
+
+      it('should NOT set aggregation mappings if nil', () => {
+        component.aggregationMappingListStateResource = null;
+
+        expect(component.aggregationMappings).toEqual([]);
+      });
+
+      it('should NOT set aggregation mappings if loading', () => {
+        component.aggregationMappingListStateResource = createLoadingStateResource();
+
+        expect(component.aggregationMappings).toEqual([]);
+      });
+    });
+
+    describe('set aggregation mapping list state resource', () => {
+      it('should update component state', () => {
+        component._update = jest.fn();
+
+        component.aggregationMappingListStateResource = aggregationMappingListStateResource;
+
+        expect(component._update).toHaveBeenCalledWith(aggregationMappingListStateResource);
+      });
+    });
+  });
+
+  describe('template', () => {
+    const aggregationMappingListStateResource: StateResource<AggregationMappingListResource> = createStateResource(
+      createAggregationMappingListResource(),
+    );
+
+    beforeEach(() => {
+      component.aggregationMappingListStateResource = aggregationMappingListStateResource;
+      fixture.detectChanges();
+    });
+
+    it('should have spinner with state resource', () => {
+      expectComponentExistsInTemplate(fixture, SpinnerComponent);
+      const comp = getElementFromFixtureByType(fixture, SpinnerComponent);
+      expect(comp.stateResource).toEqual(aggregationMappingListStateResource);
+    });
+
+    it('should have list', () => {
+      expectComponentExistsInTemplate(fixture, ListComponent);
+    });
+
+    it('should have list item with mapping', () => {
+      expectComponentExistsInTemplate(fixture, AggregationMappingListItemComponent);
+      const comp = getElementFromFixtureByType(fixture, AggregationMappingListItemComponent);
+      expect(comp.aggregationMapping).toEqual(
+        getEmbeddedResources(aggregationMappingListStateResource, AggregationMappingListLinkRel.LIST)[0],
+      );
+    });
+  });
+});
diff --git a/alfa-client/libs/admin/statistik/src/lib/aggregation-mapping-list/aggregation-mapping-list.component.ts b/alfa-client/libs/admin/statistik/src/lib/aggregation-mapping-list/aggregation-mapping-list.component.ts
new file mode 100644
index 0000000000..c427bc7903
--- /dev/null
+++ b/alfa-client/libs/admin/statistik/src/lib/aggregation-mapping-list/aggregation-mapping-list.component.ts
@@ -0,0 +1,31 @@
+import { AggregationMappingListResource, AggregationMappingResource } from '@admin-client/reporting-shared';
+import { createEmptyStateResource, getEmbeddedResources, isLoaded, isNotNil, StateResource } from '@alfa-client/tech-shared';
+import { Component, Input } from '@angular/core';
+import { SpinnerComponent } from '@ods/component';
+import { ListComponent } from '@ods/system';
+import { AggregationMappingListLinkRel } from '../../../../reporting-shared/src/lib/aggregation-mapping.linkrel';
+import { AggregationMappingListItemComponent } from './aggregation-mapping-list-item/aggregation-mapping-list-item.component';
+
+@Component({
+  selector: 'admin-aggregation-mapping-list',
+  standalone: true,
+  templateUrl: './aggregation-mapping-list.component.html',
+  imports: [SpinnerComponent, ListComponent, AggregationMappingListItemComponent],
+})
+export class AggregationMappingListComponent {
+  @Input({ required: true }) set aggregationMappingListStateResource(
+    stateResource: StateResource<AggregationMappingListResource>,
+  ) {
+    this._update(stateResource);
+  }
+
+  public listStateResource: StateResource<AggregationMappingListResource> = createEmptyStateResource();
+  public aggregationMappings: AggregationMappingResource[] = [];
+
+  _update(stateResource: StateResource<AggregationMappingListResource>): void {
+    this.listStateResource = stateResource;
+    if (isNotNil(stateResource) && isLoaded(stateResource)) {
+      this.aggregationMappings = getEmbeddedResources(stateResource, AggregationMappingListLinkRel.LIST);
+    }
+  }
+}
diff --git a/alfa-client/libs/admin/statistik/src/lib/statistik-container/statistik-container.component.html b/alfa-client/libs/admin/statistik/src/lib/statistik-container/statistik-container.component.html
index 1fd0441a39..f7cb1c606f 100644
--- a/alfa-client/libs/admin/statistik/src/lib/statistik-container/statistik-container.component.html
+++ b/alfa-client/libs/admin/statistik/src/lib/statistik-container/statistik-container.component.html
@@ -32,4 +32,4 @@
   ></ods-routing-button>
 </div>
 
-<ng-container *ngIf="listStateResource$ | async"></ng-container>
+<admin-aggregation-mapping-list [aggregationMappingListStateResource]="listStateResource$ | async"></admin-aggregation-mapping-list>
diff --git a/alfa-client/libs/admin/statistik/src/lib/statistik-container/statistik-container.component.spec.ts b/alfa-client/libs/admin/statistik/src/lib/statistik-container/statistik-container.component.spec.ts
index 3abca44e4a..014109b6a8 100644
--- a/alfa-client/libs/admin/statistik/src/lib/statistik-container/statistik-container.component.spec.ts
+++ b/alfa-client/libs/admin/statistik/src/lib/statistik-container/statistik-container.component.spec.ts
@@ -23,12 +23,15 @@
  */
 import { AggregationMappingListResource, AggregationMappingService } from '@admin-client/reporting-shared';
 import { createStateResource, StateResource } from '@alfa-client/tech-shared';
-import { mock, Mock } from '@alfa-client/test-utils';
+import { expectComponentExistsInTemplate, getElementFromFixtureByType, mock, Mock } from '@alfa-client/test-utils';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { expect } from '@jest/globals';
 import { RoutingButtonComponent } from '@ods/component';
 import { singleCold } from 'libs/tech-shared/test/marbles';
 import { MockComponent } from 'ng-mocks';
+import { of } from 'rxjs';
 import { createAggregationMappingListResource } from '../../../../reporting-shared/test/aggregation-mapping';
+import { AggregationMappingListComponent } from '../aggregation-mapping-list/aggregation-mapping-list.component';
 import { StatistikContainerComponent } from './statistik-container.component';
 
 describe('StatistikContainerComponent', () => {
@@ -41,8 +44,11 @@ describe('StatistikContainerComponent', () => {
     aggregationMappingService = mock(AggregationMappingService);
 
     await TestBed.configureTestingModule({
-      imports: [StatistikContainerComponent],
-      declarations: [MockComponent(RoutingButtonComponent)],
+      imports: [
+        StatistikContainerComponent,
+        MockComponent(RoutingButtonComponent),
+        MockComponent(AggregationMappingListComponent),
+      ],
     })
       .overrideComponent(StatistikContainerComponent, {
         set: {
@@ -86,4 +92,24 @@ describe('StatistikContainerComponent', () => {
       expect(component.listStateResource$).toBeObservable(singleCold(stateResource));
     });
   });
+
+  describe('template', () => {
+    describe('aggregation mapping list', () => {
+      it('should exists', () => {
+        expectComponentExistsInTemplate(fixture, AggregationMappingListComponent);
+      });
+
+      it('should have inputs', () => {
+        const stateResource: StateResource<AggregationMappingListResource> = createStateResource(
+          createAggregationMappingListResource(),
+        );
+        component.listStateResource$ = of(stateResource);
+        fixture.detectChanges();
+
+        const comp = getElementFromFixtureByType(fixture, AggregationMappingListComponent);
+
+        expect(comp.aggregationMappingListStateResource).toEqual(stateResource);
+      });
+    });
+  });
 });
diff --git a/alfa-client/libs/admin/statistik/src/lib/statistik-container/statistik-container.component.ts b/alfa-client/libs/admin/statistik/src/lib/statistik-container/statistik-container.component.ts
index 90c3515896..70db01b592 100644
--- a/alfa-client/libs/admin/statistik/src/lib/statistik-container/statistik-container.component.ts
+++ b/alfa-client/libs/admin/statistik/src/lib/statistik-container/statistik-container.component.ts
@@ -28,12 +28,13 @@ import { CommonModule } from '@angular/common';
 import { Component, inject, OnInit } from '@angular/core';
 import { RoutingButtonComponent } from '@ods/component';
 import { Observable } from 'rxjs';
+import { AggregationMappingListComponent } from '../aggregation-mapping-list/aggregation-mapping-list.component';
 
 @Component({
   selector: 'admin-statistik-container',
   templateUrl: './statistik-container.component.html',
   standalone: true,
-  imports: [CommonModule, RoutingButtonComponent],
+  imports: [CommonModule, RoutingButtonComponent, AggregationMappingListComponent],
   providers: [AggregationMappingService],
 })
 export class StatistikContainerComponent implements OnInit {
diff --git a/alfa-client/libs/test-utils/src/lib/helper.ts b/alfa-client/libs/test-utils/src/lib/helper.ts
index 66f33c3579..df9518764f 100644
--- a/alfa-client/libs/test-utils/src/lib/helper.ts
+++ b/alfa-client/libs/test-utils/src/lib/helper.ts
@@ -26,8 +26,8 @@ import { ComponentFixture } from '@angular/core/testing';
 import { By } from '@angular/platform-browser';
 import { EventData } from './model';
 
-export function getElementFromFixtureByType<T>(fixture: ComponentFixture<any>, component: Type<T>): T {
-  return getDebugElementFromFixtureByType(fixture, component).componentInstance as T;
+export function getElementFromFixtureByType<T>(fixture: ComponentFixture<any>, component: Type<T>): T | null {
+  return getDebugElementFromFixtureByType(fixture, component)?.componentInstance as T;
 }
 
 function getDebugElementFromFixtureByType<T>(fixture: ComponentFixture<any>, component: Type<T>): DebugElement {
diff --git a/alfa-client/libs/test-utils/src/lib/jest.helper.ts b/alfa-client/libs/test-utils/src/lib/jest.helper.ts
index 60df8592af..1b0f86ec5b 100644
--- a/alfa-client/libs/test-utils/src/lib/jest.helper.ts
+++ b/alfa-client/libs/test-utils/src/lib/jest.helper.ts
@@ -21,10 +21,11 @@
  * Die sprachspezifischen Genehmigungen und Beschränkungen
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
+import { Type } from '@angular/core';
 import { ComponentFixture } from '@angular/core/testing';
 import { expect } from '@jest/globals';
 import { TooltipDirective } from '@ods/system';
-import { getDebugElementFromFixtureByCss, getElementFromFixture } from './helper';
+import { getDebugElementFromFixtureByCss, getElementFromFixture, getElementFromFixtureByType } from './helper';
 
 export function notExistsAsHtmlElement(fixture: ComponentFixture<any>, domElement: string): void {
   expect(getElementFromFixture(fixture, domElement)).not.toBeInstanceOf(HTMLElement);
@@ -34,6 +35,14 @@ export function existsAsHtmlElement(fixture: ComponentFixture<any>, domElement:
   expect(getElementFromFixture(fixture, domElement)).toBeInstanceOf(HTMLElement);
 }
 
+export function expectComponentExistsInTemplate<T>(fixture: ComponentFixture<any>, component: Type<T>): void {
+  expect(getElementFromFixtureByType(fixture, component)).toBeInstanceOf(component);
+}
+
+export function expectComponentMissingInTemplate<T>(fixture: ComponentFixture<any>, component: Type<T>): void {
+  expect(getElementFromFixtureByType(fixture, component)).toBeUndefined();
+}
+
 export function tooltipExistsWithText(fixture: ComponentFixture<any>, domElement: string, tooltipText: string) {
   const tooltipInstance = getDebugElementFromFixtureByCss(fixture, domElement).injector.get(TooltipDirective);
   expect(tooltipInstance.componentRef.instance.text).toBe(tooltipText);
-- 
GitLab