Files
lanework/KanbanTests/BoardInfoPopoverTests.swift
T
rzen 94e60cd444 The whole board-name area opens the popover — the chevron alone was the trigger
The titlebar widget grows from a 20×18 chevron into one button saying
the board's name and, on a git-mode Pro board, its branch — click
anywhere across it and the popover opens as before, anchored to the
widget. BoardInfoTitlebarSummary is the pure seam for both strings
(title falls back to the folder name per 01's naming rule; branch only
under pro + git mode, live off the observable HistoryStore.branch).
Board windows now hide the system title display through the same
hideTitle slot card windows adopted — the widget says the name, so the
chrome would only repeat it — while navigationTitle keeps feeding
window.title to the Window menu, Exposé, VoiceOver and restoration.

The widget also refreshes the branch eagerly at appearance: it used to
populate only once the popover had been opened, which would have left
the new branch line empty on a freshly opened board.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
2026-08-01 08:28:53 -04:00

149 lines
6.4 KiB
Swift

import Foundation
import Testing
@testable import Kanban
/// **The board popover's git-note detection** (12-editions.md ▸ The free tier and `.git`, ruled
/// 2026-07-27): the free tier's whole git story is one line, shown only on a board that carries an
/// inert `.git`. The seam
/// that decides *whether* to show it — `BoardGitNote.hasGitDirectory(at:)` — is a pure, read-only
/// `FileManager` check, pinned here against real bytes on disk. Everything else about the note (its
/// wording, its placement in `BoardInfoView`) is SwiftUI rendering and deliberately untested.
struct BoardInfoPopoverTests {
@Test("A board with a .git at its root reports true")
func trueForABoardWithGit() throws {
let fixture = try WriterFixture()
defer { fixture.tearDown() }
try fixture.item("", Item.board)
try fixture.file(".git/HEAD", Data("ref: refs/heads/main\n".utf8))
#expect(BoardGitNote.hasGitDirectory(at: fixture.root))
}
@Test("An ordinary board with no .git reports false")
func falseForAnOrdinaryBoard() throws {
let fixture = try WriterFixture()
defer { fixture.tearDown() }
try fixture.item("", Item.board)
#expect(!BoardGitNote.hasGitDirectory(at: fixture.root))
}
}
/// **The popover's git slot, posture by posture** (03-board-ui.md ▸ Board popover; 06-history-undo.md
/// ▸ Rules; 12-editions.md ▸ The free tier and `.git`).
///
/// `BoardGitSection.resolve` is the whole decision, pulled out as a pure function of the tier and the
/// board's mode precisely so the matrix is assertable — the views it selects are SwiftUI and stay
/// untested, exactly as the note's wording and placement do above.
@Suite("Board popover ▸ the git section's posture")
struct BoardGitSectionTests {
@Test("The free tier: absent on an ordinary board, a Pro pointer on a board carrying an inert .git")
func theFreeTierIsContextual() {
#expect(BoardGitSection.resolve(tier: .free, mode: .none, hasGitDirectory: false) == .absent)
#expect(BoardGitSection.resolve(tier: .free, mode: .none, hasGitDirectory: true) == .proPointer)
}
@Test("Pro: mode none offers add-git, git mode shows the branch")
func proFollowsTheMode() {
#expect(BoardGitSection.resolve(tier: .pro, mode: .none, hasGitDirectory: false) == .addGit)
#expect(BoardGitSection.resolve(tier: .pro, mode: .git, hasGitDirectory: true) == .branch)
}
@Test("A repo-nested board explains itself — the add-git action is absent, not disabled")
func repoNestedExplainsRatherThanDisables() {
let section = BoardGitSection.resolve(tier: .pro, mode: .repoNested, hasGitDirectory: false)
// The design is insistent here: "not a hidden 'add git' but a short explanation … the option
// is absent because it *can't* apply, and the UI should teach that rather than look broken"
// (06 ▸ Rules). A `.addGit` that rendered disabled would satisfy neither half.
#expect(section == .repoNested)
#expect(section != .addGit)
}
@Test("Every posture is reachable, and none of them is two postures")
func theMatrixIsTotal() {
let resolved = Set(
Tier.allCases.flatMap { tier in
BoardGitMode.allCases.flatMap { mode in
[true, false].map { BoardGitSection.resolve(tier: tier, mode: mode, hasGitDirectory: $0) }
}
}
)
#expect(resolved == Set(BoardGitSection.allCases))
}
}
/// **The window-title widget's two strings** (03-board-ui.md ▸ Board popover, the card that widened
/// the widget from a chevron to the whole board-name area): `BoardInfoTitlebarSummary` is the pure
/// function this pins, exactly as `BoardGitSectionTests` above pins the section it shares its
/// tier/mode inputs with. No disk I/O — the title half of this seam takes a raw `URL`, not a
/// `BoardStore`, so a plain `/tmp/...` path is enough.
@Suite("Board popover ▸ the widget's strings")
struct BoardInfoTitlebarSummaryTests {
private let root = URL(fileURLWithPath: "/tmp/My Board.board")
@Test("The title is the on-disk title, falling back to the folder name sans extension")
func titleFallsBackToTheFolderName() {
let named = BoardInfoTitlebarSummary(
snapshotTitle: "Sprint 12", rootURL: root, tier: .free, mode: .none, branch: nil
)
#expect(named.title == "Sprint 12")
let untitled = BoardInfoTitlebarSummary(
snapshotTitle: nil, rootURL: root, tier: .free, mode: .none, branch: nil
)
#expect(untitled.title == "My Board")
let emptyTitled = BoardInfoTitlebarSummary(
snapshotTitle: "", rootURL: root, tier: .free, mode: .none, branch: nil
)
#expect(
emptyTitled.title == "My Board",
"an empty title reads the same as no title at all — the window-title rule this seam restates"
)
}
@Test("The branch shows under Pro, in git mode, once it has been read")
func branchShowsForProGitMode() {
let summary = BoardInfoTitlebarSummary(
snapshotTitle: nil, rootURL: root, tier: .pro, mode: .git, branch: "main"
)
#expect(summary.branch == "main")
}
@Test("The free tier never shows a branch, whatever the mode or the git state hands it")
func theFreeTierNeverShowsABranch() {
for mode in BoardGitMode.allCases {
let summary = BoardInfoTitlebarSummary(
snapshotTitle: nil, rootURL: root, tier: .free, mode: mode, branch: "main"
)
#expect(summary.branch == nil, "detection never runs under the free tier, so a stray branch value must never surface")
}
}
@Test("An inert .git under Pro — mode none or repo-nested — never shows a branch")
func inertGitNeverShowsABranch() {
#expect(
BoardInfoTitlebarSummary(snapshotTitle: nil, rootURL: root, tier: .pro, mode: .none, branch: "main").branch == nil
)
#expect(
BoardInfoTitlebarSummary(snapshotTitle: nil, rootURL: root, tier: .pro, mode: .repoNested, branch: "main").branch
== nil
)
}
@Test("A git-mode board whose branch has not been read yet shows none, honestly")
func unreadBranchShowsNone() {
let summary = BoardInfoTitlebarSummary(
snapshotTitle: nil, rootURL: root, tier: .pro, mode: .git, branch: nil
)
#expect(summary.branch == nil)
}
}