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