import Foundation import Testing @testable import Kanban /// A window ref is two things at once — the value `WindowGroup(for:)` keys its windows on, and the /// argument the host is built from — so it has exactly two promises: it survives the round trip /// through a scene's archive, and two refs naming one thing are *equal*. Both are pinned here, /// because a failure of either is invisible until the app opens a second window for something that /// already has one. @Suite("Window refs") struct WindowRefsTests { // MARK: Codable @Test("Both refs round-trip through JSON unchanged") func refsRoundTripThroughCoding() throws { let board = BoardWindowRef(path: "/Users/x/Boards/Work.kanban") let card = CardWindowRef(boardPath: board.path, cardID: "55555555-5555-4555-8555-555555555555") let encoder = JSONEncoder() let decoder = JSONDecoder() #expect(try decoder.decode(BoardWindowRef.self, from: encoder.encode(board)) == board) let decodedCard = try decoder.decode(CardWindowRef.self, from: encoder.encode(card)) #expect(decodedCard == card) #expect(decodedCard.cardID == card.cardID, "the folder's exact spelling survives, not just its value") #expect(decodedCard.boardPath == board.path) } @Test("A board ref and a URL agree in both directions") func boardRefAndURLAgree() { let url = URL(fileURLWithPath: "/Users/x/Boards/Work.kanban", isDirectory: true) let ref = BoardWindowRef(url: url) #expect(ref.path == url.path) #expect(ref.url.path == url.path) } // MARK: Identity @Test("A card ref's id compares by UUID value, not by spelling") func cardRefFoldsTheCaseOfItsCardID() { let board = "/Users/x/Boards/Work.kanban" let lower = CardWindowRef(boardPath: board, cardID: "55555555-5555-4555-8555-555555555555") let upper = CardWindowRef(boardPath: board, cardID: "55555555-5555-4555-8555-555555555555".uppercased()) // The consequence that matters: `WindowGroup(for:)` keys on this equality, so a card whose // folder is spelled in caps focuses the window the lowercase spelling already opened instead // of opening a second one for the same card. #expect(lower == upper) #expect(lower.hashValue == upper.hashValue) #expect(Set([lower, upper]).count == 1) // And `rawValue` is untouched by any of that — it is what builds URLs. #expect(upper.cardID == "55555555-5555-4555-8555-555555555555".uppercased()) #expect(upper.cardIdentity == lower.cardIdentity) } @Test("The board half of a card ref is compared verbatim") func cardRefsOnDifferentBoardsAreDifferent() { let card = "55555555-5555-4555-8555-555555555555" let here = CardWindowRef(boardPath: "/Users/x/Boards/Work.kanban", cardID: card) let there = CardWindowRef(boardPath: "/Users/x/Boards/Home.kanban", cardID: card) // The cross-board move rule in one assertion: the card's UUID travels with it, but the key // that named its window does not — so the window dismisses exactly like a delete. #expect(here != there) #expect(here.board == BoardWindowRef(path: "/Users/x/Boards/Work.kanban")) #expect(here.boardURL.path == "/Users/x/Boards/Work.kanban") } @Test("A card ref built from an ItemID keeps the id's exact spelling") func cardRefFromItemIDKeepsRawValue() { let board = BoardWindowRef(path: "/Users/x/Boards/Work.kanban") let id = ItemID(rawValue: "AAAAAAAA-AAAA-4AAA-8AAA-AAAAAAAAAAAA") let ref = CardWindowRef(board: board, cardID: id) #expect(ref.cardID == id.rawValue) #expect(ref == CardWindowRef(boardPath: board.path, cardID: id.rawValue.lowercased())) } }