Files
bookstax/ShareViewModelTests.swift

149 lines
5.0 KiB
Swift

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)
}
}