Files
Lorenz Hilpert 743d6c1ee9 docs: comprehensive CLAUDE.md overhaul with library reference system
- Restructure CLAUDE.md with clearer sections and updated metadata
- Add research guidelines emphasizing subagent usage and documentation-first approach
- Create library reference guide covering all 61 libraries across 12 domains
- Add automated library reference generation tool
- Complete test coverage for reward order confirmation feature (6 new spec files)
- Refine product info components and adapters with improved documentation
- Update workflows documentation for checkout service
- Fix ESLint issues: case declarations, unused imports, and unused variables
2025-10-22 11:55:04 +02:00
..
2025-04-28 15:36:03 +00:00

@isa/oms/shared/product-info

A reusable Angular component library for displaying product information in a standardized, visually consistent format across Order Management System (OMS) workflows.

Overview

The Product Info library provides the ReturnProductInfoComponent, a presentation component that displays product details including product image, contributors, name, and format information. It's specifically designed for use in return process workflows but can be adapted for any OMS context where consistent product information display is needed.

The component integrates seamlessly with the ISA design system, utilizing Tailwind CSS utilities and the ISA typography system for consistent visual presentation.

Table of Contents

Features

  • Standalone Component Architecture - Modern Angular standalone component with explicit imports
  • OnPush Change Detection - Optimized performance with ChangeDetectionStrategy.OnPush
  • Signal-based Inputs - Uses Angular signals for reactive property updates
  • Product Image Integration - Automatic product image loading via EAN with ProductImageDirective
  • Product Navigation - Built-in navigation to product details via ProductRouterLinkDirective
  • Format Icons - Visual format indicators using icon system (Hardcover, Paperback, CD, etc.)
  • E2E Testing Attributes - Comprehensive data-what and data-which attributes for automated testing
  • ISA Design System Integration - Consistent typography and spacing using ISA Tailwind utilities
  • BEM-like SCSS Structure - Maintainable component-scoped styling with clear naming
  • Type Safety - Full TypeScript integration with Zod-validated Product schema

Quick Start

1. Import the Component

import { Component } from '@angular/core';
import { ReturnProductInfoComponent } from '@isa/oms/shared/product-info';
import { Product } from '@isa/oms/data-access';

@Component({
  selector: 'app-return-summary',
  standalone: true,
  imports: [ReturnProductInfoComponent],
  template: `
    <oms-shared-return-product-info
      [product]="product"
    ></oms-shared-return-product-info>
  `
})
export class ReturnSummaryComponent {
  product: Product = {
    ean: '9783161484100',
    name: 'The Great Gatsby',
    contributors: 'F. Scott Fitzgerald',
    format: 'HC',
    formatDetail: 'Hardcover',
    // ... other product properties
  };
}

2. Basic Template Usage

<!-- Minimal usage -->
<oms-shared-return-product-info
  [product]="product"
></oms-shared-return-product-info>

<!-- With E2E attributes -->
<oms-shared-return-product-info
  [product]="product"
  data-what="component"
  data-which="return-product-info"
></oms-shared-return-product-info>

3. Integration with Signals

import { Component, signal } from '@angular/core';
import { ReturnProductInfoComponent } from '@isa/oms/shared/product-info';
import { Product } from '@isa/oms/data-access';

@Component({
  selector: 'app-product-display',
  standalone: true,
  imports: [ReturnProductInfoComponent],
  template: `
    <oms-shared-return-product-info
      [product]="currentProduct()"
    ></oms-shared-return-product-info>
  `
})
export class ProductDisplayComponent {
  currentProduct = signal<Product>({
    ean: '9783161484100',
    name: 'Angular Essentials',
    contributors: 'John Doe',
    format: 'TB',
    formatDetail: 'Paperback',
    // ... other properties
  });

  updateProduct(newProduct: Product): void {
    this.currentProduct.set(newProduct);
  }
}

Component API Reference

ReturnProductInfoComponent

A presentation component that displays product information in a standardized layout with image, contributors, name, and format.

Selector

'oms-shared-return-product-info'

Inputs

Input Type Required Description
product Product Yes Product object containing EAN, name, contributors, format, and other details

