import Foundation import Testing @testable import Kanban /// **Nearest-`.git`-wins, freshly at every open** (06-history-undo.md ▸ Rules ▸ Detection) — the one /// question every git surface starts from, pinned against real directories rather than against a /// mocked filesystem, because what it is *about* is what is on disk. /// /// The rule has four claims and this file is one test per claim: root wins, an ancestor is nested, /// neither is `none`, and the answer is re-derived rather than remembered — "a board can therefore /// change mode between opens (e.g. the user ran `git init` in a terminal) — the app just reflects /// what it finds." // MARK: - Fixtures /// A `.git` **directory** with a plausible ref inside — what `git init` leaves. private func makeGitDirectory(at parent: URL) throws { let gitDirectory = parent.appendingPathComponent(".git", isDirectory: true) try FileManager.default.createDirectory(at: gitDirectory, withIntermediateDirectories: true) try Data("ref: refs/heads/main\n".utf8).write(to: gitDirectory.appendingPathComponent("HEAD")) } /// A `.git` **file** — what a linked worktree or a submodule leaves. Still a repository, and the /// reason detection asks `fileExists` rather than `isDirectory`. private func makeGitPointerFile(at parent: URL, to target: String) throws { try Data("gitdir: \(target)\n".utf8).write(to: parent.appendingPathComponent(".git")) } /// A board folder inside the fixture, so an *ancestor* can carry the repository. private func makeSubfolder(_ fixture: WriterFixture, named name: String) throws -> URL { let url = fixture.root.appendingPathComponent(name, isDirectory: true) try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true) return url } // MARK: - Detection @Suite("Board git mode ▸ detection") struct BoardGitModeTests { @Test("A `.git` at the board root is git mode") func rootRepositoryIsGitMode() throws { let fixture = try WriterFixture() defer { fixture.tearDown() } try fixture.item("", Item.board) try makeGitDirectory(at: fixture.root) #expect(BoardGitMode.detect(boardRoot: fixture.root) == .git) } @Test("A `.git` *file* is a repository too — a worktree is not mode none") func aWorktreePointerIsGitMode() throws { let fixture = try WriterFixture() defer { fixture.tearDown() } try fixture.item("", Item.board) try makeGitPointerFile(at: fixture.root, to: "/somewhere/else/.git/worktrees/board") #expect(BoardGitMode.detect(boardRoot: fixture.root) == .git) } @Test("No `.git` at the root and none above it is mode none") func aPlainFolderIsModeNone() throws { let fixture = try WriterFixture() defer { fixture.tearDown() } try fixture.item("", Item.board) #expect(BoardGitMode.detect(boardRoot: fixture.root) == .none) } @Test("A `.git` at an ancestor and none at the root is repo-nested") func anEnclosingRepositoryIsRepoNested() throws { let fixture = try WriterFixture() defer { fixture.tearDown() } try makeGitDirectory(at: fixture.root) let board = try makeSubfolder(fixture, named: "project/docs/board") #expect(BoardGitMode.detect(boardRoot: board) == .repoNested) #expect(BoardGitMode.enclosingRepositoryRoot(above: board)?.standardizedFileURL == fixture.root.standardizedFileURL) } @Test("Nearest wins: a board with its own `.git` inside a repository is git mode, not nested") func theNearestRepositoryWins() throws { let fixture = try WriterFixture() defer { fixture.tearDown() } try makeGitDirectory(at: fixture.root) let board = try makeSubfolder(fixture, named: "board") try makeGitDirectory(at: board) #expect(BoardGitMode.detect(boardRoot: board) == .git) } @Test("The walk terminates above a board at the filesystem root, finding nothing") func theAncestorWalkTerminates() { // The one hazard this walk has ever had: NSURL-bridged URLs whose // `deletingLastPathComponent` grows "/.." forever instead of settling at "/". The temp // directory has no repository above it, so the honest answer is `nil` — reached, not hung. #expect(BoardGitMode.enclosingRepositoryRoot(above: URL(fileURLWithPath: "/")) == nil) } // MARK: Freshness @Test("Detection is re-derived at every open: a board that gains a `.git` opens in git mode next time") func modeChangesBetweenOpens() throws { let fixture = try WriterFixture() defer { fixture.tearDown() } try fixture.item("", Item.board) #expect(BoardGitMode.detect(boardRoot: fixture.root) == .none, "the first open") // What a user does in a terminal under a closed board. try makeGitDirectory(at: fixture.root) #expect(BoardGitMode.detect(boardRoot: fixture.root) == .git, "the next open reflects what it finds") } @Test("And a board that loses its `.git` opens back in mode none") func modeChangesBackBetweenOpens() throws { let fixture = try WriterFixture() defer { fixture.tearDown() } try fixture.item("", Item.board) try makeGitDirectory(at: fixture.root) #expect(BoardGitMode.detect(boardRoot: fixture.root) == .git) try FileManager.default.removeItem(at: fixture.root.appendingPathComponent(".git")) #expect(BoardGitMode.detect(boardRoot: fixture.root) == .none) } }