mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-31 09:37:15 +01:00
Merged PR 1903: fix(remission-list, product-info, search-item-to-remit): improve responsive l...
fix(remission-list, product-info, search-item-to-remit): improve responsive layout and fix orientation logic - Fix grid layout responsiveness in remission-list-item component by updating breakpoint conditions from mobileBreakpoint to desktopBreakpoint - Correct product-info orientation logic to properly apply horizontal/vertical layouts based on breakpoint state - Add consistent orientation handling to search-item-to-remit component with proper breakpoint detection - Update CSS classes to use desktop-large breakpoint for better grid column management - Add bottom margin to remission list container to prevent overlap with fixed action button - Enhance test coverage for new computed properties and breakpoint-dependent behavior Ref: #5239
This commit is contained in:
committed by
Andreas Schickinger
parent
ad00899b6e
commit
d7d535c10d
@@ -5,7 +5,7 @@
|
||||
[item]="i"
|
||||
[orientation]="remiProductInfoOrientation()"
|
||||
></remi-product-info>
|
||||
@if (displayActions() && mobileBreakpoint()) {
|
||||
@if (displayActions() && !desktopBreakpoint()) {
|
||||
<remi-feature-remission-list-item-select
|
||||
class="self-start mt-4"
|
||||
[item]="i"
|
||||
@@ -32,10 +32,8 @@
|
||||
</ui-item-row-data>
|
||||
|
||||
@if (displayActions()) {
|
||||
<ui-item-row-data
|
||||
class="justify-end desktop-small:justify-between col-end-last"
|
||||
>
|
||||
@if (!mobileBreakpoint()) {
|
||||
<ui-item-row-data class="justify-end desktop:justify-between col-end-last">
|
||||
@if (desktopBreakpoint()) {
|
||||
<remi-feature-remission-list-item-select
|
||||
class="self-end mt-4"
|
||||
[item]="i"
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
}
|
||||
|
||||
.ui-client-row {
|
||||
@apply isa-desktop-l:grid-cols-4;
|
||||
@apply isa-desktop:grid-cols-2 desktop-large:grid-cols-4;
|
||||
}
|
||||
|
||||
.ui-client-row-content {
|
||||
@apply isa-desktop:col-span-2 desktop-large:col-span-1;
|
||||
}
|
||||
|
||||
.col-end-last {
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
import { MockComponent } from 'ng-mocks';
|
||||
import { RemissionListItemSelectComponent } from './remission-list-item-select.component';
|
||||
import { signal } from '@angular/core';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
// --- Setup dynamic mocking for injectRemissionListType ---
|
||||
let remissionListTypeValue: RemissionListType = RemissionListType.Pflicht;
|
||||
@@ -26,7 +27,9 @@ jest.mock('../injects/inject-remission-list-type', () => ({
|
||||
// Mock the calculation functions to have predictable behavior
|
||||
jest.mock('@isa/remission/data-access', () => ({
|
||||
...jest.requireActual('@isa/remission/data-access'),
|
||||
calculateStockToRemit: jest.fn(),
|
||||
getStockToRemit: jest.fn(),
|
||||
calculateAvailableStock: jest.fn(),
|
||||
calculateTargetStock: jest.fn(),
|
||||
}));
|
||||
|
||||
// Mock the RemissionStore
|
||||
@@ -57,7 +60,6 @@ describe('RemissionListItemComponent', () => {
|
||||
): ReturnItem =>
|
||||
({
|
||||
id: 1,
|
||||
predefinedReturnQuantity: 5,
|
||||
remainingQuantityInStock: 10,
|
||||
...overrides,
|
||||
}) as ReturnItem;
|
||||
@@ -71,7 +73,6 @@ describe('RemissionListItemComponent', () => {
|
||||
returnItem: {
|
||||
data: {
|
||||
id: 1,
|
||||
predefinedReturnQuantity: 10,
|
||||
},
|
||||
},
|
||||
...overrides,
|
||||
@@ -110,9 +111,15 @@ describe('RemissionListItemComponent', () => {
|
||||
mockRemissionStore.selectedQuantity.set({});
|
||||
mockRemissionStore.remissionStarted.set(true);
|
||||
|
||||
// Reset the mocked function to return 0 by default
|
||||
const { calculateStockToRemit } = require('@isa/remission/data-access');
|
||||
calculateStockToRemit.mockReturnValue(0);
|
||||
// Reset the mocked functions to return default values
|
||||
const {
|
||||
getStockToRemit,
|
||||
calculateAvailableStock,
|
||||
calculateTargetStock,
|
||||
} = require('@isa/remission/data-access');
|
||||
getStockToRemit.mockReturnValue(0);
|
||||
calculateAvailableStock.mockReturnValue(100);
|
||||
calculateTargetStock.mockReturnValue(100);
|
||||
});
|
||||
|
||||
describe('Component Setup', () => {
|
||||
@@ -137,263 +144,245 @@ describe('RemissionListItemComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('predefinedReturnQuantity computed signal', () => {
|
||||
describe('with ReturnItem', () => {
|
||||
beforeEach(() => setRemissionListType(RemissionListType.Pflicht));
|
||||
describe('computed properties', () => {
|
||||
describe('availableStock', () => {
|
||||
it('should calculate available stock correctly', () => {
|
||||
const {
|
||||
calculateAvailableStock,
|
||||
} = require('@isa/remission/data-access');
|
||||
calculateAvailableStock.mockReturnValue(90);
|
||||
|
||||
it('should return predefinedReturnQuantity when available', () => {
|
||||
const mockItem = createMockReturnItem({ predefinedReturnQuantity: 15 });
|
||||
fixture.componentRef.setInput('item', mockItem);
|
||||
fixture.componentRef.setInput('stock', createMockStockInfo());
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.predefinedReturnQuantity()).toBe(15);
|
||||
});
|
||||
|
||||
it('should return 0 when predefinedReturnQuantity is null', () => {
|
||||
const mockItem = createMockReturnItem({
|
||||
predefinedReturnQuantity: null as any,
|
||||
const mockStock = createMockStockInfo({
|
||||
inStock: 100,
|
||||
removedFromStock: 10,
|
||||
});
|
||||
fixture.componentRef.setInput('item', mockItem);
|
||||
fixture.componentRef.setInput('stock', createMockStockInfo());
|
||||
fixture.componentRef.setInput('item', createMockReturnItem());
|
||||
fixture.componentRef.setInput('stock', mockStock);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.predefinedReturnQuantity()).toBe(0);
|
||||
});
|
||||
|
||||
it('should return 0 when predefinedReturnQuantity is undefined', () => {
|
||||
const mockItem = createMockReturnItem({
|
||||
predefinedReturnQuantity: undefined,
|
||||
expect(component.availableStock()).toBe(90);
|
||||
expect(calculateAvailableStock).toHaveBeenCalledWith({
|
||||
stock: 100,
|
||||
removedFromStock: 10,
|
||||
});
|
||||
fixture.componentRef.setInput('item', mockItem);
|
||||
fixture.componentRef.setInput('stock', createMockStockInfo());
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.predefinedReturnQuantity()).toBe(0);
|
||||
});
|
||||
|
||||
it('should return 0 when predefinedReturnQuantity is 0', () => {
|
||||
const mockItem = createMockReturnItem({ predefinedReturnQuantity: 0 });
|
||||
fixture.componentRef.setInput('item', mockItem);
|
||||
fixture.componentRef.setInput('stock', createMockStockInfo());
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.predefinedReturnQuantity()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with ReturnSuggestion', () => {
|
||||
beforeEach(() => setRemissionListType(RemissionListType.Abteilung));
|
||||
describe('stockToRemit', () => {
|
||||
it('should calculate stock to remit correctly', () => {
|
||||
const { getStockToRemit } = require('@isa/remission/data-access');
|
||||
getStockToRemit.mockReturnValue(25);
|
||||
|
||||
it('should return predefinedReturnQuantity from returnItem.data when available', () => {
|
||||
const mockSuggestion = createMockReturnSuggestion({
|
||||
returnItem: {
|
||||
data: {
|
||||
id: 1,
|
||||
predefinedReturnQuantity: 25,
|
||||
},
|
||||
},
|
||||
});
|
||||
fixture.componentRef.setInput('item', mockSuggestion);
|
||||
fixture.componentRef.setInput('stock', createMockStockInfo());
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.predefinedReturnQuantity()).toBe(25);
|
||||
});
|
||||
|
||||
it('should return 0 when returnItem.data.predefinedReturnQuantity is null', () => {
|
||||
const mockSuggestion = createMockReturnSuggestion({
|
||||
returnItem: {
|
||||
data: {
|
||||
id: 1,
|
||||
predefinedReturnQuantity: null as any,
|
||||
},
|
||||
},
|
||||
});
|
||||
fixture.componentRef.setInput('item', mockSuggestion);
|
||||
fixture.componentRef.setInput('stock', createMockStockInfo());
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.predefinedReturnQuantity()).toBe(0);
|
||||
});
|
||||
|
||||
it('should return 0 when returnItem.data.predefinedReturnQuantity is undefined', () => {
|
||||
const mockSuggestion = createMockReturnSuggestion({
|
||||
returnItem: {
|
||||
data: {
|
||||
id: 1,
|
||||
predefinedReturnQuantity: undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
fixture.componentRef.setInput('item', mockSuggestion);
|
||||
fixture.componentRef.setInput('stock', createMockStockInfo());
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.predefinedReturnQuantity()).toBe(0);
|
||||
});
|
||||
|
||||
it('should return 0 when returnItem is null', () => {
|
||||
const mockSuggestion = createMockReturnSuggestion({
|
||||
returnItem: null as any,
|
||||
});
|
||||
fixture.componentRef.setInput('item', mockSuggestion);
|
||||
fixture.componentRef.setInput('stock', createMockStockInfo());
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.predefinedReturnQuantity()).toBe(0);
|
||||
});
|
||||
|
||||
it('should return 0 when returnItem.data is null', () => {
|
||||
const mockSuggestion = createMockReturnSuggestion({
|
||||
returnItem: {
|
||||
data: null as any,
|
||||
},
|
||||
});
|
||||
fixture.componentRef.setInput('item', mockSuggestion);
|
||||
fixture.componentRef.setInput('stock', createMockStockInfo());
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.predefinedReturnQuantity()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Type detection', () => {
|
||||
it('should correctly identify ReturnSuggestion type', () => {
|
||||
setRemissionListType(RemissionListType.Abteilung);
|
||||
const mockSuggestion = createMockReturnSuggestion();
|
||||
fixture.componentRef.setInput('item', mockSuggestion);
|
||||
fixture.componentRef.setInput('stock', createMockStockInfo());
|
||||
fixture.detectChanges();
|
||||
|
||||
const item = component.item();
|
||||
expect('returnItem' in item).toBe(true);
|
||||
expect('predefinedReturnQuantity' in item).toBe(false);
|
||||
});
|
||||
|
||||
it('should correctly identify ReturnItem type', () => {
|
||||
setRemissionListType(RemissionListType.Pflicht);
|
||||
const mockItem = createMockReturnItem();
|
||||
const mockStock = createMockStockInfo();
|
||||
fixture.componentRef.setInput('item', mockItem);
|
||||
fixture.componentRef.setInput('stock', mockStock);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.stockToRemit()).toBe(25);
|
||||
expect(getStockToRemit).toHaveBeenCalledWith({
|
||||
remissionItem: mockItem,
|
||||
remissionListType: remissionListTypeValue,
|
||||
availableStock: 100, // default mock value
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('targetStock', () => {
|
||||
it('should calculate target stock correctly', () => {
|
||||
const { calculateTargetStock } = require('@isa/remission/data-access');
|
||||
calculateTargetStock.mockReturnValue(75);
|
||||
|
||||
const mockItem = createMockReturnItem({ remainingQuantityInStock: 15 });
|
||||
fixture.componentRef.setInput('item', mockItem);
|
||||
fixture.componentRef.setInput('stock', createMockStockInfo());
|
||||
fixture.detectChanges();
|
||||
|
||||
const item = component.item();
|
||||
expect('returnItem' in item).toBe(false);
|
||||
expect('predefinedReturnQuantity' in item).toBe(true);
|
||||
expect(component.targetStock()).toBe(75);
|
||||
expect(calculateTargetStock).toHaveBeenCalledWith({
|
||||
availableStock: 100, // default mock value
|
||||
stockToRemit: 0, // default mock value
|
||||
remainingQuantityInStock: 15,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('remainingQuantityInStock', () => {
|
||||
it('should return remainingQuantityInStock from item', () => {
|
||||
const mockItem = createMockReturnItem({ remainingQuantityInStock: 42 });
|
||||
fixture.componentRef.setInput('item', mockItem);
|
||||
fixture.componentRef.setInput('stock', createMockStockInfo());
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.remainingQuantityInStock()).toBe(42);
|
||||
});
|
||||
|
||||
it('should handle undefined remainingQuantityInStock', () => {
|
||||
const mockItem = createMockReturnItem({
|
||||
remainingQuantityInStock: undefined,
|
||||
});
|
||||
fixture.componentRef.setInput('item', mockItem);
|
||||
fixture.componentRef.setInput('stock', createMockStockInfo());
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.remainingQuantityInStock()).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('selectedStockToRemit', () => {
|
||||
it('should return selected quantity from store', () => {
|
||||
mockRemissionStore.selectedQuantity.set({ 1: 15 });
|
||||
|
||||
const mockItem = createMockReturnItem({ id: 1 });
|
||||
fixture.componentRef.setInput('item', mockItem);
|
||||
fixture.componentRef.setInput('stock', createMockStockInfo());
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.selectedStockToRemit()).toBe(15);
|
||||
});
|
||||
|
||||
it('should return undefined when no selected quantity exists', () => {
|
||||
mockRemissionStore.selectedQuantity.set({});
|
||||
|
||||
const mockItem = createMockReturnItem({ id: 1 });
|
||||
fixture.componentRef.setInput('item', mockItem);
|
||||
fixture.componentRef.setInput('stock', createMockStockInfo());
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.selectedStockToRemit()).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('displayActions', () => {
|
||||
it('should return true when stockToRemit > 0 and remission started', () => {
|
||||
const { getStockToRemit } = require('@isa/remission/data-access');
|
||||
getStockToRemit.mockReturnValue(5);
|
||||
mockRemissionStore.remissionStarted.set(true);
|
||||
|
||||
fixture.componentRef.setInput('item', createMockReturnItem());
|
||||
fixture.componentRef.setInput('stock', createMockStockInfo());
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.displayActions()).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when stockToRemit is 0', () => {
|
||||
const { getStockToRemit } = require('@isa/remission/data-access');
|
||||
getStockToRemit.mockReturnValue(0);
|
||||
mockRemissionStore.remissionStarted.set(true);
|
||||
|
||||
fixture.componentRef.setInput('item', createMockReturnItem());
|
||||
fixture.componentRef.setInput('stock', createMockStockInfo());
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.displayActions()).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when remission has not started', () => {
|
||||
const { getStockToRemit } = require('@isa/remission/data-access');
|
||||
getStockToRemit.mockReturnValue(5);
|
||||
mockRemissionStore.remissionStarted.set(false);
|
||||
|
||||
fixture.componentRef.setInput('item', createMockReturnItem());
|
||||
fixture.componentRef.setInput('stock', createMockStockInfo());
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.displayActions()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Component reactivity', () => {
|
||||
it('should update predefinedReturnQuantity when input changes from ReturnItem to ReturnSuggestion', () => {
|
||||
describe('Component behavior with different remission list types', () => {
|
||||
it('should call getStockToRemit with correct parameters for Pflicht type', () => {
|
||||
setRemissionListType(RemissionListType.Pflicht);
|
||||
const mockItem = createMockReturnItem({ predefinedReturnQuantity: 5 });
|
||||
const { getStockToRemit } = require('@isa/remission/data-access');
|
||||
|
||||
const mockItem = createMockReturnItem();
|
||||
fixture.componentRef.setInput('item', mockItem);
|
||||
fixture.componentRef.setInput('stock', createMockStockInfo());
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.predefinedReturnQuantity()).toBe(5);
|
||||
// Access the computed property to trigger the calculation
|
||||
component.stockToRemit();
|
||||
|
||||
setRemissionListType(RemissionListType.Abteilung);
|
||||
const mockSuggestion = createMockReturnSuggestion({
|
||||
returnItem: {
|
||||
data: {
|
||||
id: 1,
|
||||
predefinedReturnQuantity: 20,
|
||||
},
|
||||
},
|
||||
expect(getStockToRemit).toHaveBeenCalledWith({
|
||||
remissionItem: mockItem,
|
||||
remissionListType: RemissionListType.Pflicht,
|
||||
availableStock: 100,
|
||||
});
|
||||
fixture.componentRef.setInput('item', mockSuggestion);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.predefinedReturnQuantity()).toBe(20);
|
||||
});
|
||||
|
||||
it('should update predefinedReturnQuantity when input changes from ReturnSuggestion to ReturnItem', () => {
|
||||
it('should call getStockToRemit with correct parameters for Abteilung type', () => {
|
||||
setRemissionListType(RemissionListType.Abteilung);
|
||||
const mockSuggestion = createMockReturnSuggestion({
|
||||
returnItem: {
|
||||
data: {
|
||||
id: 1,
|
||||
predefinedReturnQuantity: 30,
|
||||
},
|
||||
},
|
||||
});
|
||||
const { getStockToRemit } = require('@isa/remission/data-access');
|
||||
|
||||
const mockSuggestion = createMockReturnSuggestion();
|
||||
fixture.componentRef.setInput('item', mockSuggestion);
|
||||
fixture.componentRef.setInput('stock', createMockStockInfo());
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.predefinedReturnQuantity()).toBe(30);
|
||||
// Access the computed property to trigger the calculation
|
||||
component.stockToRemit();
|
||||
|
||||
setRemissionListType(RemissionListType.Pflicht);
|
||||
const mockItem = createMockReturnItem({ predefinedReturnQuantity: 8 });
|
||||
fixture.componentRef.setInput('item', mockItem);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.predefinedReturnQuantity()).toBe(8);
|
||||
expect(getStockToRemit).toHaveBeenCalledWith({
|
||||
remissionItem: mockSuggestion,
|
||||
remissionListType: RemissionListType.Abteilung,
|
||||
availableStock: 100,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge cases', () => {
|
||||
beforeEach(() => setRemissionListType(RemissionListType.Pflicht));
|
||||
describe('Dialog interactions', () => {
|
||||
it('should open remission quantity dialog and update store on valid input', async () => {
|
||||
const mockDialogRef = {
|
||||
closed: of({ inputValue: 10 }), // Return Observable instead of object with toPromise
|
||||
};
|
||||
mockNumberInputDialog.mockReturnValue(mockDialogRef);
|
||||
|
||||
it('should handle negative predefinedReturnQuantity values', () => {
|
||||
const mockItem = createMockReturnItem({ predefinedReturnQuantity: -5 });
|
||||
const mockItem = createMockReturnItem({ id: 1 });
|
||||
fixture.componentRef.setInput('item', mockItem);
|
||||
fixture.componentRef.setInput('stock', createMockStockInfo());
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.predefinedReturnQuantity()).toBe(-5);
|
||||
});
|
||||
await component.openRemissionQuantityDialog();
|
||||
|
||||
it('should handle very large predefinedReturnQuantity values', () => {
|
||||
const mockItem = createMockReturnItem({
|
||||
predefinedReturnQuantity: 999999,
|
||||
});
|
||||
fixture.componentRef.setInput('item', mockItem);
|
||||
fixture.componentRef.setInput('stock', createMockStockInfo());
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.predefinedReturnQuantity()).toBe(999999);
|
||||
});
|
||||
|
||||
it('should handle decimal predefinedReturnQuantity values', () => {
|
||||
const mockItem = createMockReturnItem({ predefinedReturnQuantity: 3.5 });
|
||||
fixture.componentRef.setInput('item', mockItem);
|
||||
fixture.componentRef.setInput('stock', createMockStockInfo());
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.predefinedReturnQuantity()).toBe(3.5);
|
||||
});
|
||||
|
||||
it('should handle deeply nested null values in ReturnSuggestion', () => {
|
||||
setRemissionListType(RemissionListType.Abteilung);
|
||||
const mockSuggestion = {
|
||||
id: 1,
|
||||
remainingQuantityInStock: 10,
|
||||
returnItem: {
|
||||
data: null as any,
|
||||
expect(mockNumberInputDialog).toHaveBeenCalledWith({
|
||||
title: 'Remi-Menge ändern',
|
||||
data: {
|
||||
message: 'Wie viele Exemplare können remittiert werden?',
|
||||
inputLabel: 'Remi-Menge',
|
||||
inputValidation: expect.arrayContaining([
|
||||
expect.objectContaining({ errorKey: 'required' }),
|
||||
expect.objectContaining({ errorKey: 'pattern' }),
|
||||
]),
|
||||
},
|
||||
} as ReturnSuggestion;
|
||||
fixture.componentRef.setInput('item', mockSuggestion);
|
||||
fixture.componentRef.setInput('stock', createMockStockInfo());
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
expect(component.predefinedReturnQuantity()).toBe(0);
|
||||
expect(mockRemissionStore.updateRemissionQuantity).toHaveBeenCalledWith(
|
||||
1,
|
||||
mockItem,
|
||||
10,
|
||||
);
|
||||
expect(mockFeedbackDialog).toHaveBeenCalledWith({
|
||||
data: { message: 'Remi-Menge wurde geändert' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle item with unexpected structure', () => {
|
||||
const unexpectedItem = {
|
||||
id: 1,
|
||||
remainingQuantityInStock: 10,
|
||||
// Missing both returnItem and predefinedReturnQuantity
|
||||
} as any;
|
||||
fixture.componentRef.setInput('item', unexpectedItem);
|
||||
it('should not update store when dialog is cancelled', async () => {
|
||||
const mockDialogRef = {
|
||||
closed: of(null), // Return Observable with null result
|
||||
};
|
||||
mockNumberInputDialog.mockReturnValue(mockDialogRef);
|
||||
|
||||
const mockItem = createMockReturnItem({ id: 1 });
|
||||
fixture.componentRef.setInput('item', mockItem);
|
||||
fixture.componentRef.setInput('stock', createMockStockInfo());
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.predefinedReturnQuantity()).toBe(0);
|
||||
await component.openRemissionQuantityDialog();
|
||||
|
||||
expect(mockRemissionStore.updateRemissionQuantity).not.toHaveBeenCalled();
|
||||
expect(mockFeedbackDialog).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,7 +26,6 @@ import { ClientRowImports, ItemRowDataImports } from '@isa/ui/item-rows';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { Breakpoint, breakpoint } from '@isa/ui/layout';
|
||||
import { injectRemissionListType } from '../injects/inject-remission-list-type';
|
||||
import { CheckboxComponent } from '@isa/ui/input-controls';
|
||||
import { RemissionListItemSelectComponent } from './remission-list-item-select.component';
|
||||
|
||||
/**
|
||||
@@ -56,7 +55,6 @@ import { RemissionListItemSelectComponent } from './remission-list-item-select.c
|
||||
TextButtonComponent,
|
||||
ClientRowImports,
|
||||
ItemRowDataImports,
|
||||
CheckboxComponent,
|
||||
RemissionListItemSelectComponent,
|
||||
],
|
||||
})
|
||||
@@ -80,9 +78,10 @@ export class RemissionListItemComponent {
|
||||
#store = inject(RemissionStore);
|
||||
|
||||
/**
|
||||
* Signal indicating if the current layout is mobile (tablet breakpoint or below).
|
||||
* Signal providing the current breakpoint state.
|
||||
* Used to determine layout orientation and visibility of action buttons.
|
||||
*/
|
||||
mobileBreakpoint = breakpoint([Breakpoint.Tablet]);
|
||||
desktopBreakpoint = breakpoint([Breakpoint.DekstopL, Breakpoint.DekstopXL]);
|
||||
|
||||
/**
|
||||
* Signal providing the current remission list type (Abteilung or Pflicht).
|
||||
@@ -106,11 +105,11 @@ export class RemissionListItemComponent {
|
||||
productGroupValue = input<string>('');
|
||||
|
||||
/**
|
||||
* Computes the orientation for the product info section based on breakpoint.
|
||||
* @returns 'horizontal' if mobile, otherwise 'vertical'
|
||||
* Computes the orientation of the product info based on the mobile breakpoint.
|
||||
* If on mobile, uses vertical layout; otherwise, horizontal.
|
||||
*/
|
||||
remiProductInfoOrientation = computed(() => {
|
||||
return this.mobileBreakpoint() ? 'horizontal' : 'vertical';
|
||||
return this.desktopBreakpoint() ? 'horizontal' : 'vertical';
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
{{ hits() }} Einträge
|
||||
</span>
|
||||
|
||||
<div class="flex flex-col gap-4 w-full items-center justify-center">
|
||||
<div class="flex flex-col gap-4 w-full items-center justify-center mb-24">
|
||||
@for (item of items(); track item.id) {
|
||||
@defer (on viewport) {
|
||||
<remi-feature-remission-list-item
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
|
||||
<div
|
||||
class="grid"
|
||||
[class.grid-cols-[minmax(20rem,1fr),auto]]="horizontal"
|
||||
[class.gap-6]="horizontal"
|
||||
[class.grid-flow-row]="!horizontal"
|
||||
[class.gap-2]="!horizontal"
|
||||
[class.grid-cols-[minmax(20rem,1fr),auto]]="!horizontal"
|
||||
[class.gap-6]="!horizontal"
|
||||
[class.grid-flow-row]="horizontal"
|
||||
[class.gap-2]="horizontal"
|
||||
>
|
||||
<div class="grid grid-flow-row gap-2">
|
||||
<div class="isa-text-body-2-bold" data-what="product-contributors">
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ProductInfoComponent, ProductInfoItem } from './product-info.component';
|
||||
import {
|
||||
ProductInfoComponent,
|
||||
ProductInfoItem,
|
||||
} from './product-info.component';
|
||||
import { MockComponents, MockDirectives } from 'ng-mocks';
|
||||
import { ProductFormatComponent } from '@isa/shared/product-foramt';
|
||||
import { ProductImageDirective } from '@isa/shared/product-image';
|
||||
@@ -33,7 +36,11 @@ describe('ProductInfoComponent', () => {
|
||||
})
|
||||
.overrideComponent(ProductInfoComponent, {
|
||||
remove: {
|
||||
imports: [ProductFormatComponent, ProductImageDirective, ProductRouterLinkDirective],
|
||||
imports: [
|
||||
ProductFormatComponent,
|
||||
ProductImageDirective,
|
||||
ProductRouterLinkDirective,
|
||||
],
|
||||
},
|
||||
add: {
|
||||
imports: [
|
||||
@@ -81,7 +88,7 @@ describe('ProductInfoComponent', () => {
|
||||
it('should set host attributes correctly', () => {
|
||||
fixture.componentRef.setInput('item', mockProductItem);
|
||||
fixture.detectChanges();
|
||||
|
||||
|
||||
const hostElement = fixture.debugElement.nativeElement;
|
||||
expect(hostElement.getAttribute('data-what')).toBe('product-info');
|
||||
expect(hostElement.getAttribute('data-which')).toBe('remission-product');
|
||||
@@ -98,21 +105,25 @@ describe('ProductInfoComponent', () => {
|
||||
it('should display product image with correct attributes', () => {
|
||||
const productImage = fixture.debugElement.query(By.css('img'));
|
||||
expect(productImage).toBeTruthy();
|
||||
expect(productImage.nativeElement.getAttribute('data-what')).toBe('product-image');
|
||||
expect(productImage.nativeElement.getAttribute('data-what')).toBe(
|
||||
'product-image',
|
||||
);
|
||||
expect(productImage.nativeElement.alt).toBe('Test Product');
|
||||
});
|
||||
|
||||
it('should display product contributors', () => {
|
||||
const contributorsElement = fixture.debugElement.query(
|
||||
By.css('[data-what="product-contributors"]')
|
||||
By.css('[data-what="product-contributors"]'),
|
||||
);
|
||||
expect(contributorsElement).toBeTruthy();
|
||||
expect(contributorsElement.nativeElement.textContent.trim()).toBe('Test Contributors');
|
||||
expect(contributorsElement.nativeElement.textContent.trim()).toBe(
|
||||
'Test Contributors',
|
||||
);
|
||||
});
|
||||
|
||||
it('should display product name', () => {
|
||||
const nameElement = fixture.debugElement.query(
|
||||
By.css('[data-what="product-name"]')
|
||||
By.css('[data-what="product-name"]'),
|
||||
);
|
||||
expect(nameElement).toBeTruthy();
|
||||
expect(nameElement.nativeElement.textContent.trim()).toBe('Test Product');
|
||||
@@ -120,7 +131,7 @@ describe('ProductInfoComponent', () => {
|
||||
|
||||
it('should display formatted price', () => {
|
||||
const priceElement = fixture.debugElement.query(
|
||||
By.css('[data-what="product-price"]')
|
||||
By.css('[data-what="product-price"]'),
|
||||
);
|
||||
expect(priceElement).toBeTruthy();
|
||||
expect(priceElement.nativeElement.textContent.trim()).toBe('€19.99');
|
||||
@@ -128,7 +139,7 @@ describe('ProductInfoComponent', () => {
|
||||
|
||||
it('should display product EAN', () => {
|
||||
const eanElement = fixture.debugElement.query(
|
||||
By.css('[data-what="product-ean"]')
|
||||
By.css('[data-what="product-ean"]'),
|
||||
);
|
||||
expect(eanElement).toBeTruthy();
|
||||
expect(eanElement.nativeElement.textContent.trim()).toBe('1234567890123');
|
||||
@@ -136,10 +147,12 @@ describe('ProductInfoComponent', () => {
|
||||
|
||||
it('should render product format component with correct inputs', () => {
|
||||
const formatComponent = fixture.debugElement.query(
|
||||
By.css('shared-product-format')
|
||||
By.css('shared-product-format'),
|
||||
);
|
||||
expect(formatComponent).toBeTruthy();
|
||||
expect(formatComponent.nativeElement.getAttribute('data-what')).toBe('product-format');
|
||||
expect(formatComponent.nativeElement.getAttribute('data-what')).toBe(
|
||||
'product-format',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -150,10 +163,10 @@ describe('ProductInfoComponent', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const layoutDiv = fixture.debugElement.query(
|
||||
By.css('.grid.grid-cols-\\[minmax\\(20rem\\,1fr\\)\\,auto\\]')
|
||||
By.css('.grid.grid-flow-row.gap-2'),
|
||||
);
|
||||
expect(layoutDiv).toBeTruthy();
|
||||
expect(layoutDiv.nativeElement.classList.contains('gap-6')).toBe(true);
|
||||
expect(layoutDiv.nativeElement.classList.contains('gap-2')).toBe(true);
|
||||
});
|
||||
|
||||
it('should apply vertical layout classes when orientation is vertical', () => {
|
||||
@@ -185,9 +198,11 @@ describe('ProductInfoComponent', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const nameElement = fixture.debugElement.query(
|
||||
By.css('[data-what="product-name"]')
|
||||
By.css('[data-what="product-name"]'),
|
||||
);
|
||||
expect(nameElement.nativeElement.textContent.trim()).toBe(
|
||||
'Updated Product Name',
|
||||
);
|
||||
expect(nameElement.nativeElement.textContent.trim()).toBe('Updated Product Name');
|
||||
|
||||
const hostElement = fixture.debugElement.nativeElement;
|
||||
expect(hostElement.getAttribute('data-ean')).toBe('9876543210987');
|
||||
@@ -198,7 +213,7 @@ describe('ProductInfoComponent', () => {
|
||||
...mockProductItem,
|
||||
retailPrice: {
|
||||
value: {
|
||||
value: 25.50,
|
||||
value: 25.5,
|
||||
currencySymbol: '$',
|
||||
},
|
||||
},
|
||||
@@ -208,7 +223,7 @@ describe('ProductInfoComponent', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const priceElement = fixture.debugElement.query(
|
||||
By.css('[data-what="product-price"]')
|
||||
By.css('[data-what="product-price"]'),
|
||||
);
|
||||
expect(priceElement.nativeElement.textContent.trim()).toBe('$25.50');
|
||||
});
|
||||
@@ -228,7 +243,7 @@ describe('ProductInfoComponent', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const priceElement = fixture.debugElement.query(
|
||||
By.css('[data-what="product-price"]')
|
||||
By.css('[data-what="product-price"]'),
|
||||
);
|
||||
expect(priceElement.nativeElement.textContent.trim()).toBe('€0.00');
|
||||
});
|
||||
@@ -243,28 +258,30 @@ describe('ProductInfoComponent', () => {
|
||||
it('should have proper grid structure', () => {
|
||||
const hostElement = fixture.debugElement.nativeElement;
|
||||
expect(hostElement.classList.contains('grid')).toBe(true);
|
||||
expect(hostElement.classList.contains('grid-cols-[3.5rem,1fr]')).toBe(true);
|
||||
expect(hostElement.classList.contains('grid-cols-[3.5rem,1fr]')).toBe(
|
||||
true,
|
||||
);
|
||||
expect(hostElement.classList.contains('gap-6')).toBe(true);
|
||||
});
|
||||
|
||||
it('should maintain data attributes for testing', () => {
|
||||
const dataWhatElements = fixture.debugElement.queryAll(
|
||||
By.css('[data-what]')
|
||||
By.css('[data-what]'),
|
||||
);
|
||||
expect(dataWhatElements.length).toBeGreaterThan(0);
|
||||
|
||||
const expectedDataWhatValues = [
|
||||
'product-image',
|
||||
'product-contributors',
|
||||
'product-name',
|
||||
'product-name',
|
||||
'product-price',
|
||||
'product-format',
|
||||
'product-ean',
|
||||
];
|
||||
|
||||
expectedDataWhatValues.forEach(value => {
|
||||
expectedDataWhatValues.forEach((value) => {
|
||||
const element = fixture.debugElement.query(
|
||||
By.css(`[data-what="${value}"]`)
|
||||
By.css(`[data-what="${value}"]`),
|
||||
);
|
||||
expect(element).toBeTruthy();
|
||||
});
|
||||
@@ -285,7 +302,7 @@ describe('ProductInfoComponent', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const nameElement = fixture.debugElement.query(
|
||||
By.css('[data-what="product-name"]')
|
||||
By.css('[data-what="product-name"]'),
|
||||
);
|
||||
expect(nameElement.nativeElement.textContent.trim()).toBe('');
|
||||
});
|
||||
@@ -303,7 +320,7 @@ describe('ProductInfoComponent', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const contributorsElement = fixture.debugElement.query(
|
||||
By.css('[data-what="product-contributors"]')
|
||||
By.css('[data-what="product-contributors"]'),
|
||||
);
|
||||
expect(contributorsElement.nativeElement.textContent.trim()).toBe('');
|
||||
});
|
||||
@@ -321,9 +338,11 @@ describe('ProductInfoComponent', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const nameElement = fixture.debugElement.query(
|
||||
By.css('[data-what="product-name"]')
|
||||
By.css('[data-what="product-name"]'),
|
||||
);
|
||||
expect(nameElement.nativeElement.textContent.trim()).toContain(
|
||||
'This is a very long product name',
|
||||
);
|
||||
expect(nameElement.nativeElement.textContent.trim()).toContain('This is a very long product name');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
product: item().product,
|
||||
retailPrice: item().catalogAvailability.price,
|
||||
}"
|
||||
[orientation]="productInfoOrientation()"
|
||||
></remi-product-info>
|
||||
<div class="text-right">
|
||||
<button
|
||||
|
||||
@@ -3,10 +3,12 @@ import {
|
||||
Component,
|
||||
inject,
|
||||
input,
|
||||
computed,
|
||||
} from '@angular/core';
|
||||
import { Item } from '@isa/catalogue/data-access';
|
||||
import { ProductInfoComponent } from '@isa/remission/shared/product';
|
||||
import { TextButtonComponent } from '@isa/ui/buttons';
|
||||
import { Breakpoint, breakpoint } from '@isa/ui/layout';
|
||||
import { SearchItemToRemitDialogComponent } from './search-item-to-remit-dialog.component';
|
||||
|
||||
@Component({
|
||||
@@ -20,4 +22,10 @@ export class SearchItemToRemitComponent {
|
||||
host = inject(SearchItemToRemitDialogComponent);
|
||||
|
||||
item = input.required<Item>();
|
||||
|
||||
desktopBreakpoint = breakpoint([Breakpoint.DekstopL, Breakpoint.DekstopXL]);
|
||||
|
||||
productInfoOrientation = computed(() => {
|
||||
return this.desktopBreakpoint() ? 'vertical' : 'horizontal';
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user