# @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](#features)
- [Quick Start](#quick-start)
- [Component API Reference](#component-api-reference)
- [Usage Examples](#usage-examples)
- [Styling and Customization](#styling-and-customization)
- [Architecture Notes](#architecture-notes)
- [Dependencies](#dependencies)
- [Testing](#testing)
- [Best Practices](#best-practices)

## 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

```typescript
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

```html
<!-- 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

```typescript
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

```typescript
'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:

```typescript
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:

```html
<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

```typescript
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

```typescript
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

```typescript
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

```typescript
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

```typescript
@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

```typescript
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:

```scss
.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:

```html
<div class="custom-product-info">
  <oms-shared-return-product-info
    [product]="product"
  ></oms-shared-return-product-info>
</div>
```

```scss
.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:

```html
<!-- Stack on mobile, horizontal on desktop -->
<div class="product-wrapper">
  <oms-shared-return-product-info
    [product]="product"
  ></oms-shared-return-product-info>
</div>
```

```scss
.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

#### 2. ProductRouterLinkDirective (`sharedProductRouterLink`)

- **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:

```typescript
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:

```html
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:**
```typescript
@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`

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

## Testing

The library uses **Jest** with **Spectator** for testing.

### Running Tests

```bash
# 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

```typescript
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:

```typescript
// 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**
   ```typescript
   // 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**
   ```typescript
   // 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**
   ```html
   <!-- 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**
   ```typescript
   // 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**
   ```html
   <!-- 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**
   ```html
   <!-- 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**
   ```html
   <!-- 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**
   ```typescript
   // 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**
   ```html
   <!-- Good: Add semantic context -->
   <article aria-label="Product Information">
     <oms-shared-return-product-info [product]="product" />
   </article>
   ```

### Testing

1. **Mock External Directives**
   ```typescript
   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**
   ```typescript
   // 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**
   ```typescript
   // 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**
   ```typescript
   // 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**
   ```typescript
   // 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.
