Nudge-Screen
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
import Testing
|
||||
@testable import bookstax
|
||||
import Foundation
|
||||
|
||||
// MARK: - TagDTO
|
||||
|
||||
@Suite("TagDTO")
|
||||
struct TagDTOTests {
|
||||
|
||||
@Test("id is composed of name and value")
|
||||
func idFormat() {
|
||||
let tag = TagDTO(name: "status", value: "published", order: 0)
|
||||
#expect(tag.id == "status:published")
|
||||
}
|
||||
|
||||
@Test("id with empty value still includes colon separator")
|
||||
func idEmptyValue() {
|
||||
let tag = TagDTO(name: "featured", value: "", order: 0)
|
||||
#expect(tag.id == "featured:")
|
||||
}
|
||||
|
||||
@Test("Two tags with same name/value have equal ids")
|
||||
func duplicateIds() {
|
||||
let t1 = TagDTO(name: "env", value: "prod", order: 0)
|
||||
let t2 = TagDTO(name: "env", value: "prod", order: 1)
|
||||
#expect(t1.id == t2.id)
|
||||
}
|
||||
|
||||
@Test("Tags decode from JSON correctly")
|
||||
func decodeFromJSON() throws {
|
||||
let json = """
|
||||
{"name":"topic","value":"swift","order":3}
|
||||
""".data(using: .utf8)!
|
||||
let tag = try JSONDecoder().decode(TagDTO.self, from: json)
|
||||
#expect(tag.name == "topic")
|
||||
#expect(tag.value == "swift")
|
||||
#expect(tag.order == 3)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - PageDTO
|
||||
|
||||
private let iso8601Formatter: ISO8601DateFormatter = {
|
||||
let f = ISO8601DateFormatter()
|
||||
f.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
||||
return f
|
||||
}()
|
||||
|
||||
private func pageJSON(
|
||||
slug: String? = nil,
|
||||
priority: Int? = nil,
|
||||
draft: Bool? = nil,
|
||||
tags: String? = nil,
|
||||
markdown: String? = nil
|
||||
) -> Data {
|
||||
var fields: [String] = [
|
||||
#""id":1,"book_id":10,"name":"Test Page""#,
|
||||
#""created_at":"2024-01-01T00:00:00.000Z","updated_at":"2024-01-01T00:00:00.000Z""#
|
||||
]
|
||||
if let s = slug { fields.append("\"slug\":\"\(s)\"") }
|
||||
if let p = priority { fields.append("\"priority\":\(p)") }
|
||||
if let d = draft { fields.append("\"draft\":\(d)") }
|
||||
if let t = tags { fields.append("\"tags\":[\(t)]") }
|
||||
if let m = markdown { fields.append("\"markdown\":\"\(m)\"") }
|
||||
return ("{\(fields.joined(separator: ","))}").data(using: .utf8)!
|
||||
}
|
||||
|
||||
private func makeDecoder() -> JSONDecoder {
|
||||
// Mirrors the decoder used in BookStackAPI
|
||||
let decoder = JSONDecoder()
|
||||
let formatter = ISO8601DateFormatter()
|
||||
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
||||
decoder.dateDecodingStrategy = .custom { decoder in
|
||||
let container = try decoder.singleValueContainer()
|
||||
let str = try container.decode(String.self)
|
||||
if let date = formatter.date(from: str) { return date }
|
||||
// Fallback without fractional seconds
|
||||
let fallback = ISO8601DateFormatter()
|
||||
if let date = fallback.date(from: str) { return date }
|
||||
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot decode date: \(str)")
|
||||
}
|
||||
return decoder
|
||||
}
|
||||
|
||||
@Suite("PageDTO – JSON Decoding")
|
||||
struct PageDTOTests {
|
||||
|
||||
@Test("Minimal JSON (no optional fields) decodes with defaults")
|
||||
func minimalDecoding() throws {
|
||||
let data = pageJSON()
|
||||
let page = try makeDecoder().decode(PageDTO.self, from: data)
|
||||
#expect(page.id == 1)
|
||||
#expect(page.bookId == 10)
|
||||
#expect(page.name == "Test Page")
|
||||
#expect(page.slug == "") // defaults to ""
|
||||
#expect(page.priority == 0) // defaults to 0
|
||||
#expect(page.draftStatus == false) // defaults to false
|
||||
#expect(page.tags.isEmpty) // defaults to []
|
||||
#expect(page.markdown == nil)
|
||||
#expect(page.html == nil)
|
||||
#expect(page.chapterId == nil)
|
||||
}
|
||||
|
||||
@Test("slug is decoded when present")
|
||||
func slugDecoded() throws {
|
||||
let data = pageJSON(slug: "my-page")
|
||||
let page = try makeDecoder().decode(PageDTO.self, from: data)
|
||||
#expect(page.slug == "my-page")
|
||||
}
|
||||
|
||||
@Test("priority is decoded when present")
|
||||
func priorityDecoded() throws {
|
||||
let data = pageJSON(priority: 5)
|
||||
let page = try makeDecoder().decode(PageDTO.self, from: data)
|
||||
#expect(page.priority == 5)
|
||||
}
|
||||
|
||||
@Test("draft true decodes correctly")
|
||||
func draftDecoded() throws {
|
||||
let data = pageJSON(draft: true)
|
||||
let page = try makeDecoder().decode(PageDTO.self, from: data)
|
||||
#expect(page.draftStatus == true)
|
||||
}
|
||||
|
||||
@Test("tags array is decoded when present")
|
||||
func tagsDecoded() throws {
|
||||
let tagJSON = #"{"name":"lang","value":"swift","order":0}"#
|
||||
let data = pageJSON(tags: tagJSON)
|
||||
let page = try makeDecoder().decode(PageDTO.self, from: data)
|
||||
#expect(page.tags.count == 1)
|
||||
#expect(page.tags.first?.name == "lang")
|
||||
}
|
||||
|
||||
@Test("markdown is decoded when present")
|
||||
func markdownDecoded() throws {
|
||||
let data = pageJSON(markdown: "# Hello")
|
||||
let page = try makeDecoder().decode(PageDTO.self, from: data)
|
||||
#expect(page.markdown == "# Hello")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SearchResultDTO
|
||||
|
||||
@Suite("SearchResultDTO – JSON Decoding")
|
||||
struct SearchResultDTOTests {
|
||||
|
||||
private func resultJSON(withTags: Bool = false) -> Data {
|
||||
let tags = withTags ? #","tags":[{"name":"topic","value":"swift","order":0}]"# : ""
|
||||
return """
|
||||
{"id":7,"name":"Swift Basics","slug":"swift-basics","type":"page",
|
||||
"url":"https://example.com/books/1/page/7","preview":"An intro to Swift"\(tags)}
|
||||
""".data(using: .utf8)!
|
||||
}
|
||||
|
||||
@Test("Result with no tags field defaults to empty array")
|
||||
func defaultsEmptyTags() throws {
|
||||
let result = try JSONDecoder().decode(SearchResultDTO.self, from: resultJSON())
|
||||
#expect(result.tags.isEmpty)
|
||||
}
|
||||
|
||||
@Test("Result with tags decodes them correctly")
|
||||
func decodesTagsWhenPresent() throws {
|
||||
let result = try JSONDecoder().decode(SearchResultDTO.self, from: resultJSON(withTags: true))
|
||||
#expect(result.tags.count == 1)
|
||||
}
|
||||
|
||||
@Test("All fields decode correctly")
|
||||
func allFieldsDecode() throws {
|
||||
let result = try JSONDecoder().decode(SearchResultDTO.self, from: resultJSON())
|
||||
#expect(result.id == 7)
|
||||
#expect(result.name == "Swift Basics")
|
||||
#expect(result.slug == "swift-basics")
|
||||
#expect(result.type == .page)
|
||||
#expect(result.preview == "An intro to Swift")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SearchResultDTO.ContentType
|
||||
|
||||
@Suite("SearchResultDTO.ContentType")
|
||||
struct ContentTypeTests {
|
||||
|
||||
@Test("All content types have non-empty systemImage")
|
||||
func systemImagesNonEmpty() {
|
||||
for type_ in SearchResultDTO.ContentType.allCases {
|
||||
#expect(!type_.systemImage.isEmpty)
|
||||
}
|
||||
}
|
||||
|
||||
@Test("Page system image is doc.text")
|
||||
func pageSystemImage() {
|
||||
#expect(SearchResultDTO.ContentType.page.systemImage == "doc.text")
|
||||
}
|
||||
|
||||
@Test("Book system image is book.closed")
|
||||
func bookSystemImage() {
|
||||
#expect(SearchResultDTO.ContentType.book.systemImage == "book.closed")
|
||||
}
|
||||
|
||||
@Test("Chapter system image is list.bullet.rectangle")
|
||||
func chapterSystemImage() {
|
||||
#expect(SearchResultDTO.ContentType.chapter.systemImage == "list.bullet.rectangle")
|
||||
}
|
||||
|
||||
@Test("Shelf system image is books.vertical")
|
||||
func shelfSystemImage() {
|
||||
#expect(SearchResultDTO.ContentType.shelf.systemImage == "books.vertical")
|
||||
}
|
||||
|
||||
@Test("ContentType decodes from raw string value")
|
||||
func rawValueDecoding() throws {
|
||||
let data = #""page""#.data(using: .utf8)!
|
||||
let type_ = try JSONDecoder().decode(SearchResultDTO.ContentType.self, from: data)
|
||||
#expect(type_ == .page)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - PaginatedResponse
|
||||
|
||||
@Suite("PaginatedResponse – Decoding")
|
||||
struct PaginatedResponseTests {
|
||||
|
||||
@Test("Paginated shelf response decodes total and data")
|
||||
func paginatedDecoding() throws {
|
||||
let json = """
|
||||
{"data":[{"id":1,"name":"My Shelf","slug":"my-shelf","description":"",
|
||||
"created_at":"2024-01-01T00:00:00.000Z","updated_at":"2024-01-01T00:00:00.000Z"}],
|
||||
"total":1}
|
||||
""".data(using: .utf8)!
|
||||
let decoded = try makeDecoder().decode(PaginatedResponse<ShelfDTO>.self, from: json)
|
||||
#expect(decoded.total == 1)
|
||||
#expect(decoded.data.count == 1)
|
||||
#expect(decoded.data.first?.name == "My Shelf")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user