mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Merged PR 1931: fix(remission-quantity-and-reason-item)
fix(remission-quantity-and-reason-item) Ref: #5292
This commit is contained in:
committed by
Andreas Schickinger
parent
332699ca74
commit
708ec01704
@@ -25,11 +25,11 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="grid"
|
||||
[class.grid-cols-[minmax(20rem,1fr),auto]]="!horizontal"
|
||||
[ngClass]="!horizontal ? innerGridClass() : ''"
|
||||
[class.gap-6]="!horizontal"
|
||||
[class.grid-flow-row]="horizontal"
|
||||
[class.gap-2]="horizontal"
|
||||
class="grid"
|
||||
>
|
||||
<div class="grid grid-flow-row gap-2">
|
||||
<div class="isa-text-body-2-bold" data-what="product-contributors">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CurrencyPipe } from '@angular/common';
|
||||
import { CurrencyPipe, NgClass } from '@angular/common';
|
||||
import { Component, input } from '@angular/core';
|
||||
import { RemissionItem, ReturnItem } from '@isa/remission/data-access';
|
||||
import { RemissionItem } from '@isa/remission/data-access';
|
||||
import { ProductImageDirective } from '@isa/shared/product-image';
|
||||
import { ProductRouterLinkDirective } from '@isa/shared/product-router-link';
|
||||
import { ProductFormatComponent } from '@isa/shared/product-foramt';
|
||||
@@ -23,6 +23,7 @@ export const RemissionItemTags = {
|
||||
selector: 'remi-product-info',
|
||||
templateUrl: 'product-info.component.html',
|
||||
imports: [
|
||||
NgClass,
|
||||
ProductImageDirective,
|
||||
ProductRouterLinkDirective,
|
||||
CurrencyPipe,
|
||||
@@ -50,4 +51,6 @@ export class ProductInfoComponent {
|
||||
item = input.required<ProductInfoItem>();
|
||||
|
||||
orientation = input<ProductInfoOrientation>('horizontal');
|
||||
|
||||
innerGridClass = input<string>('grid-cols-[minmax(20rem,1fr),auto]');
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<remi-select-remi-quantity-and-reason></remi-select-remi-quantity-and-reason>
|
||||
} @else {
|
||||
<button
|
||||
class="absolute top-1 right-[1.33rem]"
|
||||
class="absolute top-4 right-[1.33rem]"
|
||||
type="button"
|
||||
uiTextButton
|
||||
size="small"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
:host {
|
||||
@apply block h-full;
|
||||
@apply block h-full mt-6;
|
||||
}
|
||||
|
||||
@@ -1,377 +0,0 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { SearchItemToRemitDialogComponent } from './search-item-to-remit-dialog.component';
|
||||
import { DialogRef, DIALOG_DATA } from '@angular/cdk/dialog';
|
||||
import { DialogComponent } from '@isa/ui/dialog';
|
||||
import { MockComponents } from 'ng-mocks';
|
||||
import { TextButtonComponent } from '@isa/ui/buttons';
|
||||
import { SearchItemToRemitListComponent } from './search-item-to-remit-list.component';
|
||||
import { SelectRemiQuantityAndReasonComponent } from './select-remi-quantity-and-reason.component';
|
||||
import { signal } from '@angular/core';
|
||||
import { Item } from '@isa/catalogue/data-access';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
describe('SearchItemToRemitDialogComponent', () => {
|
||||
let component: SearchItemToRemitDialogComponent;
|
||||
let fixture: ComponentFixture<SearchItemToRemitDialogComponent>;
|
||||
let mockDialogRef: {
|
||||
updateSize: ReturnType<typeof vi.fn>;
|
||||
close: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
let mockDialogComponent: {
|
||||
title: ReturnType<typeof signal>;
|
||||
};
|
||||
|
||||
const mockItem = {
|
||||
id: 1,
|
||||
product: {
|
||||
id: 1,
|
||||
name: 'Test Product',
|
||||
},
|
||||
catalogAvailability: {},
|
||||
} as unknown as Item;
|
||||
|
||||
beforeEach(async () => {
|
||||
mockDialogRef = {
|
||||
updateSize: vi.fn(),
|
||||
close: vi.fn(),
|
||||
};
|
||||
|
||||
mockDialogComponent = {
|
||||
title: signal(''),
|
||||
};
|
||||
|
||||
const mockData = { searchTerm: 'test' };
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [SearchItemToRemitDialogComponent],
|
||||
providers: [
|
||||
{ provide: DialogRef, useValue: mockDialogRef },
|
||||
{ provide: DIALOG_DATA, useValue: mockData },
|
||||
{ provide: DialogComponent, useValue: mockDialogComponent },
|
||||
],
|
||||
})
|
||||
.overrideComponent(SearchItemToRemitDialogComponent, {
|
||||
remove: {
|
||||
imports: [
|
||||
TextButtonComponent,
|
||||
SearchItemToRemitListComponent,
|
||||
SelectRemiQuantityAndReasonComponent,
|
||||
],
|
||||
},
|
||||
add: {
|
||||
imports: MockComponents(
|
||||
TextButtonComponent,
|
||||
SearchItemToRemitListComponent,
|
||||
SelectRemiQuantityAndReasonComponent,
|
||||
),
|
||||
},
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(SearchItemToRemitDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
|
||||
describe('Component Setup and Initialization', () => {
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should initialize searchTerm from string data', () => {
|
||||
fixture.detectChanges();
|
||||
expect(component.searchTerm()).toBe('test');
|
||||
});
|
||||
|
||||
it('should initialize searchTerm from Signal data', async () => {
|
||||
const searchTermSignal = signal('signal test');
|
||||
const mockSignalData = { searchTerm: searchTermSignal };
|
||||
|
||||
TestBed.resetTestingModule();
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [SearchItemToRemitDialogComponent],
|
||||
providers: [
|
||||
{ provide: DialogRef, useValue: mockDialogRef },
|
||||
{ provide: DIALOG_DATA, useValue: mockSignalData },
|
||||
{ provide: DialogComponent, useValue: mockDialogComponent },
|
||||
],
|
||||
})
|
||||
.overrideComponent(SearchItemToRemitDialogComponent, {
|
||||
remove: {
|
||||
imports: [
|
||||
TextButtonComponent,
|
||||
SearchItemToRemitListComponent,
|
||||
SelectRemiQuantityAndReasonComponent,
|
||||
],
|
||||
},
|
||||
add: {
|
||||
imports: MockComponents(
|
||||
TextButtonComponent,
|
||||
SearchItemToRemitListComponent,
|
||||
SelectRemiQuantityAndReasonComponent,
|
||||
),
|
||||
},
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(SearchItemToRemitDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.searchTerm()).toBe('signal test');
|
||||
|
||||
// Test that it reacts to signal changes
|
||||
searchTermSignal.set('updated signal test');
|
||||
fixture.detectChanges();
|
||||
expect(component.searchTerm()).toBe('updated signal test');
|
||||
});
|
||||
|
||||
it('should initialize item signal as undefined', () => {
|
||||
fixture.detectChanges();
|
||||
expect(component.item()).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should extend DialogContentDirective', () => {
|
||||
expect(component.dialogRef).toBeDefined();
|
||||
expect(component.data).toBeDefined();
|
||||
expect(component.close).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Signal and Effect Behavior', () => {
|
||||
it('should update dialog size to auto when item is undefined', () => {
|
||||
fixture.detectChanges();
|
||||
expect(mockDialogRef.updateSize).toHaveBeenCalledWith('auto');
|
||||
});
|
||||
|
||||
it('should update dialog size to 36rem when item is set', () => {
|
||||
fixture.detectChanges();
|
||||
mockDialogRef.updateSize.mockClear();
|
||||
|
||||
component.item.set(mockItem);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(mockDialogRef.updateSize).toHaveBeenCalledWith('36rem');
|
||||
});
|
||||
|
||||
it('should update searchTerm when linkedSignal source changes', () => {
|
||||
const searchTermSignal = signal('initial');
|
||||
const mockSignalData = { searchTerm: searchTermSignal };
|
||||
|
||||
TestBed.resetTestingModule();
|
||||
TestBed.configureTestingModule({
|
||||
imports: [SearchItemToRemitDialogComponent],
|
||||
providers: [
|
||||
{ provide: DialogRef, useValue: mockDialogRef },
|
||||
{ provide: DIALOG_DATA, useValue: mockSignalData },
|
||||
{ provide: DialogComponent, useValue: mockDialogComponent },
|
||||
],
|
||||
})
|
||||
.overrideComponent(SearchItemToRemitDialogComponent, {
|
||||
remove: {
|
||||
imports: [
|
||||
TextButtonComponent,
|
||||
SearchItemToRemitListComponent,
|
||||
SelectRemiQuantityAndReasonComponent,
|
||||
],
|
||||
},
|
||||
add: {
|
||||
imports: MockComponents(
|
||||
TextButtonComponent,
|
||||
SearchItemToRemitListComponent,
|
||||
SelectRemiQuantityAndReasonComponent,
|
||||
),
|
||||
},
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(SearchItemToRemitDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.searchTerm()).toBe('initial');
|
||||
|
||||
searchTermSignal.set('updated');
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.searchTerm()).toBe('updated');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Template Behavior', () => {
|
||||
it('should show search list component when item is undefined', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const searchList = fixture.debugElement.query(
|
||||
By.css('remi-search-item-to-remit-list'),
|
||||
);
|
||||
const selectQuantity = fixture.debugElement.query(
|
||||
By.css('remi-select-remi-quantity-and-reason'),
|
||||
);
|
||||
|
||||
expect(searchList).toBeTruthy();
|
||||
expect(selectQuantity).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should show select quantity component when item is set', () => {
|
||||
component.item.set(mockItem);
|
||||
fixture.detectChanges();
|
||||
|
||||
const searchList = fixture.debugElement.query(
|
||||
By.css('remi-search-item-to-remit-list'),
|
||||
);
|
||||
const selectQuantity = fixture.debugElement.query(
|
||||
By.css('remi-select-remi-quantity-and-reason'),
|
||||
);
|
||||
|
||||
expect(searchList).toBeFalsy();
|
||||
expect(selectQuantity).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should show close button only when item is undefined', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let closeButton = fixture.debugElement.query(
|
||||
By.css('button[uiTextButton]'),
|
||||
);
|
||||
expect(closeButton).toBeTruthy();
|
||||
expect(closeButton.nativeElement.textContent.trim()).toBe('Schließen');
|
||||
|
||||
component.item.set(mockItem);
|
||||
fixture.detectChanges();
|
||||
|
||||
closeButton = fixture.debugElement.query(By.css('button[uiTextButton]'));
|
||||
expect(closeButton).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should call close with undefined when close button is clicked', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const closeButton = fixture.debugElement.query(
|
||||
By.css('button[uiTextButton]'),
|
||||
);
|
||||
closeButton.nativeElement.click();
|
||||
|
||||
expect(mockDialogRef.close).toHaveBeenCalledWith(undefined);
|
||||
});
|
||||
|
||||
it('should have correct button attributes', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const closeButton = fixture.debugElement.query(By.css('button'));
|
||||
const buttonEl = closeButton.nativeElement;
|
||||
|
||||
expect(buttonEl.type).toBe('button');
|
||||
expect(buttonEl.classList.contains('absolute')).toBe(true);
|
||||
expect(buttonEl.classList.contains('top-1')).toBe(true);
|
||||
expect(buttonEl.classList.contains('right-[1.33rem]')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('DialogRef Integration', () => {
|
||||
it('should call dialogRef.updateSize on initialization', () => {
|
||||
fixture.detectChanges();
|
||||
expect(mockDialogRef.updateSize).toHaveBeenCalledWith('auto');
|
||||
});
|
||||
|
||||
it('should call dialogRef.updateSize when item changes', () => {
|
||||
fixture.detectChanges();
|
||||
mockDialogRef.updateSize.mockClear();
|
||||
|
||||
component.item.set(mockItem);
|
||||
fixture.detectChanges();
|
||||
expect(mockDialogRef.updateSize).toHaveBeenCalledWith('36rem');
|
||||
|
||||
component.item.set(undefined);
|
||||
fixture.detectChanges();
|
||||
expect(mockDialogRef.updateSize).toHaveBeenCalledWith('auto');
|
||||
});
|
||||
|
||||
it('should inherit close method from DialogContentDirective', () => {
|
||||
const closeSpy = vi.spyOn(component, 'close');
|
||||
component.close(mockItem);
|
||||
|
||||
expect(closeSpy).toHaveBeenCalledWith(mockItem);
|
||||
expect(mockDialogRef.close).toHaveBeenCalledWith(mockItem);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge Cases and Error Handling', () => {
|
||||
it('should handle empty searchTerm', async () => {
|
||||
const emptyData = { searchTerm: '' };
|
||||
|
||||
TestBed.resetTestingModule();
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [SearchItemToRemitDialogComponent],
|
||||
providers: [
|
||||
{ provide: DialogRef, useValue: mockDialogRef },
|
||||
{ provide: DIALOG_DATA, useValue: emptyData },
|
||||
{ provide: DialogComponent, useValue: mockDialogComponent },
|
||||
],
|
||||
})
|
||||
.overrideComponent(SearchItemToRemitDialogComponent, {
|
||||
remove: {
|
||||
imports: [
|
||||
TextButtonComponent,
|
||||
SearchItemToRemitListComponent,
|
||||
SelectRemiQuantityAndReasonComponent,
|
||||
],
|
||||
},
|
||||
add: {
|
||||
imports: MockComponents(
|
||||
TextButtonComponent,
|
||||
SearchItemToRemitListComponent,
|
||||
SelectRemiQuantityAndReasonComponent,
|
||||
),
|
||||
},
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(SearchItemToRemitDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.searchTerm()).toBe('');
|
||||
});
|
||||
|
||||
it('should handle multiple rapid item changes', () => {
|
||||
fixture.detectChanges();
|
||||
mockDialogRef.updateSize.mockClear();
|
||||
|
||||
// First change
|
||||
component.item.set(mockItem);
|
||||
fixture.detectChanges();
|
||||
expect(mockDialogRef.updateSize).toHaveBeenCalledWith('36rem');
|
||||
|
||||
// Second change
|
||||
component.item.set(undefined);
|
||||
fixture.detectChanges();
|
||||
expect(mockDialogRef.updateSize).toHaveBeenCalledWith('auto');
|
||||
|
||||
// Third change
|
||||
component.item.set(mockItem);
|
||||
fixture.detectChanges();
|
||||
expect(mockDialogRef.updateSize).toHaveBeenCalledWith('36rem');
|
||||
|
||||
// Total calls
|
||||
expect(mockDialogRef.updateSize).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('should handle component destruction gracefully', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
// Component destruction should not throw errors
|
||||
expect(() => {
|
||||
fixture.destroy();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should maintain data integrity', () => {
|
||||
const originalData = { searchTerm: 'test' };
|
||||
fixture.detectChanges();
|
||||
|
||||
// Data should remain unchanged
|
||||
expect(component.data).toEqual(originalData);
|
||||
expect(component.data.searchTerm).toBe('test');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -20,16 +20,25 @@
|
||||
></ui-icon-button>
|
||||
</ui-search-bar>
|
||||
<p
|
||||
class="text-isa-neutral-600 isa-text-body-1-regular pb-4 border-b border-b-isa-neutral-300"
|
||||
class="relative text-isa-neutral-600 isa-text-body-1-regular pb-4 border-b border-b-isa-neutral-300"
|
||||
>
|
||||
Sie können Artikel die nicht auf der Remi Liste stehen direkt zum
|
||||
Warenbegleitschein hinzufügen.
|
||||
|
||||
<button
|
||||
class="absolute ml-1 w-6 h-6 inline-flex items-center justify-center text-isa-accent-blue"
|
||||
uiTooltip
|
||||
[content]="'Es werden nur Artikel mit Bestand angezeigt'"
|
||||
[triggerOn]="['click', 'hover']"
|
||||
>
|
||||
<ng-icon size="1.5rem" name="isaOtherInfo"></ng-icon>
|
||||
</button>
|
||||
</p>
|
||||
<div class="overflow-y-auto">
|
||||
@if (searchResource.value()?.result; as items) {
|
||||
@for (item of items; track item.id) {
|
||||
@defer {
|
||||
<remi-search-item-to-remit
|
||||
<remi-search-item-to-remit
|
||||
[item]="item"
|
||||
data-what="list-item"
|
||||
data-which="search-result"
|
||||
|
||||
@@ -6,13 +6,13 @@ import {
|
||||
resource,
|
||||
signal,
|
||||
} from '@angular/core';
|
||||
import { isaActionSearch } from '@isa/icons';
|
||||
import { isaActionSearch, isaOtherInfo } from '@isa/icons';
|
||||
import { IconButtonComponent } from '@isa/ui/buttons';
|
||||
import {
|
||||
UiSearchBarClearComponent,
|
||||
UiSearchBarComponent,
|
||||
} from '@isa/ui/search-bar';
|
||||
import { provideIcons } from '@ng-icons/core';
|
||||
import { NgIcon, provideIcons } from '@ng-icons/core';
|
||||
import { SearchItemToRemitComponent } from './search-item-to-remit.component';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { DEFAULT_LIST_RESPONSE_ARGS_OF_ITEM } from './constants';
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
} from '@isa/common/data-access';
|
||||
import { SearchItemToRemitDialogComponent } from './search-item-to-remit-dialog.component';
|
||||
import { CdkTrapFocus } from '@angular/cdk/a11y';
|
||||
|
||||
import { TooltipDirective } from '@isa/ui/tooltip';
|
||||
@Component({
|
||||
selector: 'remi-search-item-to-remit-list',
|
||||
templateUrl: './search-item-to-remit-list.component.html',
|
||||
@@ -41,8 +41,10 @@ import { CdkTrapFocus } from '@angular/cdk/a11y';
|
||||
FormsModule,
|
||||
SearchItemToRemitComponent,
|
||||
CdkTrapFocus,
|
||||
TooltipDirective,
|
||||
NgIcon,
|
||||
],
|
||||
providers: [provideIcons({ isaActionSearch })],
|
||||
providers: [provideIcons({ isaActionSearch, isaOtherInfo })],
|
||||
})
|
||||
export class SearchItemToRemitListComponent implements OnInit {
|
||||
host = inject(SearchItemToRemitDialogComponent);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
retailPrice: item().catalogAvailability.price,
|
||||
}"
|
||||
[orientation]="productInfoOrientation()"
|
||||
[innerGridClass]="'grid-cols-[minmax(20rem,1fr),minmax(18rem,auto)]'"
|
||||
></remi-product-info>
|
||||
<div class="text-right">
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user