Product Model Structure

The component expects a Product object with the following structure:

interface Product {
  // Product Identification
  ean?: string;                      // European Article Number (barcode)
  catalogProductNumber?: string;     // Internal catalog number
  supplierProductNumber?: string;    // Supplier's product number

  // Product Information
  name?: string;                     // Product name/title
  additionalName?: string;           // Subtitle or additional name
  contributors?: string;             // Authors, artists, or contributors
  manufacturer?: string;             // Manufacturer/publisher name

  // Format and Physical Details
  format?: string;                   // Format code (HC, TB, AU, etc.)
  formatDetail?: string;             // Human-readable format (Hardcover, Paperback, etc.)
  size?: any;                        // Physical size information
  weight?: any;                      // Weight information
  volume?: string;                   // Volume number or identifier

  // Classification
  productGroup?: string;             // Product group classification
  productGroupDetails?: string;      // Detailed product group info
  edition?: string;                  // Edition information
  locale?: string;                   // Language/locale code
  serial?: string;                   // Serial number
  publicationDate?: string;          // Publication date (ISO format)

  // TouchBase fields (from TouchBaseSchema)
  // Additional timestamp and metadata fields
}

Template Structure

The component renders the following structure:

<div class="return-product-info" data-what="product-info" data-which="return-product">
  <!-- Product Image with Navigation -->
  <img
    sharedProductRouterLink
    sharedProductImage
    [ean]="product().ean"
    [alt]="product().name"
    class="return-product-info__image w-14"
    data-what="product-image"
  />

  <!-- Product Details -->
  <div class="return-product-info__content">
    <!-- Contributors (Bold) -->
    <div
      class="return-product-info__contributors isa-text-body-2-bold text-isa-neutral-900"
      data-what="product-contributors"
    >
      {{ product().contributors }}
    </div>

    <!-- Product Name (Regular) -->
    <div
      class="return-product-info__name isa-text-body-2-regular text-isa-neutral-900"
      data-what="product-name"
    >
      {{ product().name }}
    </div>

    <!-- Format Icon and Detail -->
    <div class="return-product-info__format" data-what="product-format">
      <ng-icon [name]="product().format | lowercase"></ng-icon>
      <span class="text-isa-secondary-900">{{ product().formatDetail }}</span>
    </div>
  </div>
</div>

E2E Testing Attributes

The component includes comprehensive testing attributes:

Element data-what data-which Description
Root container product-info return-product Main component container
Product image product-image - Product thumbnail image
Contributors product-contributors - Author/artist/contributor names
Product name product-name - Product title/name
Format section product-format - Format icon and detail container

Change Detection

  • Strategy: ChangeDetectionStrategy.OnPush
  • Benefit: Component only re-renders when input references change
  • Usage: Provides optimal performance in lists and grids

Usage Examples

Basic Product Display in Return Workflow

import { Component, inject } from '@angular/core';
import { ReturnProductInfoComponent } from '@isa/oms/shared/product-info';
import { ReturnProcessStore } from '@isa/oms/data-access';

@Component({
  selector: 'app-return-summary-item',
  standalone: true,
  imports: [ReturnProductInfoComponent],
  template: `
    @let product = returnProcess().receiptItem.product;
    <oms-shared-return-product-info
      [product]="product"
      data-what="component"
      data-which="return-product-info"
    ></oms-shared-return-product-info>
  `
})
export class ReturnSummaryItemComponent {
  #returnProcessStore = inject(ReturnProcessStore);
  returnProcess = this.#returnProcessStore.returnProcess;
}

Product List Display

import { Component, signal } from '@angular/core';
import { ReturnProductInfoComponent } from '@isa/oms/shared/product-info';
import { Product } from '@isa/oms/data-access';

