# MangaReader Test Suite

Suite completa de tests para el proyecto MangaReader usando XCTest.

## Tabla de Contenidos

- [Descripción General](#descripción-general)
- [Estructura de Tests](#estructura-de-tests)
- [Ejecutar Tests](#ejecutar-tests)
- [Guía de Tests](#guía-de-tests)
- [Mejores Prácticas](#mejores-prácticas)

## Descripción General

Esta suite de tests cubre todos los componentes principales del proyecto MangaReader:

1. **Modelos de Datos** - Validación de Codable, edge cases, y lógica de negocio
2. **StorageService** - Almacenamiento local, favoritos, progreso de lectura
3. **ManhwaWebScraper** - Web scraping y parsing de HTML/JavaScript
4. **Integración** - Flujos completos que conectan múltiples componentes

## Estructura de Tests

```
Tests/
├── ModelTests.swift              # Tests para modelos de datos
├── StorageServiceTests.swift     # Tests para servicio de almacenamiento
├── ManhwaWebScraperTests.swift   # Tests para web scraper
├── IntegrationTests.swift        # Tests de integración
├── TestHelpers.swift             # Helpers y factories para tests
└── XCTestSuiteExtensions.swift   # Extensiones de XCTest
```

## Ejecutar Tests

### Desde Xcode

1. Abrir el proyecto en Xcode
2. Cmd + U para ejecutar todos los tests
3. Cmd + 6 para abrir el Test Navigator
4. Click derecho en un test específico para ejecutarlo

### Desde Línea de Comandos

```bash
# Ejecutar todos los tests
xcodebuild test -scheme MangaReader -destination 'platform=iOS Simulator,name=iPhone 15'

# Ejecutar tests específicos
xcodebuild test -scheme MangaReader -only-testing:MangaReaderTests/ModelTests

# Ejecutar con cobertura
xcodebuild test -scheme MangaReader -enableCodeCoverage YES
```

## Guía de Tests

### ModelTests.swift

Prueba todos los modelos de datos del proyecto.

#### Tests Incluidos:

**Manga Model:**
- `testMangaInitialization` - Verifica inicialización correcta
- `testMangaCodableSerialization` - Prueba encoding/decoding JSON
- `testMangaDisplayStatus` - Verifica traducción de estados
- `testMangaHashable` - Prueba conformidad con Hashable

**Chapter Model:**
- `testChapterInitialization` - Inicialización con valores por defecto
- `testChapterDisplayNumber` - Formato de número de capítulo
- `testChapterProgress` - Cálculo de progreso de lectura
- `testChapterCodableSerialization` - Serialización JSON

**MangaPage Model:**
- `testMangaPageInitialization` - Creación de páginas
- `testMangaPageThumbnailURL` - URLs de thumbnails
- `testMangaPageCodableSerialization` - Serialización

**ReadingProgress Model:**
- `testReadingProgressInitialization` - Creación de progreso
- `testReadingProgressIsCompleted` - Lógica de completación
- `testReadingProgressCodableSerialization` - Persistencia

**DownloadedChapter Model:**
- `testDownloadedChapterInitialization` - Creación de capítulos descargados
- `testDownloadedChapterDisplayTitle` - Formato de títulos
- `testDownloadedChapterCodableSerialization` - Serialización completa

**Edge Cases:**
- `testMangaWithEmptyGenres` - Manejo de arrays vacíos
- `testMangaWithNilCoverImage` - Imagen de portada opcional
- `testChapterWithZeroNumber` - Capítulo cero
- `testMangaPageWithNegativeIndex` - Índices negativos

### StorageServiceTests.swift

Prueba el servicio de almacenamiento local.

#### Tests Incluidos:

**Favorites:**
- `testSaveFavorite` - Guardar un favorito
- `testSaveMultipleFavorites` - Guardar varios favoritos
- `testSaveDuplicateFavorite` - Evitar duplicados
- `testRemoveFavorite` - Eliminar favorito
- `testIsFavorite` - Verificar si es favorito

**Reading Progress:**
- `testSaveReadingProgress` - Guardar progreso
- `testSaveMultipleReadingProgress` - Múltiples progresos
- `testUpdateExistingReadingProgress` - Actualizar progreso
- `testGetLastReadChapter` - Obtener último capítulo leído
- `testGetReadingProgressWhenNotExists` - Progreso inexistente

**Downloaded Chapters:**
- `testSaveDownloadedChapter` - Guardar metadatos de capítulo
- `testIsChapterDownloaded` - Verificar descarga
- `testGetDownloadedChapters` - Listar capítulos
- `testDeleteDownloadedChapter` - Eliminar capítulo

**Image Caching:**
- `testSaveAndLoadImage` - Guardar y cargar imagen
- `testLoadNonExistentImage` - Imagen inexistente
- `testGetImageURL` - Obtener URL de imagen

**Storage Management:**
- `testGetStorageSize` - Calcular tamaño usado
- `testClearAllDownloads` - Limpiar todo el almacenamiento
- `testFormatFileSize` - Formatear tamaño a legible

**Concurrent Operations:**
- `testConcurrentImageSave` - Guardar imágenes concurrentemente

### ManhwaWebScraperTests.swift

Prueba el web scraper con mocks de WKWebView.

#### Tests Incluidos:

**Error Handling:**
- `testScrapingErrorDescriptions` - Descripciones de errores
- `testScrapingErrorLocalizedError` - Conformidad con LocalizedError

**Chapter Parsing:**
- `testChapterParsingFromJavaScriptResult` - Parsear respuesta JS
- `testChapterParsingWithInvalidData` - Manejar datos inválidos
- `testChapterDeduplication` - Eliminar capítulos duplicados
- `testChapterSorting` - Ordenar capítulos

**Image Parsing:**
- `testImageParsingFromJavaScriptResult` - Parsear URLs de imágenes
- `testImageParsingWithEmptyArray` - Array vacío de imágenes
- `testImageParsingWithInvalidURLs` - Filtrar URLs inválidas

**Manga Info Parsing:**
- `testMangaInfoParsingFromJavaScriptResult` - Extraer info de manga
- `testMangaInfoParsingWithEmptyFields` - Campos vacíos
- `testMangaStatusParsing` - Normalizar estados

**URL Construction:**
- `testMangaURLConstruction` - Construir URLs de manga
- `testChapterURLConstruction` - Construir URLs de capítulo
- `testURLConstructionWithSpecialCharacters` - Caracteres especiales

**Edge Cases:**
- `testChapterNumberExtraction` - Extraer números de capítulo
- `testChapterSlugExtraction` - Extraer slugs
- `testDuplicateRemovalPreservingOrder` - Eliminar duplicados manteniendo orden

### IntegrationTests.swift

Prueba flujos completos que integran múltiples componentes.

#### Tests Incluidos:

**Complete Flow:**
- `testCompleteScrapingAndStorageFlow` - Scraper -> Storage
- `testChapterDownloadFlow` - Descarga completa de capítulo
- `testReadingProgressTrackingFlow` - Seguimiento de lectura

**Multi-Manga Scenarios:**
- `testMultipleMangasProgressTracking` - Varios mangas
- `testMultipleChapterDownloads` - Descargas de múltiples capítulos

**Error Handling:**
- `testDownloadFlowWithMissingImages` - Imágenes faltantes
- `testStorageCleanupFlow` - Limpieza de almacenamiento

**Data Persistence:**
- `testDataPersistenceAcrossOperations` - Persistencia de datos

**Concurrent Operations:**
- `testConcurrentFavoriteOperations` - Operaciones concurrentes favoritos
- `testConcurrentProgressOperations` - Operaciones concurrentes progreso
- `testConcurrentImageOperations` - Guardado concurrente de imágenes

**Large Scale:**
- `testLargeScaleFavoriteOperations` - 1000 favoritos
- `testLargeScaleProgressOperations` - 500 progresos

## TestHelpers.swift

Proporciona helpers y factories para crear datos de prueba:

### TestDataFactory

Crea objetos de prueba:

```swift
let manga = TestDataFactory.createManga(
    slug: "test-manga",
    title: "Test Manga"
)

let chapter = TestDataFactory.createChapter(number: 1)

let chapters = TestDataFactory.createChapters(count: 10)
```

### ImageTestHelpers

Crea imágenes de prueba:

```swift
let image = ImageTestHelpers.createTestImage(
    color: .blue,
    size: CGSize(width: 800, height: 1200)
)
```

### FileSystemTestHelpers

Operaciones de sistema de archivos:

```swift
let tempDir = try FileSystemTestHelpers.createTemporaryDirectory()

try FileSystemTestHelpers.createTestChapterStructure(
    mangaSlug: "test",
    chapterNumber: 1,
    pageCount: 10,
    in: tempDir
)
```

### StorageTestHelpers

Limpieza y preparación de almacenamiento:

```swift
StorageTestHelpers.clearAllStorage()

StorageTestHelpers.seedTestData(
    favoriteCount: 5,
    progressCount: 10
)
```

## Mejores Prácticas

### 1. Independencia de Tests

Cada test debe ser independiente y poder ejecutarse solo:

```swift
override func setUp() {
    super.setUp()
    // Limpiar estado antes del test
    UserDefaults.standard.removeObject(forKey: "favoritesKey")
}

override func tearDown() {
    // Limpiar estado después del test
    super.tearDown()
}
```

### 2. Nombres Descriptivos

Usa nombres que describan qué se está probando:

```swift
// ✅ Bueno
func testSaveDuplicateFavoriteDoesNotAddDuplicate()

// ❌ Malo
func testFavorite()
```

### 3. Un Assert por Test

Cuando sea posible, usa un assert por test:

```swift
// ✅ Bueno
func testFavoriteIsSaved() {
    storageService.saveFavorite(mangaSlug: "test")
    XCTAssertTrue(storageService.isFavorite(mangaSlug: "test"))
}

func testFavoriteIsRemoved() {
    storageService.saveFavorite(mangaSlug: "test")
    storageService.removeFavorite(mangaSlug: "test")
    XCTAssertFalse(storageService.isFavorite(mangaSlug: "test"))
}

// ❌ Evitar
func testFavoriteOperations() {
    storageService.saveFavorite(mangaSlug: "test")
    XCTAssertTrue(storageService.isFavorite(mangaSlug: "test"))

    storageService.removeFavorite(mangaSlug: "test")
    XCTAssertFalse(storageService.isFavorite(mangaSlug: "test"))
}
```

### 4. AAA Pattern

Usa el patrón Arrange-Act-Assert:

```swift
func testChapterProgressCalculation() {
    // Arrange - Preparar el test
    var chapter = Chapter(number: 1, title: "Test", url: "", slug: "")
    let expectedPage = 5

    // Act - Ejecutar la acción
    chapter.lastReadPage = expectedPage

    // Assert - Verificar el resultado
    XCTAssertEqual(chapter.progress, Double(expectedPage))
}
```

### 5. Mock de Dependencias

No hagas llamadas de red reales en tests unitarios:

```swift
// ✅ Bueno - Mock
let mockJSResult = [["number": 10, "title": "Chapter 10"]]
let chapters = parseChaptersFromJS(mockJSResult)

// ❌ Malo - Llamada real
let chapters = await scraper.scrapeChapters(mangaSlug: "test")
```

### 6. Tests Asíncronos

Usa `async/await` apropiadamente:

```swift
func testAsyncImageSave() async throws {
    let image = createTestImage()

    let url = try await storageService.saveImage(
        image,
        mangaSlug: "test",
        chapterNumber: 1,
        pageIndex: 0
    )

    XCTAssertTrue(FileManager.default.fileExists(atPath: url.path))
}
```

## Cobertura de Código

Objetivos de cobertura:

- **Modelos**: 95%+ (lógica crítica de datos)
- **StorageService**: 90%+ (manejo de archivos y persistencia)
- **Scraper**: 85%+ (con mocks de WKWebView)
- **Integración**: 80%+ (flujos críticos de usuario)

## Troubleshooting

### Tests Fallan Intermittentemente

Si un test falla solo algunas veces:

1. Verifica que hay cleanup adecuado en `tearDown()`
2. Asegura que los tests son independientes
3. Usa `waitFor` apropiadamente para operaciones asíncronas

### Tests de Performance Fallan

Si los tests de rendimiento fallan en diferentes máquinas:

1. Ajusta las métricas según el hardware
2. Usa medidas relativas en lugar de absolutas
3. Considera deshabilitar tests de performance en CI

### Memory Leaks en Tests

Para detectar memory leaks:

```swift
func testNoMemoryLeak() {
    let instance = MyClass()
    assertNoMemoryLeak(instance)
}
```

## Recursos Adicionales

- [XCTest Documentation](https://developer.apple.com/documentation/xctest)
- [Testing with Xcode](https://developer.apple.com/documentation/xcode/testing)
- [Unit Testing Best Practices](https://www.objc.io/books/unit-testing/)

## Contribuir

Para agregar nuevos tests:

1. Decide si es unit test, integration test, o performance test
2. Agrega el test al archivo apropiado
3. Usa los helpers en `TestHelpers.swift` cuando sea posible
4. Asegura que el test es independiente
5. Agrega documentación si el test es complejo
6. Ejecuta todos los tests para asegurar que nada se rompe

## Licencia

Mismo que el proyecto principal.
