The titlebar widget becomes a two-line identity block: the board glyph at 22pt spanning both lines, the title over the branch (git-mode only, smaller and secondary), the em-dash retired. New Branch… returns to the switch menu behind a divider, revealing an inline name field — the pre-split shape. The board settings sheet retires whole: add-git and commit identity render inline in the Git tab's postures (BoardGitSetup.swift), the availability rule collapses into BoardGitSetupSection.resolve, and Board ▸ Board Settings… leaves the menu bar. Where 07's remote/credential setup surfaces land is deliberately left open — filed on the Redesign board. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
156 lines
8.4 KiB
Swift
156 lines
8.4 KiB
Swift
import Foundation
|
|
import Testing
|
|
@testable import Kanban
|
|
|
|
/// **What the popover's Git tab sets up, board by board** (03-board-ui.md ▸ Board popover ▸ Git tab;
|
|
/// 06-history-undo.md ▸ Rules) — `BoardGitSetupSection.resolve`, the tab's setup inventory, pinned
|
|
/// for `BoardGitSection.resolve`'s reason one seam over: the *contents* are the decision worth
|
|
/// asserting and the SwiftUI that renders them is not. Every case below is a plain value — no board
|
|
/// on disk, no window, no session.
|
|
///
|
|
/// ### This suite is the settings sheet's, inherited
|
|
///
|
|
/// It was `BoardSettingsSectionTests` and `BoardSettingsAvailabilityTests` against the sheet's own
|
|
/// inventory (2026-07-31 → 2026-08-07). The **2026-08-07 reversal** retired that sheet and rehomed
|
|
/// its controls into the tab, so the *subject* moved and most of the rules did not: an unreadable
|
|
/// repository still hosts no setup, repo-nested and unverifiable still host none, and mode `none`
|
|
/// still means add-git and nothing else.
|
|
///
|
|
/// Two things did change, and both are asserted below rather than described:
|
|
///
|
|
/// - **Branch creation is not in this inventory.** It went back into the switch menu as New Branch…
|
|
/// with an inline reveal (`BoardGitControls`), which is a daily control rather than a standing
|
|
/// form — so the git-mode posture's setup half is the commit identity alone.
|
|
/// - **Emptiness no longer means "no surface".** The sheet's `BoardSettingsAvailability` existed to
|
|
/// answer "does this surface exist at all", because a sheet with no sections had no honest empty
|
|
/// state and two doors had to validate before opening it. The tab always exists (every board
|
|
/// carries all three tabs since 12-editions.md ▸ PIVOT 2026-08-07) and always has a posture to
|
|
/// render, so an empty inventory now means only "this posture hosts no setup block" — there is no
|
|
/// door left to disable, and the availability seam retired with the doors.
|
|
@Suite("Board popover ▸ Git tab ▸ the setup inventory")
|
|
struct BoardGitSetupSectionTests {
|
|
|
|
@Test("Mode none: add-git and nothing else")
|
|
func modeNoneHoldsAddGit() {
|
|
// "add-git (mode none; opt-in init — 06)", rendered under the no-repository note since the
|
|
// reversal put it back where the pre-split popover had it. Every tier's since
|
|
// 12-editions.md ▸ PIVOT 2026-08-07, and still an offer rather than an auto-init.
|
|
#expect(BoardGitSetupSection.resolve(mode: .none) == [.addGit])
|
|
}
|
|
|
|
@Test("Git mode: the commit identity — creation is the switch menu's again")
|
|
func gitModeHoldsTheIdentity() {
|
|
// "commit identity name/email (06 — the visibility-scoped 2 s config re-read rides with the
|
|
// fields)". Branch creation was this posture's other section while the sheet existed; the
|
|
// reversal moved it back into the menu, so it is deliberately absent here.
|
|
#expect(BoardGitSetupSection.resolve(mode: .git) == [.commitIdentity])
|
|
}
|
|
|
|
@Test("Every section is reachable from some posture, and no posture invents one")
|
|
func theInventoryIsTotal() {
|
|
let offered = Set(BoardGitMode.allCases.flatMap { BoardGitSetupSection.resolve(mode: $0) })
|
|
|
|
#expect(offered == Set(BoardGitSetupSection.allCases))
|
|
}
|
|
|
|
@Test("Repo-nested: nothing setup-shaped applies — the posture is its explanation")
|
|
func repoNestedHoldsNothing() {
|
|
// "not a hidden 'add git' but a short explanation … the option is absent because it *can't*
|
|
// apply" (06 ▸ Rules). The posture still renders — its note is the whole of it — but there
|
|
// is no setup block under it.
|
|
#expect(BoardGitSetupSection.resolve(mode: .repoNested) == [])
|
|
}
|
|
|
|
@Test("Unverifiable: the same emptiness, for the denial-not-absence reason")
|
|
func unverifiableHoldsNothing() {
|
|
// "Denial is not absence" (06 ▸ Rules ▸ Detection, ruled 2026-07-31): a denied ancestor check
|
|
// can never be told apart from a repository actually being there, so add-git stays as
|
|
// unreachable here as it is on a genuinely nested board.
|
|
#expect(BoardGitSetupSection.resolve(mode: .unverifiable) == [])
|
|
}
|
|
|
|
@Test("Git mode with an unreadable repository: no setup block, but still the branch posture")
|
|
func anUnreadableRepositoryHoldsNoSetup() {
|
|
// **The corrupt-`.git` loud failure** (06 ▸ Rules, ruled 2026-07-31): writing an identity is
|
|
// a *write* into the repository's own config, and there is no repository the app can open to
|
|
// write it into.
|
|
#expect(BoardGitSetupSection.resolve(mode: .git, isRepositoryUnreadable: true) == [])
|
|
|
|
// …and the mode is still `git` throughout: this is emptiness *within* git mode, never the
|
|
// fall to mode none that would let add-git be offered against an existing `.git`.
|
|
#expect(BoardGitSetupSection.resolve(mode: .git) == [.commitIdentity])
|
|
|
|
// The difference the tab can express and the sheet could not: the *posture* is unchanged, so
|
|
// the board still gets its branch surface, holding and explaining itself
|
|
// (`BoardGitBranchSurface.unreadableNote`). Only the setup block goes.
|
|
#expect(BoardGitSection.resolve(mode: .git) == .branch)
|
|
}
|
|
|
|
@Test("Only the identity block carries a header")
|
|
func headersAreNamed() {
|
|
// 10-accessibility.md's navigable-header rule, carried over from the retired sheet's
|
|
// sections: a heading the VoiceOver rotor jumps to. Add-git has none — it renders directly
|
|
// under the note that states the posture, and a "Git" header inside the Git tab would be the
|
|
// surface naming itself twice.
|
|
#expect(BoardGitSetupSection.commitIdentity.title == "Commit Identity")
|
|
#expect(BoardGitSetupSection.addGit.title == nil)
|
|
}
|
|
}
|
|
|
|
/// The same inventory asked about **a board on disk** — the two ends of the range a real
|
|
/// `HistoryStore` puts into it, so the seam's inputs are shown to be the ones a composed session
|
|
/// actually produces rather than values a test invented.
|
|
///
|
|
/// `@MainActor` because composing a `HistoryStore` is (the inventory itself is `nonisolated`, which
|
|
/// is exactly what the suite above exercises with no actor in sight).
|
|
@MainActor
|
|
@Suite("Board popover ▸ Git tab ▸ the setup inventory, against a composed session")
|
|
struct BoardGitSetupCompositionTests {
|
|
|
|
@Test("A board carrying an unopenable `.git` is a git board with no setup")
|
|
func anInertGitIsAnUnreadableGit() 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))
|
|
|
|
// The retired posture, asserted where it used to bite: a `.git` at a board root was **inert**
|
|
// off Pro — never read, never written, the popover's one-line Pro pointer that board's whole
|
|
// story. Detection runs at every board open on every tier now (12-editions.md ▸ PIVOT
|
|
// 2026-08-07), so this composes in git mode with no tier asked for.
|
|
let git = HistoryStore.compose(boardRoot: fixture.root)
|
|
#expect(git.mode == .git)
|
|
|
|
// And what these three bytes actually are is a repository libgit2 will not open, so the board
|
|
// takes **06's corrupt-`.git` posture, not the retired inert one** (06 ▸ Rules, ruled
|
|
// 2026-07-31): git mode throughout — never a fall to mode none that would offer add-git
|
|
// against an existing `.git` — with no setup block, because every control there would write
|
|
// into something the app cannot open.
|
|
#expect(git.isRepositoryUnreadable)
|
|
#expect(
|
|
BoardGitSetupSection.resolve(
|
|
mode: git.mode,
|
|
isRepositoryUnreadable: git.isRepositoryUnreadable
|
|
) == []
|
|
)
|
|
}
|
|
|
|
@Test("A fresh mode-none board resolves to the add-git offer")
|
|
func aFreshBoardOffersAddGit() throws {
|
|
let fixture = try WriterFixture()
|
|
defer { fixture.tearDown() }
|
|
try fixture.item("", Item.board)
|
|
|
|
// Composed with no tier in sight: git is tier-independent since 12-editions.md ▸ PIVOT
|
|
// 2026-08-07, so this is every session's git state, not Pro's.
|
|
let git = HistoryStore.compose(boardRoot: fixture.root)
|
|
#expect(git.mode == .none)
|
|
#expect(
|
|
BoardGitSetupSection.resolve(
|
|
mode: git.mode,
|
|
isRepositoryUnreadable: git.isRepositoryUnreadable
|
|
) == [.addGit]
|
|
)
|
|
}
|
|
}
|