@Component({
  selector: 'app-product-list',
  standalone: true,
  imports: [ReturnProductInfoComponent],
  template: `
    <div class="product-grid grid gap-4">
      @for (product of products(); track product.ean) {
        <div class="product-card border rounded-lg p-4">
          <oms-shared-return-product-info
            [product]="product"
            [attr.data-product-ean]="product.ean"
          ></oms-shared-return-product-info>
        </div>
      }
    </div>
  `
})
export class ProductListComponent {
  products = signal<Product[]>([
    {
      ean: '9783161484100',
      name: 'The Great Gatsby',
      contributors: 'F. Scott Fitzgerald',
      format: 'HC',
      formatDetail: 'Hardcover',
      catalogProductNumber: 'CAT-001',
    },
    {
      ean: '9780142437230',
      name: '1984',
      contributors: 'George Orwell',
      format: 'TB',
      formatDetail: 'Paperback',
      catalogProductNumber: 'CAT-002',
    }
  ]);
}

Async Product Loading

import { Component, signal, OnInit, inject } from '@angular/core';
import { ReturnProductInfoComponent } from '@isa/oms/shared/product-info';
import { Product } from '@isa/oms/data-access';
import { ProductService } from './product.service';

@Component({
  selector: 'app-product-detail',
  standalone: true,
  imports: [ReturnProductInfoComponent],
  template: `
    @if (product(); as prod) {
      <div class="product-detail-card">
        <oms-shared-return-product-info
          [product]="prod"
        ></oms-shared-return-product-info>

        <!-- Additional product details -->
        <div class="mt-4">
          <p>EAN: {{ prod.ean }}</p>
          <p>Publisher: {{ prod.manufacturer }}</p>
        </div>
      </div>
    } @else {
      <div class="loading">Loading product...</div>
    }
  `
})
export class ProductDetailComponent implements OnInit {
  #productService = inject(ProductService);
  product = signal<Product | null>(null);

  async ngOnInit(): Promise<void> {
    const productData = await this.#productService.getProduct('9783161484100');
    this.product.set(productData);
  }
}

Integration with Task List

import { Component, input } from '@angular/core';
import { ReturnProductInfoComponent } from '@isa/oms/shared/product-info';
import { Product } from '@isa/oms/data-access';

interface TaskItem {
  id: string;
  product: Product;
  quantity: number;
  status: 'pending' | 'completed';
}

@Component({
  selector: 'app-task-list-item',
  standalone: true,
  imports: [ReturnProductInfoComponent],
  template: `
    <div class="task-item flex items-start gap-4 p-4 border-b">
      <!-- Product Info -->
      <oms-shared-return-product-info
        [product]="task().product"
      ></oms-shared-return-product-info>

      <!-- Task Metadata -->
      <div class="task-meta">
        <span>Quantity: {{ task().quantity }}</span>
        <span>Status: {{ task().status }}</span>
      </div>
    </div>
  `
})
export class TaskListItemComponent {
  task = input.required<TaskItem>();
}

Custom E2E Attributes for Complex Workflows

@Component({
  selector: 'app-return-process-item',
  standalone: true,
  imports: [ReturnProductInfoComponent],
  template: `
    <div
      class="return-item"
      [attr.data-return-id]="returnId()"
      [attr.data-step]="currentStep()"
    >
      <oms-shared-return-product-info
        [product]="product()"
        data-what="component"
        data-which="return-product-info"
        [attr.data-product-ean]="product().ean"
        [attr.data-return-status]="returnStatus()"
      ></oms-shared-return-product-info>
    </div>
  `
})
export class ReturnProcessItemComponent {
  returnId = input.required<string>();
  product = input.required<Product>();
  currentStep = input<number>(1);
  returnStatus = input<string>('pending');
}

Conditional Product Display

import { Component, signal, computed } from '@angular/core';
import { ReturnProductInfoComponent } from '@isa/oms/shared/product-info';
import { Product } from '@isa/oms/data-access';

@Component({
  selector: 'app-conditional-product',
  standalone: true,
  imports: [ReturnProductInfoComponent],
  template: `
    @if (isProductAvailable()) {
      <oms-shared-return-product-info
        [product]="product()!"
      ></oms-shared-return-product-info>
    } @else {
      <div class="product-unavailable">
        <p>Product information unavailable</p>
      </div>
    }
  `
})
export class ConditionalProductComponent {
  product = signal<Product | null>(null);

  isProductAvailable = computed(() => {
    const prod = this.product();
    return prod !== null && prod.ean !== undefined;
  });
}

