245 lines
7.7 KiB
Swift
245 lines
7.7 KiB
Swift
import Testing
|
||
@testable import bookstax
|
||
import Foundation
|
||
|
||
// MARK: - Helpers
|
||
|
||
private func makePageDTO(markdown: String? = "# Hello", tags: [TagDTO] = []) -> PageDTO {
|
||
PageDTO(
|
||
id: 42,
|
||
bookId: 1,
|
||
chapterId: nil,
|
||
name: "Test Page",
|
||
slug: "test-page",
|
||
html: nil,
|
||
markdown: markdown,
|
||
priority: 0,
|
||
draftStatus: false,
|
||
tags: tags,
|
||
createdAt: Date(),
|
||
updatedAt: Date()
|
||
)
|
||
}
|
||
|
||
// MARK: - Initialisation
|
||
|
||
@Suite("PageEditorViewModel – Initialisation")
|
||
struct PageEditorViewModelInitTests {
|
||
|
||
@Test("Create mode starts with empty title and content")
|
||
func createModeDefaults() {
|
||
let vm = PageEditorViewModel(mode: .create(bookId: 1))
|
||
#expect(vm.title.isEmpty)
|
||
#expect(vm.markdownContent.isEmpty)
|
||
#expect(vm.tags.isEmpty)
|
||
#expect(vm.isHtmlOnlyPage == false)
|
||
}
|
||
|
||
@Test("Edit mode populates title and markdown from page")
|
||
func editModePopulates() {
|
||
let page = makePageDTO(markdown: "## Content")
|
||
let vm = PageEditorViewModel(mode: .edit(page: page))
|
||
#expect(vm.title == "Test Page")
|
||
#expect(vm.markdownContent == "## Content")
|
||
#expect(vm.isHtmlOnlyPage == false)
|
||
}
|
||
|
||
@Test("Edit mode with nil markdown sets isHtmlOnlyPage")
|
||
func htmlOnlyPage() {
|
||
let page = makePageDTO(markdown: nil)
|
||
let vm = PageEditorViewModel(mode: .edit(page: page))
|
||
#expect(vm.isHtmlOnlyPage == true)
|
||
#expect(vm.markdownContent.isEmpty)
|
||
}
|
||
|
||
@Test("Edit mode with existing tags loads them")
|
||
func editModeLoadsTags() {
|
||
let tags = [TagDTO(name: "topic", value: "swift", order: 0)]
|
||
let page = makePageDTO(tags: tags)
|
||
let vm = PageEditorViewModel(mode: .edit(page: page))
|
||
#expect(vm.tags.count == 1)
|
||
#expect(vm.tags.first?.name == "topic")
|
||
}
|
||
}
|
||
|
||
// MARK: - hasUnsavedChanges
|
||
|
||
@Suite("PageEditorViewModel – hasUnsavedChanges")
|
||
struct PageEditorViewModelUnsavedTests {
|
||
|
||
@Test("No changes after init → hasUnsavedChanges is false")
|
||
func noChangesAfterInit() {
|
||
let vm = PageEditorViewModel(mode: .create(bookId: 1))
|
||
#expect(vm.hasUnsavedChanges == false)
|
||
}
|
||
|
||
@Test("Changing title → hasUnsavedChanges is true")
|
||
func titleChange() {
|
||
let vm = PageEditorViewModel(mode: .create(bookId: 1))
|
||
vm.title = "New Title"
|
||
#expect(vm.hasUnsavedChanges == true)
|
||
}
|
||
|
||
@Test("Changing markdownContent → hasUnsavedChanges is true")
|
||
func contentChange() {
|
||
let vm = PageEditorViewModel(mode: .create(bookId: 1))
|
||
vm.markdownContent = "Some text"
|
||
#expect(vm.hasUnsavedChanges == true)
|
||
}
|
||
|
||
@Test("Adding a tag → hasUnsavedChanges is true")
|
||
func tagAddition() {
|
||
let page = makePageDTO()
|
||
let vm = PageEditorViewModel(mode: .edit(page: page))
|
||
vm.addTag(name: "new-tag")
|
||
#expect(vm.hasUnsavedChanges == true)
|
||
}
|
||
|
||
@Test("Restoring original values → hasUnsavedChanges is false again")
|
||
func revertChanges() {
|
||
let vm = PageEditorViewModel(mode: .create(bookId: 1))
|
||
vm.title = "Changed"
|
||
vm.title = ""
|
||
#expect(vm.hasUnsavedChanges == false)
|
||
}
|
||
}
|
||
|
||
// MARK: - isSaveDisabled
|
||
|
||
@Suite("PageEditorViewModel – isSaveDisabled")
|
||
struct PageEditorViewModelSaveDisabledTests {
|
||
|
||
@Test("Empty title disables save in create mode")
|
||
func emptyTitleCreate() {
|
||
let vm = PageEditorViewModel(mode: .create(bookId: 1))
|
||
vm.markdownContent = "Some content"
|
||
#expect(vm.isSaveDisabled == true)
|
||
}
|
||
|
||
@Test("Empty content disables save in create mode even if title is set")
|
||
func emptyContentCreate() {
|
||
let vm = PageEditorViewModel(mode: .create(bookId: 1))
|
||
vm.title = "My Page"
|
||
vm.markdownContent = ""
|
||
#expect(vm.isSaveDisabled == true)
|
||
}
|
||
|
||
@Test("Whitespace-only content disables save in create mode")
|
||
func whitespaceContentCreate() {
|
||
let vm = PageEditorViewModel(mode: .create(bookId: 1))
|
||
vm.title = "My Page"
|
||
vm.markdownContent = " \n "
|
||
#expect(vm.isSaveDisabled == true)
|
||
}
|
||
|
||
@Test("Title and content both set enables save in create mode")
|
||
func validCreate() {
|
||
let vm = PageEditorViewModel(mode: .create(bookId: 1))
|
||
vm.title = "My Page"
|
||
vm.markdownContent = "Hello world"
|
||
#expect(vm.isSaveDisabled == false)
|
||
}
|
||
|
||
@Test("Edit mode only requires title – empty content is allowed")
|
||
func editOnlyNeedsTitle() {
|
||
let page = makePageDTO(markdown: nil)
|
||
let vm = PageEditorViewModel(mode: .edit(page: page))
|
||
vm.title = "Existing Page"
|
||
vm.markdownContent = ""
|
||
#expect(vm.isSaveDisabled == false)
|
||
}
|
||
|
||
@Test("Empty title disables save in edit mode")
|
||
func emptyTitleEdit() {
|
||
let page = makePageDTO()
|
||
let vm = PageEditorViewModel(mode: .edit(page: page))
|
||
vm.title = ""
|
||
#expect(vm.isSaveDisabled == true)
|
||
}
|
||
}
|
||
|
||
// MARK: - Tag Management
|
||
|
||
@Suite("PageEditorViewModel – Tags")
|
||
struct PageEditorViewModelTagTests {
|
||
|
||
@Test("addTag appends a new tag")
|
||
func addTag() {
|
||
let vm = PageEditorViewModel(mode: .create(bookId: 1))
|
||
vm.addTag(name: "status", value: "draft")
|
||
#expect(vm.tags.count == 1)
|
||
#expect(vm.tags.first?.name == "status")
|
||
#expect(vm.tags.first?.value == "draft")
|
||
}
|
||
|
||
@Test("addTag trims whitespace from name and value")
|
||
func addTagTrims() {
|
||
let vm = PageEditorViewModel(mode: .create(bookId: 1))
|
||
vm.addTag(name: " topic ", value: " swift ")
|
||
#expect(vm.tags.first?.name == "topic")
|
||
#expect(vm.tags.first?.value == "swift")
|
||
}
|
||
|
||
@Test("addTag with empty name after trimming is ignored")
|
||
func addTagEmptyNameIgnored() {
|
||
let vm = PageEditorViewModel(mode: .create(bookId: 1))
|
||
vm.addTag(name: " ")
|
||
#expect(vm.tags.isEmpty)
|
||
}
|
||
|
||
@Test("addTag prevents duplicate (same name + value) entries")
|
||
func addTagNoDuplicates() {
|
||
let vm = PageEditorViewModel(mode: .create(bookId: 1))
|
||
vm.addTag(name: "lang", value: "swift")
|
||
vm.addTag(name: "lang", value: "swift")
|
||
#expect(vm.tags.count == 1)
|
||
}
|
||
|
||
@Test("addTag allows same name with different value")
|
||
func addTagSameNameDifferentValue() {
|
||
let vm = PageEditorViewModel(mode: .create(bookId: 1))
|
||
vm.addTag(name: "env", value: "dev")
|
||
vm.addTag(name: "env", value: "prod")
|
||
#expect(vm.tags.count == 2)
|
||
}
|
||
|
||
@Test("removeTag removes the matching tag by id")
|
||
func removeTag() {
|
||
let vm = PageEditorViewModel(mode: .create(bookId: 1))
|
||
vm.addTag(name: "remove-me", value: "yes")
|
||
let tag = vm.tags[0]
|
||
vm.removeTag(tag)
|
||
#expect(vm.tags.isEmpty)
|
||
}
|
||
|
||
@Test("removeTag does not remove non-matching tags")
|
||
func removeTagKeepsOthers() {
|
||
let vm = PageEditorViewModel(mode: .create(bookId: 1))
|
||
vm.addTag(name: "keep", value: "")
|
||
vm.addTag(name: "remove", value: "")
|
||
let toRemove = vm.tags.first { $0.name == "remove" }!
|
||
vm.removeTag(toRemove)
|
||
#expect(vm.tags.count == 1)
|
||
#expect(vm.tags.first?.name == "keep")
|
||
}
|
||
}
|
||
|
||
// MARK: - uploadTargetPageId
|
||
|
||
@Suite("PageEditorViewModel – uploadTargetPageId")
|
||
struct PageEditorViewModelUploadIDTests {
|
||
|
||
@Test("Create mode returns 0 as upload target")
|
||
func createModeUploadTarget() {
|
||
let vm = PageEditorViewModel(mode: .create(bookId: 5))
|
||
#expect(vm.uploadTargetPageId == 0)
|
||
}
|
||
|
||
@Test("Edit mode returns the existing page id")
|
||
func editModeUploadTarget() {
|
||
let page = makePageDTO()
|
||
let vm = PageEditorViewModel(mode: .edit(page: page))
|
||
#expect(vm.uploadTargetPageId == 42)
|
||
}
|
||
}
|