import AppKit import Foundation import Testing @testable import phbar // MARK: - Loading @MainActor @Test func loadBlocksFromTOML() throws { let toml = """ [[block]] command = "echo hello" name = "greeting" refresh = 5.0 [[block]] command = "echo world" name = "world" """ let blocks = try PHBlock.load(from: toml) #expect(blocks.count == 2) #expect(blocks[0].command == "echo hello") #expect(blocks[0].name == "greeting") #expect(blocks[0].refresh == 5.0) #expect(blocks[0].label == nil) #expect(blocks[1].name == "world") #expect(blocks[1].refresh == nil) } @MainActor @Test func loadBlocksRejectsInvalidTOML() { #expect(throws: (any Error).self) { try PHBlock.load(from: "not = valid = toml = =") } } // MARK: - Named set loading private func makeBlocksConfigDir() throws -> URL { let dir = FileManager.default.temporaryDirectory.appending(path: "phbar_blocks_\(UUID().uuidString)") try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) return dir } @MainActor @Test func loadSetReadsNamedFile() throws { let dir = try makeBlocksConfigDir() defer { try? FileManager.default.removeItem(at: dir) } let blocksDir = dir.appending(path: "blocks") try FileManager.default.createDirectory(at: blocksDir, withIntermediateDirectories: true) try Data(""" [[block]] command = "echo hi" name = "greeting" """.utf8).write(to: blocksDir.appending(path: "laptop.toml")) let blocks = try PHBlock.load("laptop", in: dir) #expect(blocks.count == 1) #expect(blocks[0].name == "greeting") } @MainActor @Test func loadSetThrowsForMissingSet() { let dir = FileManager.default.temporaryDirectory.appending(path: "phbar_missing_\(UUID().uuidString)") #expect(throws: (any Error).self) { try PHBlock.load("nope", in: dir) } } // MARK: - Compute @MainActor @Test func computeSeesBlockEnvironment() async throws { // The `[env]` section (overlaid by `BarController`) must reach the script's // process environment — this is the wiring the `env` config documents. let block = try PHBlock.load(from: """ [[block]] name = "env" command = "echo $PHBAR_TEST_VAR" """)[0] block.environment = PHEnvironment(variables: ["PHBAR_TEST_VAR": "panini"]) let output = await block.compute() #expect(output == "panini") } @MainActor @Test func computeReturnsCommandStdout() async throws { let blocks = try PHBlock.load(from: """ [[block]] command = "printf panini" name = "panini" """) let output = await blocks[0].compute() #expect(output == "panini") } @MainActor @Test func computeRunsInConfigDirectory() async throws { // Block commands execute with the resolved config root (see `PHPaths`) as // their working directory, so a block from any set resolves relative paths // the same way. Guards against the config dir being absent in sandboxed CIs. guard FileManager.default.fileExists(atPath: PHPaths.configDirectory.path) else { return } let blocks = try PHBlock.load(from: """ [[block]] command = "pwd" name = "pwd" """) let output = await blocks[0].compute() // Resolve symlinks on both sides: the config dir is commonly a symlink into // a dotfiles repo, and `pwd` reports the physical path. let expected = PHPaths.configDirectory.resolvingSymlinksInPath().path #expect(output == expected) } // MARK: - Auto-refresh @MainActor @Test func updateSetsLabel() async throws { let block = try PHBlock.load(from: """ [[block]] command = "printf panini" name = "panini" """)[0] #expect(block.label == nil) await block.update() #expect(block.label == "panini") } @MainActor @Test func startAutoRefreshWithoutIntervalComputesOnce() async throws { let block = try PHBlock.load(from: """ [[block]] command = "printf hi" name = "hi" """)[0] block.startAutoRefresh() // Allow the one-shot update task to run. try await Task.sleep(for: .milliseconds(100)) #expect(block.label == "hi") block.stopAutoRefresh() } @MainActor @Test func startAutoRefreshRepeatsAtInterval() async throws { // A counter file lets us observe how many times the command ran. let counter = FileManager.default.temporaryDirectory .appending(path: "phbar_test_\(UUID().uuidString)") defer { try? FileManager.default.removeItem(at: counter) } let path = counter.path let toml = """ [[block]] command = "n=$(cat \(path) 2>/dev/null || echo 0); n=$((n+1)); echo $n > \(path); echo $n" name = "counter" refresh = 0.05 """ let block = try PHBlock.load(from: toml)[0] block.startAutoRefresh() try await Task.sleep(for: .milliseconds(250)) block.stopAutoRefresh() let count = Int(block.label!.trimmingCharacters(in: .whitespacesAndNewlines)) ?? 0 // At ~20Hz over 250ms the command should have run more than once. #expect(count >= 2) }