Styling and Customization

Component Styling Structure

The component uses a BEM-like SCSS structure with the following classes:

.return-product-info {
  display: flex;
  align-items: flex-start;
  gap: 1.5rem;
}

.return-product-info__image {
  border-radius: 0.25rem;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

.return-product-info__content {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.return-product-info__format {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

Layout Characteristics

  • Flexbox Layout: Horizontal layout with image on left, content on right
  • Alignment: flex-start for top alignment of image and content
  • Gap: 1.5rem (24px) between image and content
  • Image: Fixed width of w-14 (56px) with rounded corners and subtle shadow
  • Content Stack: Vertical layout with 0.25rem (4px) gap between elements

ISA Design System Integration

The component uses ISA-specific Tailwind utilities:

Element Typography Class Color Class Description
Contributors isa-text-body-2-bold text-isa-neutral-900 Bold, dark neutral color
Product Name isa-text-body-2-regular text-isa-neutral-900 Regular weight, dark neutral
Format Detail - text-isa-secondary-900 Secondary color for format text
Image w-14 - 56px fixed width thumbnail

Customizing via CSS Custom Properties

While the component uses scoped styles, you can customize the appearance by wrapping it:

<div class="custom-product-info">
  <oms-shared-return-product-info
    [product]="product"
  ></oms-shared-return-product-info>
</div>
.custom-product-info {
  // Adjust the overall container
  ::ng-deep .return-product-info {
    gap: 2rem; // Increase spacing
    padding: 1rem; // Add padding
    background-color: var(--isa-neutral-50); // Add background
    border-radius: 0.5rem;
  }

  // Adjust the image size
  ::ng-deep .return-product-info__image {
    width: 4rem; // 64px instead of 56px
    height: 4rem;
  }
}

Note: Use ::ng-deep sparingly and only when necessary, as it breaks view encapsulation.

Format Icons

The component supports the following format icons from ProductFormatIconGroup:

Format Code Icon Description
tb isaArtikelTaschenbuch Paperback/Taschenbuch
hc isaArtikelKartoniert Hardcover/Kartoniert
au isaArtikelCd Audio CD
ka isaArtikelSonstige Card/Other
sw isaArtikelCd Software/CD
nb isaArtikelGame Game/Notebook

The format code from product.format is automatically lowercased and mapped to the corresponding icon.

Responsive Design Considerations

The component uses fixed-width layout suitable for most contexts. For responsive behavior:

<!-- Stack on mobile, horizontal on desktop -->
<div class="product-wrapper">
  <oms-shared-return-product-info
    [product]="product"
  ></oms-shared-return-product-info>
</div>
.product-wrapper {
  // Override flex direction on mobile
  @media (max-width: 640px) {
    ::ng-deep .return-product-info {
      flex-direction: column;
      align-items: center;
    }
  }
}

Architecture Notes

Component Design Pattern

The component follows the Presentation Component pattern:

  • No Business Logic: Pure presentation with no service injection
  • Signal Input: Reactive data binding via Angular signals
  • OnPush Detection: Optimal performance with immutable inputs
  • Standalone: No module dependencies, explicit imports only

Directive Integration

The component integrates two key directives:

1. ProductImageDirective (sharedProductImage)

  • Purpose: Automatically loads product images from EAN
  • Service: Uses ProductImageService to construct image URLs
  • Inputs:
    • ean (required): Product EAN for image lookup
    • imageWidth (optional): Image width, defaults to 150px
    • imageHeight (optional): Image height, defaults to 150px
  • Behavior: Computes image URL and binds to [src] attribute
  • Purpose: Makes product images clickable and navigable
  • Service: Uses injected PRODUCT_ROUTER_LINK_BUILDER for URL construction
  • Inputs:
    • ean (required): Product EAN for routing
  • Behavior: Extends RouterLink and builds URL asynchronously via effect
  • Visual: Adds cursor-pointer class for clickable appearance

Icon System Integration

The component uses @ng-icons/core with the ISA icon library:

providers: [provideIcons({ ...ProductFormatIconGroup })]

This provides format-specific icons that are:

  • Automatically selected based on lowercase format code
  • Rendered inline with format detail text
  • Scalable and themeable

Change Detection Strategy

ChangeDetectionStrategy.OnPush ensures:

  • Component only re-renders when product input reference changes
  • Optimal performance in lists with many product items
  • Requires immutable data patterns (use .set() not .mutate() on signals)

E2E Testing Architecture

The component follows ISA's E2E testing standards:

data-what="product-info"      <!-- Element type -->
data-which="return-product"   <!-- Element purpose/context -->

This enables QA automation to:

  • Locate elements independent of styling changes
  • Verify product information display
  • Test product navigation behavior
  • Validate format icon rendering

Dependency Graph

ReturnProductInfoComponent
├─→ @isa/oms/data-access (Product type)
├─→ @isa/shared/product-image (ProductImageDirective)
├─→ @isa/shared/product-router-link (ProductRouterLinkDirective)
├─→ @isa/icons (ProductFormatIconGroup)
├─→ @ng-icons/core (NgIcon, provideIcons)
└─→ @angular/common (LowerCasePipe)

Known Architectural Considerations

1. Naming Convention (Low Priority)

Current State:

  • Component name includes "Return" prefix (ReturnProductInfoComponent)
  • Selector uses oms-shared-return-product-info
  • Implies exclusive use in return workflows

Consideration:

  • Component is generic and could be used in any product display context
  • Consider renaming to ProductInfoComponent for broader reusability
  • Would require migration of existing usage sites

Impact: Low - component works well as-is, naming is semantic for current use

2. Product Model Optional Fields

Current State:

  • All Product fields are optional (string | undefined)
  • Component assumes ean, name, contributors, format, and formatDetail exist
  • No null/undefined handling in template

Consideration:

  • Component may render empty strings if required fields are undefined
  • Consider adding default values or conditional rendering
  • Could add input validation or required field checks

Impact: Medium - potential for incomplete UI if product data is partial

Recommended Enhancement:

@Component({
  template: `
    @if (hasRequiredFields()) {
      <!-- Current template -->
    } @else {
      <div class="product-info-error">Incomplete product data</div>
    }
  `
})
export class ReturnProductInfoComponent {
  product = input.required<Product>();

  hasRequiredFields = computed(() => {
    const prod = this.product();
    return !!(prod.ean && prod.name && prod.contributors);
  });
}

3. Image Loading States

Current State:

  • No loading state or error handling for product images
  • ProductImageDirective handles URL construction
  • Missing image will show broken image icon

Consideration:

  • Add loading skeleton or placeholder
  • Handle image load errors gracefully
  • Consider lazy loading for lists

Impact: Medium - affects user experience with slow networks or missing images

4. Format Icon Fallback

Current State:

  • Format code is lowercased and mapped to icon name
  • If format code doesn't match icon group, icon won't render
  • No fallback icon for unknown formats

Consideration:

  • Add default icon for unknown format codes
  • Handle undefined/null format gracefully
  • Log warning when format is unmapped

Impact: Low - most products have standard format codes

Dependencies

Required Angular Packages

  • @angular/core ^20.1.2 - Angular framework (signals, components, change detection)
  • @angular/common - Common Angular utilities (LowerCasePipe)

Required ISA Libraries

  • @isa/oms/data-access - Product type definition and Zod schema
  • @isa/shared/product-image - ProductImageDirective for image loading
  • @isa/shared/product-router-link - ProductRouterLinkDirective for navigation
  • @isa/icons - ProductFormatIconGroup icon set

Required Third-Party Packages

  • @ng-icons/core - Icon rendering system
  • zod - Runtime schema validation (Product type)

Development Dependencies

  • @ngneat/spectator - Testing utilities (current tests)
  • jest - Testing framework
  • ng-mocks - Directive mocking for tests

Path Alias

Import from: @isa/oms/shared/product-info

import { ReturnProductInfoComponent } from '@isa/oms/shared/product-info';

Testing

The library uses Jest with Spectator for testing.

Running Tests

# Run tests for this library
npx nx test oms-shared-product-info --skip-nx-cache

# Run tests with coverage
npx nx test oms-shared-product-info --code-coverage --skip-nx-cache

# Run tests in watch mode
npx nx test oms-shared-product-info --watch

Test Structure

The component includes comprehensive unit tests covering:

  • Component Creation - Validates component instantiation
  • Product Display - Tests rendering of name and contributors
  • Image Directive Binding - Verifies EAN is passed to ProductImageDirective
  • Alt Text - Validates image alt attribute matches product name
  • Format Display - Tests format icon and formatDetail rendering
  • E2E Attributes - Validates all testing attributes are present

Example Test

import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { ReturnProductInfoComponent } from './return-product-info.component';
import { Product } from '@isa/oms/data-access';

const MOCK_PRODUCT: Product = {
  catalogProductNumber: '123456',
  ean: '1234567890123',
  manufacturer: 'Example Publisher',
  name: 'Example Product Name',
  volume: '2',
  contributors: 'John Doe, Jane Smith',
  format: 'HC',
  formatDetail: 'Hardcover',
};

describe('ReturnProductInfoComponent', () => {
  let spectator: Spectator<ReturnProductInfoComponent>;

  const createComponent = createComponentFactory({
    component: ReturnProductInfoComponent,
    // Mock directives to avoid external dependencies
    detectChanges: false,
  });

  beforeEach(() => {
    spectator = createComponent({
      props: { product: MOCK_PRODUCT }
    });
    spectator.detectChanges();
  });

  it('should display product name and contributors', () => {
    const contributorsEl = spectator.query('[data-what="product-contributors"]');
    const nameEl = spectator.query('[data-what="product-name"]');

    expect(contributorsEl).toHaveText(MOCK_PRODUCT.contributors);
    expect(nameEl).toHaveText(MOCK_PRODUCT.name);
  });

  it('should pass correct EAN to product image directive', () => {
    const imgElement = spectator.query('img');
    const productImageDirective = spectator.query(ProductImageDirective);

    expect(productImageDirective?.ean).toBe(MOCK_PRODUCT.ean);
    expect(imgElement).toHaveAttribute('alt', MOCK_PRODUCT.name);
  });
});

Test Coverage

Current test coverage includes:

  • Component instantiation
  • Product name display
  • Contributors display
  • EAN binding to image directive
  • Alt text for accessibility
  • Format icon rendering
  • Format detail text display
  • E2E attribute presence

Testing Best Practices

  1. Mock Directives: Use MockDirective() from ng-mocks for ProductImageDirective and ProductRouterLinkDirective
  2. Mock Product Data: Create comprehensive mock products with all required fields
  3. E2E Selectors: Use data-what attributes for element selection in tests
  4. Signal Testing: Test computed signals and reactive updates
  5. Accessibility: Verify alt text and semantic HTML structure

Migration to Vitest (Planned)

This library is scheduled for migration to Vitest + Angular Testing Utilities:

// Future test structure
import { TestBed } from '@angular/core/testing';
import { describe, it, expect, beforeEach } from 'vitest';
import { ReturnProductInfoComponent } from './return-product-info.component';

describe('ReturnProductInfoComponent', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [ReturnProductInfoComponent]
    });
  });

  it('should create', () => {
    const fixture = TestBed.createComponent(ReturnProductInfoComponent);
    expect(fixture.componentInstance).toBeTruthy();
  });
});

