Model facts off the live snapshot (Lanes, Cards with the trash's freight as a quiet tail, Attachments — the welcome-count live-only rule) and disk facts off one background whole-folder walk (File, Files, Size, Created, Modified — .git and .trash included, so the rows agree with Finder's Get Info), with a Reveal in Finder link as the door to the folder the rows describe. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
153 lines
6.3 KiB
Swift
153 lines
6.3 KiB
Swift
import Foundation
|
|
import Testing
|
|
@testable import Kanban
|
|
|
|
/// **The Info tab's two seams** (03-board-ui.md § Board popover ▸ Info tab, settled 2026-08-07),
|
|
/// pinned the way the popover's other pure seams are: `BoardInfoMetrics` against a loaded fixture
|
|
/// board, `BoardDiskFootprint` against real bytes on disk. The rows' rendering — labels, the em-dash
|
|
/// placeholder, the Reveal button — is SwiftUI and deliberately untested.
|
|
@Suite("Board popover ▸ Info tab metrics")
|
|
struct BoardInfoTabTests {
|
|
|
|
/// Two live lanes holding three cards (one carrying two attachments), a loose trashed card, and
|
|
/// a trashed lane holding two more — the shape that exercises every counting rule at once.
|
|
private func makeFixture() throws -> WriterFixture {
|
|
let fixture = try WriterFixture()
|
|
|
|
try fixture.item("", Item.board)
|
|
try fixture.item(Ident.lane1, Item.rich(order: "1024", title: "Doing"))
|
|
try fixture.item("\(Ident.lane1)/\(Ident.card1)", Item.rich(order: "1024", title: "One"))
|
|
try fixture.item("\(Ident.lane1)/\(Ident.card2)", Item.rich(order: "2048", title: "Two"))
|
|
try fixture.file("\(Ident.lane1)/\(Ident.card1)/attachments/sketch.png", Data([0xFF, 0xD8]))
|
|
try fixture.file("\(Ident.lane1)/\(Ident.card1)/attachments/notes.txt", Data("notes".utf8))
|
|
try fixture.item(Ident.lane2, Item.rich(order: "2048", title: "Done"))
|
|
try fixture.item("\(Ident.lane2)/\(Ident.card3)", Item.rich(order: "1024", title: "Three"))
|
|
|
|
try fixture.item(".trash/\(Ident.card4)", Item.rich(order: "1024", title: "Tossed"))
|
|
try fixture.item(
|
|
".trash/\(Ident.lane3)",
|
|
"---\nschema: 1\ntitle: Shipped\norder: 3072\nkind: lane\n---\n"
|
|
)
|
|
try fixture.item(
|
|
".trash/\(Ident.lane3)/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
|
|
Item.rich(order: "1024", title: "Held one")
|
|
)
|
|
try fixture.item(
|
|
".trash/\(Ident.lane3)/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
|
|
Item.rich(order: "2048", title: "Held two")
|
|
)
|
|
|
|
return fixture
|
|
}
|
|
|
|
@Test("Lanes and cards count the live board only — the welcome-count rule")
|
|
func lanesAndCardsAreLiveOnly() throws {
|
|
let fixture = try makeFixture()
|
|
defer { fixture.tearDown() }
|
|
|
|
let metrics = BoardInfoMetrics(snapshot: try BoardLoader.load(boardRoot: fixture.root).model)
|
|
|
|
#expect(metrics.lanes == 2)
|
|
#expect(metrics.cards == 3)
|
|
}
|
|
|
|
@Test("The trash tail counts freight — loose cards plus every trashed lane's held cards")
|
|
func trashedCardsCountFreight() throws {
|
|
let fixture = try makeFixture()
|
|
defer { fixture.tearDown() }
|
|
|
|
let metrics = BoardInfoMetrics(snapshot: try BoardLoader.load(boardRoot: fixture.root).model)
|
|
|
|
// One loose trashed card, two held by the trashed lane — the purge-confirm counting rule.
|
|
#expect(metrics.trashedCards == 3)
|
|
}
|
|
|
|
@Test("Attachments count the live cards' files")
|
|
func attachmentsCountLiveCards() throws {
|
|
let fixture = try makeFixture()
|
|
defer { fixture.tearDown() }
|
|
|
|
let metrics = BoardInfoMetrics(snapshot: try BoardLoader.load(boardRoot: fixture.root).model)
|
|
|
|
#expect(metrics.attachments == 2)
|
|
}
|
|
|
|
@Test("An empty board is all zeros, not a crash or a lie")
|
|
func emptyBoardIsZeros() throws {
|
|
let fixture = try WriterFixture()
|
|
defer { fixture.tearDown() }
|
|
try fixture.item("", Item.board)
|
|
|
|
let metrics = BoardInfoMetrics(snapshot: try BoardLoader.load(boardRoot: fixture.root).model)
|
|
|
|
#expect(metrics.lanes == 0)
|
|
#expect(metrics.cards == 0)
|
|
#expect(metrics.trashedCards == 0)
|
|
#expect(metrics.attachments == 0)
|
|
}
|
|
}
|
|
|
|
/// **The whole-folder walk** — the disk rows exist to agree with Finder's Get Info, so the walk
|
|
/// must count hidden entries (`.git`, `.trash/`) exactly as it counts the visible board.
|
|
@Suite("Board popover ▸ Info tab disk footprint")
|
|
struct BoardDiskFootprintTests {
|
|
|
|
@Test("Files counts every regular file in the tree, hidden entries included")
|
|
func filesCountsTheWholeFolder() throws {
|
|
let fixture = try WriterFixture()
|
|
defer { fixture.tearDown() }
|
|
|
|
try fixture.item("", Item.board)
|
|
try fixture.item(Ident.lane1, Item.rich(order: "1024", title: "Doing"))
|
|
try fixture.item("\(Ident.lane1)/\(Ident.card1)", Item.rich(order: "1024", title: "One"))
|
|
try fixture.file("\(Ident.lane1)/\(Ident.card1)/attachments/sketch.png", Data([0xFF, 0xD8]))
|
|
try fixture.file(".git/HEAD", Data("ref: refs/heads/main\n".utf8))
|
|
try fixture.item(".trash/\(Ident.card2)", Item.rich(order: "1024", title: "Tossed"))
|
|
|
|
let footprint = BoardDiskFootprint.measure(at: fixture.root)
|
|
|
|
// Three index.md files live (board, lane, card), one attachment, one .git file, one trashed
|
|
// card's index.md — six regular files; directories are containers, not files.
|
|
#expect(footprint.files == 6)
|
|
}
|
|
|
|
@Test("Size on disk is at least the logical bytes — allocation rounds up, never down to zero")
|
|
func bytesCoverTheLogicalSize() throws {
|
|
let fixture = try WriterFixture()
|
|
defer { fixture.tearDown() }
|
|
|
|
try fixture.item("", Item.board)
|
|
let payload = Data(repeating: 0x2A, count: 4096)
|
|
try fixture.file("stray.bin", payload)
|
|
|
|
let footprint = BoardDiskFootprint.measure(at: fixture.root)
|
|
|
|
let logical = Int64(payload.count + Data(Item.board.utf8).count)
|
|
#expect(footprint.bytes >= logical)
|
|
}
|
|
|
|
@Test("Created and Modified come back from a real folder, and never out of order")
|
|
func datesAreReadAndOrdered() throws {
|
|
let fixture = try WriterFixture()
|
|
defer { fixture.tearDown() }
|
|
|
|
try fixture.item("", Item.board)
|
|
|
|
let footprint = BoardDiskFootprint.measure(at: fixture.root)
|
|
|
|
let created = try #require(footprint.created)
|
|
let modified = try #require(footprint.modified)
|
|
#expect(modified >= created)
|
|
}
|
|
|
|
@Test("A vanished folder answers empty rather than trapping — the walk has no user to ask")
|
|
func vanishedFolderAnswersEmpty() {
|
|
let gone = URL(fileURLWithPath: "/tmp/lanework-info-tab-never-existed-\(UUID().uuidString)")
|
|
|
|
let footprint = BoardDiskFootprint.measure(at: gone)
|
|
|
|
#expect(footprint.files == 0)
|
|
#expect(footprint.bytes == 0)
|
|
}
|
|
}
|