add unit test target with BrewParser extraction

Extract brew output parsing into BrewParser.swift (pure Swift, no AppKit)
and add BrewBarTests target with 7 passing tests covering parseOutdated
and parseVersion edge cases.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
maxsoch 2026-07-06 21:17:26 +02:00
parent 6f255380d0
commit 4ab3cc1e70
5 changed files with 173 additions and 2 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
*.xcuserdatad/ *.xcuserdatad/
CLAUDE.local.md CLAUDE.local.md
.claude/settings.local.json .claude/settings.local.json
build/

View file

@ -6,8 +6,19 @@
objectVersion = 77; objectVersion = 77;
objects = { objects = {
/* Begin PBXContainerItemProxy section */
87F2AA852FEE797F0014F9D6 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 87F2AA5A2FEE797F0014F9D6 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 87F2AA612FEE797F0014F9D6;
remoteInfo = BrewBar;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
87F2AA622FEE797F0014F9D6 /* BrewBar.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BrewBar.app; sourceTree = BUILT_PRODUCTS_DIR; }; 87F2AA622FEE797F0014F9D6 /* BrewBar.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BrewBar.app; sourceTree = BUILT_PRODUCTS_DIR; };
87F2AA802FEE797F0014F9D6 /* BrewBarTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BrewBarTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
@ -29,6 +40,11 @@
path = BrewBar; path = BrewBar;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
87F2AA812FEE797F0014F9D6 /* BrewBarTests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = BrewBarTests;
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */ /* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -39,6 +55,13 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
87F2AA822FEE797F0014F9D6 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
@ -46,6 +69,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
87F2AA642FEE797F0014F9D6 /* BrewBar */, 87F2AA642FEE797F0014F9D6 /* BrewBar */,
87F2AA812FEE797F0014F9D6 /* BrewBarTests */,
87F2AA632FEE797F0014F9D6 /* Products */, 87F2AA632FEE797F0014F9D6 /* Products */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
@ -54,6 +78,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
87F2AA622FEE797F0014F9D6 /* BrewBar.app */, 87F2AA622FEE797F0014F9D6 /* BrewBar.app */,
87F2AA802FEE797F0014F9D6 /* BrewBarTests.xctest */,
); );
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
@ -83,6 +108,28 @@
productReference = 87F2AA622FEE797F0014F9D6 /* BrewBar.app */; productReference = 87F2AA622FEE797F0014F9D6 /* BrewBar.app */;
productType = "com.apple.product-type.application"; productType = "com.apple.product-type.application";
}; };
87F2AA842FEE797F0014F9D6 /* BrewBarTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 87F2AA892FEE797F0014F9D6 /* Build configuration list for PBXNativeTarget "BrewBarTests" */;
buildPhases = (
87F2AA832FEE797F0014F9D6 /* Sources */,
87F2AA822FEE797F0014F9D6 /* Frameworks */,
);
buildRules = (
);
dependencies = (
87F2AA862FEE797F0014F9D6 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
87F2AA812FEE797F0014F9D6 /* BrewBarTests */,
);
name = BrewBarTests;
packageProductDependencies = (
);
productName = BrewBarTests;
productReference = 87F2AA802FEE797F0014F9D6 /* BrewBarTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */ /* End PBXNativeTarget section */
/* Begin PBXProject section */ /* Begin PBXProject section */
@ -96,6 +143,10 @@
87F2AA612FEE797F0014F9D6 = { 87F2AA612FEE797F0014F9D6 = {
CreatedOnToolsVersion = 26.3; CreatedOnToolsVersion = 26.3;
}; };
87F2AA842FEE797F0014F9D6 = {
CreatedOnToolsVersion = 26.3;
TestTargetID = 87F2AA612FEE797F0014F9D6;
};
}; };
}; };
buildConfigurationList = 87F2AA5D2FEE797F0014F9D6 /* Build configuration list for PBXProject "BrewBar" */; buildConfigurationList = 87F2AA5D2FEE797F0014F9D6 /* Build configuration list for PBXProject "BrewBar" */;
@ -113,6 +164,7 @@
projectRoot = ""; projectRoot = "";
targets = ( targets = (
87F2AA612FEE797F0014F9D6 /* BrewBar */, 87F2AA612FEE797F0014F9D6 /* BrewBar */,
87F2AA842FEE797F0014F9D6 /* BrewBarTests */,
); );
}; };
/* End PBXProject section */ /* End PBXProject section */
@ -135,8 +187,23 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
87F2AA832FEE797F0014F9D6 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */ /* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
87F2AA862FEE797F0014F9D6 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 87F2AA612FEE797F0014F9D6 /* BrewBar */;
targetProxy = 87F2AA852FEE797F0014F9D6 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */ /* Begin XCBuildConfiguration section */
87F2AA6B2FEE79800014F9D6 /* Debug */ = { 87F2AA6B2FEE79800014F9D6 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
@ -352,6 +419,48 @@
}; };
name = Release; name = Release;
}; };
87F2AA872FEE797F0014F9D6 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = GLVUYYHFD9;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 15.6;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.MaxSoch.BrewBarTests";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BrewBar.app/Contents/MacOS/BrewBar";
};
name = Debug;
};
87F2AA882FEE797F0014F9D6 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = GLVUYYHFD9;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 15.6;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.MaxSoch.BrewBarTests";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BrewBar.app/Contents/MacOS/BrewBar";
};
name = Release;
};
/* End XCBuildConfiguration section */ /* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */ /* Begin XCConfigurationList section */
@ -373,6 +482,15 @@
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
87F2AA892FEE797F0014F9D6 /* Build configuration list for PBXNativeTarget "BrewBarTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
87F2AA872FEE797F0014F9D6 /* Debug */,
87F2AA882FEE797F0014F9D6 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */ /* End XCConfigurationList section */
}; };
rootObject = 87F2AA5A2FEE797F0014F9D6 /* Project object */; rootObject = 87F2AA5A2FEE797F0014F9D6 /* Project object */;