Best Practices

Component Usage

  1. Always Provide Complete Product Data

    // Good: Complete product object
    product = signal<Product>({
      ean: '9783161484100',
      name: 'The Great Gatsby',
      contributors: 'F. Scott Fitzgerald',
      format: 'HC',
      formatDetail: 'Hardcover',
    });
    
    // Avoid: Partial product data may cause rendering issues
    product = signal<Product>({ ean: '123' }); // Missing name, contributors
    
  2. Use Immutable Data Patterns with OnPush

    // Good: Create new reference
    this.product.set({ ...currentProduct, name: 'New Name' });
    
    // Bad: Mutate existing object (won't trigger change detection)
    this.product().name = 'New Name';
    
  3. Include E2E Attributes in Parent Components

    <!-- Good: Add context-specific attributes -->
    <oms-shared-return-product-info
      [product]="product"
      data-what="component"
      data-which="return-product-info"
      [attr.data-product-ean]="product.ean"
    ></oms-shared-return-product-info>
    
    <!-- Minimal: Basic usage without additional attributes -->
    <oms-shared-return-product-info
      [product]="product"
    ></oms-shared-return-product-info>
    
  4. Handle Async Product Loading

    // Good: Wait for product data before rendering
    @Component({
      template: `
        @if (product(); as prod) {
          <oms-shared-return-product-info [product]="prod" />
        } @else {
          <div class="loading-skeleton"></div>
        }
      `
    })
    export class ProductContainerComponent {
      product = signal<Product | null>(null);
    }
    

