import Foundation import Testing @testable import bookstax // MARK: - Mock final class MockShareAPIService: ShareAPIServiceProtocol, @unchecked Sendable { var shelvesToReturn: [ShelfSummary] = [] var booksToReturn: [BookSummary] = [] var chaptersToReturn: [ChapterSummary] = [] var errorToThrow: Error? func fetchShelves() async throws -> [ShelfSummary] { if let error = errorToThrow { throw error } return shelvesToReturn } func fetchBooks(shelfId: Int) async throws -> [BookSummary] { if let error = errorToThrow { throw error } return booksToReturn } func fetchChapters(bookId: Int) async throws -> [ChapterSummary] { if let error = errorToThrow { throw error } return chaptersToReturn } func createPage(bookId: Int, chapterId: Int?, title: String, markdown: String) async throws -> PageResult { if let error = errorToThrow { throw error } return PageResult(id: 42, name: title) } } // MARK: - Tests @Suite("ShareViewModel") @MainActor struct ShareViewModelTests { private func makeDefaults() -> UserDefaults { let name = "test.bookstax.shareext.\(UUID().uuidString)" let defaults = UserDefaults(suiteName: name)! defaults.removePersistentDomain(forName: name) return defaults } // MARK: 1. Shelves are loaded on start @Test("Beim Start werden alle Regale geladen") func shelvesLoadOnStart() async throws { let mock = MockShareAPIService() mock.shelvesToReturn = [ ShelfSummary(id: 1, name: "Regal A", slug: "a"), ShelfSummary(id: 2, name: "Regal B", slug: "b") ] let vm = ShareViewModel(sharedText: "Testinhalt", apiService: mock, defaults: makeDefaults()) await vm.loadShelves() #expect(vm.shelves.count == 2) #expect(vm.shelves[0].name == "Regal A") #expect(vm.isLoading == false) #expect(vm.errorMessage == nil) } // MARK: 2. Selecting a shelf loads its books @Test("Shelf-Auswahl lädt Bücher nach") func selectingShelfLoadsBooksAsync() async throws { let mock = MockShareAPIService() mock.booksToReturn = [ BookSummary(id: 100, name: "Buch 1", slug: "b1"), BookSummary(id: 101, name: "Buch 2", slug: "b2") ] let vm = ShareViewModel(sharedText: "Test", apiService: mock, defaults: makeDefaults()) await vm.selectShelf(ShelfSummary(id: 10, name: "Regal X", slug: "x")) #expect(vm.selectedShelf?.id == 10) #expect(vm.books.count == 2) #expect(vm.isLoading == false) } // MARK: 3. Last shelf/book is restored from UserDefaults @Test("Gespeicherte Shelf- und Book-IDs werden beim Start wiederhergestellt") func lastSelectionIsRestored() async throws { let mock = MockShareAPIService() mock.shelvesToReturn = [ShelfSummary(id: 5, name: "Gespeichertes Regal", slug: "saved")] mock.booksToReturn = [BookSummary(id: 50, name: "Gespeichertes Buch", slug: "saved-b")] let defaults = makeDefaults() defaults.set(5, forKey: "shareExtension.lastShelfID") defaults.set(50, forKey: "shareExtension.lastBookID") let vm = ShareViewModel(sharedText: "Test", apiService: mock, defaults: defaults) await vm.loadShelves() #expect(vm.selectedShelf?.id == 5, "Letztes Regal soll wiederhergestellt sein") #expect(vm.selectedBook?.id == 50, "Letztes Buch soll wiederhergestellt sein") } // MARK: 4. Title auto-populated from first line @Test("Seitentitel wird aus erster Zeile des Textes befüllt") func titleAutoPopulatedFromFirstLine() { let vm = ShareViewModel(sharedText: "Erste Zeile\nZweite Zeile", apiService: MockShareAPIService()) #expect(vm.pageTitle == "Erste Zeile") } // MARK: 5. Save page sets isSaved @Test("Seite speichern setzt isSaved auf true") func savePageSetsisSaved() async throws { let mock = MockShareAPIService() let vm = ShareViewModel(sharedText: "Inhalt", apiService: mock) vm.pageTitle = "Mein Titel" vm.selectedBook = BookSummary(id: 1, name: "Buch", slug: "buch") await vm.savePage() #expect(vm.isSaved == true) #expect(vm.errorMessage == nil) } // MARK: 6. isSaveDisabled logic @Test("Speichern ist deaktiviert ohne Titel oder Buch") func isSaveDisabledWithoutTitleOrBook() { let vm = ShareViewModel(sharedText: "Test", apiService: MockShareAPIService()) vm.pageTitle = "" #expect(vm.isSaveDisabled == true) vm.pageTitle = "Titel" #expect(vm.isSaveDisabled == true) // no book yet vm.selectedBook = BookSummary(id: 1, name: "Buch", slug: "b") #expect(vm.isSaveDisabled == false) } }