View file

@ -244,7 +244,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
setMenuBarIcon("brewbar-updating") setMenuBarIcon("brewbar-updating")
runBrew("outdated") { output in runBrew("outdated") { output in
let lines = output.split(separator: "\n").map { String($0) } let lines = BrewParser.parseOutdated(output)
self.cachedOutdated = lines self.cachedOutdated = lines
self.lastOutdatedFetch = Date() self.lastOutdatedFetch = Date()
@ -304,7 +304,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
/// Brew version (correct) /// Brew version (correct)
func refreshBrewVersion() { func refreshBrewVersion() {
runBrew("--version") { output in runBrew("--version") { output in
let firstLine = output.split(separator: "\n").first.map(String.init) ?? "Unknown" let firstLine = BrewParser.parseVersion(output)
DispatchQueue.main.async { DispatchQueue.main.async {
self.versionItem.title = "🏷 \(firstLine)" self.versionItem.title = "🏷 \(firstLine)"

13
BrewBar/BrewParser.swift Normal file
View file

@ -0,0 +1,13 @@
import Foundation
enum BrewParser {
static func parseOutdated(_ output: String) -> [String] {
output.split(separator: "\n")
.map { String($0) }
.filter { !$0.isEmpty }
}
static func parseVersion(_ output: String) -> String {
output.split(separator: "\n").first.map(String.init) ?? "Unknown"
}
}

View file

@ -0,0 +1,39 @@
@testable import BrewBar
import XCTest
final class BrewParserTests: XCTestCase {
// MARK: - parseOutdated
func testParseOutdated_multiplePackages() {
let output = "wget\nffmpeg\ngit\n"
XCTAssertEqual(BrewParser.parseOutdated(output), ["wget", "ffmpeg", "git"])
}
func testParseOutdated_emptyOutput() {
XCTAssertEqual(BrewParser.parseOutdated(""), [])
}
func testParseOutdated_singlePackage() {
XCTAssertEqual(BrewParser.parseOutdated("wget\n"), ["wget"])
}
func testParseOutdated_trailingNewlinesIgnored() {
let output = "wget\n\n\n"
XCTAssertEqual(BrewParser.parseOutdated(output), ["wget"])
}
// MARK: - parseVersion
func testParseVersion_picksFirstLine() {
let output = "Homebrew 4.3.0\nHomebrew/homebrew-core (git revision abc123)\n"
XCTAssertEqual(BrewParser.parseVersion(output), "Homebrew 4.3.0")
}
func testParseVersion_emptyOutput() {
XCTAssertEqual(BrewParser.parseVersion(""), "Unknown")
}
func testParseVersion_singleLine() {
XCTAssertEqual(BrewParser.parseVersion("Homebrew 4.3.0"), "Homebrew 4.3.0")
}
}