Styling and Layout

  1. Use Wrapper Divs for Additional Layout

    <!-- Good: Wrap for additional styling without ::ng-deep -->
    <div class="custom-card p-4 border rounded">
      <oms-shared-return-product-info [product]="product" />
    </div>
    
    <!-- Avoid: Don't break encapsulation unless necessary -->
    <style>
      ::ng-deep .return-product-info { /* ... */ }
    </style>
    
  2. Respect Component's Fixed Image Width

    <!-- Component uses w-14 (56px) - design other elements accordingly -->
    <div class="grid grid-cols-[56px_1fr_auto]">
      <oms-shared-return-product-info [product]="product" />
      <div>Additional content</div>
    </div>
    

Performance

  1. Use trackBy in Lists

    <!-- Good: Track by unique identifier -->
    @for (product of products(); track product.ean) {
      <oms-shared-return-product-info [product]="product" />
    }
    
    <!-- Avoid: No tracking causes unnecessary re-renders -->
    @for (product of products(); track $index) {
      <oms-shared-return-product-info [product]="product" />
    }
    
  2. Leverage OnPush Change Detection

    // Good: Component already uses OnPush, ensure parent does too
    @Component({
      changeDetection: ChangeDetectionStrategy.OnPush,
      imports: [ReturnProductInfoComponent]
    })
    

