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