import XCTest @testable import MangaReader /// Tests para los modelos de datos: Manga, Chapter, MangaPage, ReadingProgress, DownloadedChapter final class ModelTests: XCTestCase { // MARK: - Setup & Teardown override func setUp() { super.setUp() // Configuración inicial antes de cada test } override func tearDown() { // Limpieza después de cada test super.tearDown() } // MARK: - Manga Model Tests func testMangaInitialization() { let manga = Manga( slug: "tower-of-god", title: "Tower of God", description: "A young boy enters a mysterious tower", genres: ["Action", "Fantasy", "Adventure"], status: "PUBLICANDOSE", url: "https://manhwaweb.com/manga/tower-of-god", coverImage: "https://example.com/cover.jpg" ) XCTAssertEqual(manga.slug, "tower-of-god") XCTAssertEqual(manga.title, "Tower of God") XCTAssertEqual(manga.genres.count, 3) XCTAssertEqual(manga.id, "tower-of-god") } func testMangaCodableSerialization() { let manga = Manga( slug: "solo-leveling", title: "Solo Leveling", description: "The weakest hunter becomes the strongest", genres: ["Action", "Adventure"], status: "FINALIZADO", url: "https://manhwaweb.com/manga/solo-leveling", coverImage: nil ) // Test encoding do { let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted let jsonData = try encoder.encode(manga) XCTAssertFalse(jsonData.isEmpty) // Test decoding let decoder = JSONDecoder() let decodedManga = try decoder.decode(Manga.self, from: jsonData) XCTAssertEqual(manga.slug, decodedManga.slug) XCTAssertEqual(manga.title, decodedManga.title) XCTAssertEqual(manga.description, decodedManga.description) XCTAssertEqual(manga.genres, decodedManga.genres) XCTAssertEqual(manga.status, decodedManga.status) XCTAssertEqual(manga.url, decodedManga.url) XCTAssertEqual(manga.coverImage, decodedManga.coverImage) } catch { XCTFail("Coding/decoding failed: \(error)") } } func testMangaDisplayStatus() { let publishingManga = Manga( slug: "test1", title: "Test", description: "Desc", genres: [], status: "PUBLICANDOSE", url: "" ) XCTAssertEqual(publishingManga.displayStatus, "En publicación") let finishedManga = Manga( slug: "test2", title: "Test", description: "Desc", genres: [], status: "FINALIZADO", url: "" ) XCTAssertEqual(finishedManga.displayStatus, "Finalizado") let pausedManga = Manga( slug: "test3", title: "Test", description: "Desc", genres: [], status: "EN_PAUSA", url: "" ) XCTAssertEqual(pausedManga.displayStatus, "En pausa") let waitingManga = Manga( slug: "test4", title: "Test", description: "Desc", genres: [], status: "EN_ESPERA", url: "" ) XCTAssertEqual(waitingManga.displayStatus, "En pausa") let unknownManga = Manga( slug: "test5", title: "Test", description: "Desc", genres: [], status: "UNKNOWN_STATUS", url: "" ) XCTAssertEqual(unknownManga.displayStatus, "UNKNOWN_STATUS") } func testMangaHashable() { let manga1 = Manga( slug: "test", title: "Test Manga", description: "Description", genres: ["Action"], status: "PUBLICANDOSE", url: "https://example.com", coverImage: nil ) let manga2 = Manga( slug: "test", title: "Different Title", description: "Different Description", genres: ["Drama"], status: "FINALIZADO", url: "https://different.com", coverImage: "cover.jpg" ) XCTAssertEqual(manga1, manga2) XCTAssertEqual(manga1.hashValue, manga2.hashValue) let set: Set = [manga1, manga2] XCTAssertEqual(set.count, 1, "Mangas with same slug should be considered equal") } // MARK: - Chapter Model Tests func testChapterInitialization() { let chapter = Chapter( number: 150, title: "The Final Battle", url: "https://manhwaweb.com/leer/solo-leveling/150", slug: "solo-leveling/150" ) XCTAssertEqual(chapter.id, 150) XCTAssertEqual(chapter.number, 150) XCTAssertEqual(chapter.title, "The Final Battle") XCTAssertEqual(chapter.isRead, false) XCTAssertEqual(chapter.isDownloaded, false) XCTAssertEqual(chapter.lastReadPage, 0) } func testChapterDisplayNumber() { let chapter = Chapter(number: 42, title: "Test", url: "", slug: "") XCTAssertEqual(chapter.displayNumber, "Capítulo 42") } func testChapterProgress() { var chapter = Chapter(number: 1, title: "Test", url: "", slug: "") XCTAssertEqual(chapter.progress, 0.0) chapter.lastReadPage = 5 XCTAssertEqual(chapter.progress, 5.0) chapter.lastReadPage = 100 XCTAssertEqual(chapter.progress, 100.0) } func testChapterCodableSerialization() { let chapter = Chapter( number: 50, title: "Chapter 50", url: "https://manhwaweb.com/leer/test/50", slug: "test/50", isRead: true, isDownloaded: true, lastReadPage: 25 ) // Test encoding do { let encoder = JSONEncoder() let jsonData = try encoder.encode(chapter) // Test decoding let decoder = JSONDecoder() let decodedChapter = try decoder.decode(Chapter.self, from: jsonData) XCTAssertEqual(chapter.number, decodedChapter.number) XCTAssertEqual(chapter.title, decodedChapter.title) XCTAssertEqual(chapter.url, decodedChapter.url) XCTAssertEqual(chapter.slug, decodedChapter.slug) XCTAssertEqual(chapter.isRead, decodedChapter.isRead) XCTAssertEqual(chapter.isDownloaded, decodedChapter.isDownloaded) XCTAssertEqual(chapter.lastReadPage, decodedChapter.lastReadPage) } catch { XCTFail("Chapter coding/decoding failed: \(error)") } } func testChapterHashable() { let chapter1 = Chapter( number: 10, title: "Chapter 10", url: "url1", slug: "slug1", isRead: true, isDownloaded: false, lastReadPage: 5 ) let chapter2 = Chapter( number: 10, title: "Different Title", url: "url2", slug: "slug2", isRead: false, isDownloaded: true, lastReadPage: 10 ) XCTAssertEqual(chapter1, chapter2) XCTAssertEqual(chapter1.hashValue, chapter2.hashValue) } // MARK: - MangaPage Model Tests func testMangaPageInitialization() { let page = MangaPage(url: "https://example.com/page1.jpg", index: 0) XCTAssertEqual(page.id, "https://example.com/page1.jpg") XCTAssertEqual(page.url, "https://example.com/page1.jpg") XCTAssertEqual(page.index, 0) XCTAssertEqual(page.isCached, false) } func testMangaPageThumbnailURL() { let page = MangaPage(url: "https://example.com/high-res.jpg", index: 5) XCTAssertEqual(page.thumbnailURL, "https://example.com/high-res.jpg") } func testMangaPageCodableSerialization() { let page = MangaPage( url: "https://example.com/page.jpg", index: 10, isCached: true ) do { let encoder = JSONEncoder() let jsonData = try encoder.encode(page) let decoder = JSONDecoder() let decodedPage = try decoder.decode(MangaPage.self, from: jsonData) XCTAssertEqual(page.url, decodedPage.url) XCTAssertEqual(page.index, decodedPage.index) XCTAssertEqual(page.isCached, decodedPage.isCached) } catch { XCTFail("MangaPage coding/decoding failed: \(error)") } } func testMangaPageHashable() { let page1 = MangaPage(url: "https://example.com/page.jpg", index: 0) let page2 = MangaPage(url: "https://example.com/page.jpg", index: 5, isCached: true) XCTAssertEqual(page1, page2) XCTAssertEqual(page1.hashValue, page2.hashValue) } // MARK: - ReadingProgress Model Tests func testReadingProgressInitialization() { let progress = ReadingProgress( mangaSlug: "solo-leveling", chapterNumber: 50, pageNumber: 10, timestamp: Date() ) XCTAssertEqual(progress.mangaSlug, "solo-leveling") XCTAssertEqual(progress.chapterNumber, 50) XCTAssertEqual(progress.pageNumber, 10) } func testReadingProgressIsCompleted() { let incompleteProgress = ReadingProgress( mangaSlug: "test", chapterNumber: 1, pageNumber: 3, timestamp: Date() ) XCTAssertFalse(incompleteProgress.isCompleted) let completedProgress = ReadingProgress( mangaSlug: "test", chapterNumber: 1, pageNumber: 6, timestamp: Date() ) XCTAssertTrue(completedProgress.isCompleted) let boundaryProgress = ReadingProgress( mangaSlug: "test", chapterNumber: 1, pageNumber: 5, timestamp: Date() ) XCTAssertFalse(boundaryProgress.isCompleted, "Exactly 5 pages should not be considered completed") } func testReadingProgressCodableSerialization() { let timestamp = Date(timeIntervalSince1970: 1609459200) // 2021-01-01 let progress = ReadingProgress( mangaSlug: "tower-of-god", chapterNumber: 500, pageNumber: 42, timestamp: timestamp ) do { let encoder = JSONEncoder() let jsonData = try encoder.encode(progress) let decoder = JSONDecoder() let decodedProgress = try decoder.decode(ReadingProgress.self, from: jsonData) XCTAssertEqual(progress.mangaSlug, decodedProgress.mangaSlug) XCTAssertEqual(progress.chapterNumber, decodedProgress.chapterNumber) XCTAssertEqual(progress.pageNumber, decodedProgress.pageNumber) // Compare timestamp with tolerance for encoding/decoding precision let timeDifference = abs(progress.timestamp.timeIntervalSince(decodedProgress.timestamp)) XCTAssertLessThan(timeDifference, 0.001, "Timestamps should match within milliseconds") } catch { XCTFail("ReadingProgress coding/decoding failed: \(error)") } } // MARK: - DownloadedChapter Model Tests func testDownloadedChapterInitialization() { let pages = [ MangaPage(url: "page1.jpg", index: 0), MangaPage(url: "page2.jpg", index: 1) ] let downloadedChapter = DownloadedChapter( mangaSlug: "solo-leveling", mangaTitle: "Solo Leveling", chapterNumber: 100, pages: pages, downloadedAt: Date(), totalSize: 1024000 ) XCTAssertEqual(downloadedChapter.id, "solo-leveling-chapter100") XCTAssertEqual(downloadedChapter.mangaSlug, "solo-leveling") XCTAssertEqual(downloadedChapter.pages.count, 2) } func testDownloadedChapterDisplayTitle() { let chapter = DownloadedChapter( mangaSlug: "test", mangaTitle: "Test Manga", chapterNumber: 25, pages: [], downloadedAt: Date() ) XCTAssertEqual(chapter.displayTitle, "Test Manga - Capítulo 25") } func testDownloadedChapterCodableSerialization() { let pages = [ MangaPage(url: "page1.jpg", index: 0, isCached: true), MangaPage(url: "page2.jpg", index: 1, isCached: true) ] let downloadDate = Date(timeIntervalSince1970: 1609459200) let downloadedChapter = DownloadedChapter( mangaSlug: "test-manga", mangaTitle: "Test Manga", chapterNumber: 10, pages: pages, downloadedAt: downloadDate, totalSize: 2048000 ) do { let encoder = JSONEncoder() let jsonData = try encoder.encode(downloadedChapter) let decoder = JSONDecoder() let decodedChapter = try decoder.decode(DownloadedChapter.self, from: jsonData) XCTAssertEqual(downloadedChapter.mangaSlug, decodedChapter.mangaSlug) XCTAssertEqual(downloadedChapter.mangaTitle, decodedChapter.mangaTitle) XCTAssertEqual(downloadedChapter.chapterNumber, decodedChapter.chapterNumber) XCTAssertEqual(downloadedChapter.pages.count, decodedChapter.pages.count) XCTAssertEqual(downloadedChapter.totalSize, decodedChapter.totalSize) } catch { XCTFail("DownloadedChapter coding/decoding failed: \(error)") } } // MARK: - Edge Cases Tests func testMangaWithEmptyGenres() { let manga = Manga( slug: "test", title: "Test", description: "Description", genres: [], status: "PUBLICANDOSE", url: "url" ) XCTAssertTrue(manga.genres.isEmpty) XCTAssertEqual(manga.genres.count, 0) } func testMangaWithNilCoverImage() { let manga1 = Manga( slug: "test", title: "Test", description: "Desc", genres: [], status: "PUBLICANDOSE", url: "url", coverImage: nil ) XCTAssertNil(manga1.coverImage) let manga2 = Manga( slug: "test", title: "Test", description: "Desc", genres: [], status: "PUBLICANDOSE", url: "url", coverImage: "" ) XCTAssertNil(manga2.coverImage, "Empty string should be treated as nil") } func testChapterWithZeroNumber() { let chapter = Chapter(number: 0, title: "Prologue", url: "url", slug: "slug") XCTAssertEqual(chapter.id, 0) XCTAssertEqual(chapter.displayNumber, "Capítulo 0") } func testChapterWithLargePageNumber() { var chapter = Chapter(number: 1, title: "Test", url: "url", slug: "slug") chapter.lastReadPage = 10000 XCTAssertEqual(chapter.progress, 10000.0) } func testMangaPageWithNegativeIndex() { let page = MangaPage(url: "test.jpg", index: -1) XCTAssertEqual(page.index, -1) // Edge case: negative indices should still work } func testReadingProgressWithZeroPages() { let progress = ReadingProgress( mangaSlug: "test", chapterNumber: 1, pageNumber: 0, timestamp: Date() ) XCTAssertFalse(progress.isCompleted) } func testDownloadedChapterWithEmptyPages() { let chapter = DownloadedChapter( mangaSlug: "test", mangaTitle: "Test", chapterNumber: 1, pages: [], downloadedAt: Date() ) XCTAssertTrue(chapter.pages.isEmpty) XCTAssertEqual(chapter.totalSize, 0) } // MARK: - Performance Tests func testMangaEncodingPerformance() { let manga = Manga( slug: "test-manga-with-very-long-slug", title: "Test Manga Title That Is Quite Long", description: "This is a very long description that contains a lot of text about the manga plot and characters", genres: ["Action", "Adventure", "Comedy", "Drama", "Fantasy", "Horror", "Mystery", "Romance", "Sci-Fi", "Thriller"], status: "PUBLICANDOSE", url: "https://manhwaweb.com/manga/test-manga", coverImage: "https://example.com/cover.jpg" ) measure { for _ in 0..<1000 { _ = try? JSONEncoder().encode(manga) } } } func testChapterArrayEqualityPerformance() { var chapters: [Chapter] = [] for i in 0..<1000 { chapters.append(Chapter(number: i, title: "Chapter \(i)", url: "url\(i)", slug: "slug\(i)")) } let set = Set(chapters) measure { _ = set.contains(chapters[500]) } } }