Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ final class FileTreeState {

var nodes: [FileTreeNode] = []
for name in contents {
if Self.hiddenNames.contains(name) || name.hasPrefix(".") { continue }
if Self.hiddenNames.contains(name) { continue }
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve gitignore filtering when including dotfiles

By removing the name.hasPrefix(".") guard here, dotfiles now enter the regular scan path, but refresh() still rebuilds rootNodes via scanDirectory without re-running computeGitIgnored/filterIgnored. In practice, a dotfile that is ignored (for example .env) is hidden right after load() but reappears after any watcher-triggered refresh, which is a regression from the previous behavior where dotfiles stayed hidden on refresh.

Useful? React with 👍 / 👎.


let absPath = (path as NSString).appendingPathComponent(name)
let relPath = String(absPath.dropFirst(root.count + 1))
Expand Down
125 changes: 125 additions & 0 deletions apps/purepoint-macos/purepoint-macosTests/FileTreeStateTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import Testing
import Foundation
@testable import PurePoint

@MainActor
struct FileTreeStateTests {

private func makeTempDir(files: [String], dirs: [String] = []) throws -> String {
let root = NSTemporaryDirectory() + "FileTreeStateTests-\(UUID().uuidString)"
let fm = FileManager.default
try fm.createDirectory(atPath: root, withIntermediateDirectories: true)
for dir in dirs {
try fm.createDirectory(
atPath: (root as NSString).appendingPathComponent(dir),
withIntermediateDirectories: true
)
}
for file in files {
fm.createFile(
atPath: (root as NSString).appendingPathComponent(file),
contents: nil
)
}
return root
}

private func cleanup(_ path: String) {
try? FileManager.default.removeItem(atPath: path)
}

// MARK: - Dot file visibility

@Test func givenDotFileShouldIncludeInTree() throws {
// given
let root = try makeTempDir(files: [".gitignore", ".env.example", "README.md"])
defer { cleanup(root) }
let state = FileTreeState()

// when
state.load(worktreePath: root)

// then
let names = state.rootNodes.map(\.name)
#expect(names.contains(".gitignore"))
#expect(names.contains(".env.example"))
#expect(names.contains("README.md"))
}

@Test func givenDotDirectoryShouldIncludeInTree() throws {
// given
let root = try makeTempDir(files: [], dirs: [".claude", ".pu"])
defer { cleanup(root) }
let state = FileTreeState()

// when
state.load(worktreePath: root)

// then
let names = state.rootNodes.map(\.name)
#expect(names.contains(".claude"))
#expect(names.contains(".pu"))
}

// MARK: - Hidden names still excluded

@Test func givenGitDirectoryShouldExcludeFromTree() throws {
// given
let root = try makeTempDir(files: [".DS_Store"], dirs: [".git", ".build"])
defer { cleanup(root) }
let state = FileTreeState()

// when
state.load(worktreePath: root)

// then
let names = state.rootNodes.map(\.name)
#expect(!names.contains(".git"))
#expect(!names.contains(".DS_Store"))
#expect(!names.contains(".build"))
}

@Test func givenNonDotHiddenNamesShouldExcludeFromTree() throws {
// given
let root = try makeTempDir(files: [], dirs: ["node_modules", "DerivedData", "xcuserdata", "__pycache__"])
defer { cleanup(root) }
let state = FileTreeState()

// when
state.load(worktreePath: root)

// then
let names = state.rootNodes.map(\.name)
#expect(!names.contains("node_modules"))
#expect(!names.contains("DerivedData"))
#expect(!names.contains("xcuserdata"))
#expect(!names.contains("__pycache__"))
}

// MARK: - Mixed content

@Test func givenMixedContentShouldShowDotFilesButNotHiddenNames() throws {
// given
let root = try makeTempDir(
files: [".gitignore", ".env", ".DS_Store", "main.swift"],
dirs: [".claude", ".git", "Sources"]
)
defer { cleanup(root) }
let state = FileTreeState()

// when
state.load(worktreePath: root)

// then
let names = Set(state.rootNodes.map(\.name))
// Visible
#expect(names.contains(".gitignore"))
#expect(names.contains(".env"))
#expect(names.contains(".claude"))
#expect(names.contains("main.swift"))
#expect(names.contains("Sources"))
// Hidden
#expect(!names.contains(".git"))
#expect(!names.contains(".DS_Store"))
}
}
Loading