Unit tests, Lokalisierung, ShareExtension
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
import Testing
|
||||
@testable import bookstax
|
||||
// Note: ShareViewModel and its dependencies live in BookStaxShareExtension,
|
||||
// which is a separate target. These tests are compiled against the extension
|
||||
// module. If you build the tests via the main scheme, add BookStaxShareExtension
|
||||
// sources to the bookstaxTests target membership as needed.
|
||||
//
|
||||
// The tests below use a MockShareAPIService injected at init time so no
|
||||
// network or Keychain access is required.
|
||||
|
||||
// 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 {
|
||||
|
||||
// Isolated UserDefaults suite so tests don't pollute each other.
|
||||
private func makeDefaults() -> UserDefaults {
|
||||
let suiteName = "test.bookstax.shareext.\(UUID().uuidString)"
|
||||
let defaults = UserDefaults(suiteName: suiteName)!
|
||||
defaults.removePersistentDomain(forName: suiteName)
|
||||
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.shelves[1].name == "Regal B")
|
||||
#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.shelvesToReturn = [ShelfSummary(id: 10, name: "Regal X", slug: "x")]
|
||||
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.books[0].name == "Buch 1")
|
||||
#expect(vm.isLoading == false)
|
||||
}
|
||||
|
||||
// MARK: - 3. Last shelf / book are 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: - Additional: title auto-populated from first line
|
||||
|
||||
@Test("Seitentitel wird aus erster Zeile des geteilten Textes befüllt")
|
||||
func titleAutoPopulatedFromFirstLine() {
|
||||
let text = "Erste Zeile\nZweite Zeile\nDritte Zeile"
|
||||
let vm = ShareViewModel(
|
||||
sharedText: text,
|
||||
apiService: MockShareAPIService()
|
||||
)
|
||||
#expect(vm.pageTitle == "Erste Zeile")
|
||||
}
|
||||
|
||||
// MARK: - Additional: save page sets isSaved
|
||||
|
||||
@Test("Seite speichern setzt isSaved auf true")
|
||||
func savePageSetsisSaved() async throws {
|
||||
let mock = MockShareAPIService()
|
||||
let vm = ShareViewModel(
|
||||
sharedText: "Inhalt der Seite",
|
||||
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: - Additional: isSaveDisabled logic
|
||||
|
||||
@Test("Speichern ist deaktiviert ohne Titel oder Buch")
|
||||
func isSaveDisabledWithoutTitleOrBook() {
|
||||
let vm = ShareViewModel(sharedText: "Test", apiService: MockShareAPIService())
|
||||
|
||||
// No title, no book
|
||||
vm.pageTitle = ""
|
||||
#expect(vm.isSaveDisabled == true)
|
||||
|
||||
// Title but no book
|
||||
vm.pageTitle = "Titel"
|
||||
#expect(vm.isSaveDisabled == true)
|
||||
|
||||
// Title + book → enabled
|
||||
vm.selectedBook = BookSummary(id: 1, name: "Buch", slug: "b")
|
||||
#expect(vm.isSaveDisabled == false)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user