Accessibility

  1. Product Images Already Have Alt Text

    • Component binds product.name to image alt attribute
    • No additional ARIA labels needed for images
  2. Consider Screen Reader Context

    <!-- Good: Add semantic context -->
    <article aria-label="Product Information">
      <oms-shared-return-product-info [product]="product" />
    </article>
    

Testing

  1. Mock External Directives

    import { MockDirective } from 'ng-mocks';
    
    createComponentFactory({
      component: ReturnProductInfoComponent,
      overrideComponents: [[
        ReturnProductInfoComponent,
        {
          remove: { imports: [ProductImageDirective, ProductRouterLinkDirective] },
          add: { imports: [
            MockDirective(ProductImageDirective),
            MockDirective(ProductRouterLinkDirective)
          ]}
        }
      ]]
    });
    
  2. Use E2E Attributes for Selectors

    // Good: Stable selectors independent of styling
    const name = spectator.query('[data-what="product-name"]');
    
    // Avoid: Class-based selectors are fragile
    const name = spectator.query('.return-product-info__name');
    
  3. Test with Realistic Product Data

    // Good: Complete mock product
    const MOCK_PRODUCT: Product = {
      ean: '9783161484100',
      name: 'Complete Product Name',
      contributors: 'First Last, Second Author',
      format: 'HC',
      formatDetail: 'Hardcover',
      catalogProductNumber: 'CAT123',
      manufacturer: 'Publisher Name',
    };
    

Error Handling

  1. Validate Product Data Before Passing to Component

    // Good: Ensure product has required fields
    function isValidProduct(product: Product): boolean {
      return !!(product.ean && product.name && product.contributors);
    }
    
    @Component({
      template: `
        @if (isValidProduct(product())) {
          <oms-shared-return-product-info [product]="product()" />
        } @else {
          <div class="error">Invalid product data</div>
        }
      `
    })
    
  2. Handle Missing Format Icons

    // Component handles lowercase conversion
    // Ensure format codes match ProductFormatIconGroup: tb, hc, au, ka, sw, nb
    product.format = 'HC'; // Will become 'hc' and map to icon
    

License

Internal ISA Frontend library - not for external distribution.