diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 07f5579e..9b9a2f35 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -17,16 +17,19 @@ jobs: run: "sudo xcode-select -s /Applications/Xcode_$XCODE_VERSION.app" - name: Checkout uses: actions/checkout@v4 - - name: Build project + - name: Lint Swift files + run: swift format lint Sources/ Tests/ Example/ --recursive + - name: Build sourcekit-bazel-bsp (debug) run: swift build - - name: Run tests + - name: Test sourcekit-bazel-bsp run: swift test - - name: Build example iOS app + - name: Build sourcekit-bazel-bsp (release) + run: swift build -c release + - name: Build example //HelloWorld iOS app run: | cd Example - bazel build //HelloWorld - # TODO: add test target to the example iOS app - # - name: Test example iOS app - # run: | - # cd Example - # bazel test //HelloWorld/HelloWorldTests/... + bazelisk build //HelloWorld + - name: Test example //HelloWorld iOS app + run: | + cd Example + bazelisk test //HelloWorld:HelloWorldTests diff --git a/Example/.gitignore b/Example/.gitignore index 048d2c50..7a919dd4 100644 --- a/Example/.gitignore +++ b/Example/.gitignore @@ -11,6 +11,7 @@ /.settings /bazel.iml /bazel-* +/custom-* /output/ /production /.sass-cache diff --git a/Example/HelloWorld/HelloWorldTests/SKDateDistanceCalculatorTests.swift b/Example/HelloWorld/HelloWorldTests/SKDateDistanceCalculatorTests.swift deleted file mode 100644 index 1a28fa0b..00000000 --- a/Example/HelloWorld/HelloWorldTests/SKDateDistanceCalculatorTests.swift +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) 2025 Spotify AB. -// -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -import TodoObjCSupport -import XCTest - -@testable import HelloWorldLib - -class SKDateDistanceCalculatorTests: XCTestCase { - - func testDistanceFromNowWithNilDate() { - let distance = SKDateDistanceCalculator.distance(fromNow: nil) - XCTAssertEqual(distance, 0.0) - } - - func testDistanceFromNowWithPastDate() { - let pastDate = Date().addingTimeInterval(-3600) // 1 hour ago - let distance = SKDateDistanceCalculator.distance(fromNow: pastDate) - XCTAssertGreaterThan(distance, 0) - XCTAssertEqual(distance, 3600, accuracy: 1.0) // Allow 1 second tolerance - } - - func testDistanceFromNowWithFutureDate() { - let futureDate = Date().addingTimeInterval(3600) // 1 hour from now - let distance = SKDateDistanceCalculator.distance(fromNow: futureDate) - XCTAssertLessThan(distance, 0) - XCTAssertEqual(distance, -3600, accuracy: 1.0) // Allow 1 second tolerance - } - - func testHumanReadableDistanceFromNowWithNilDate() { - let result = SKDateDistanceCalculator.humanReadableDistance(fromNow: nil) - XCTAssertEqual(result, "Invalid date") - } - - func testHumanReadableDistanceFromNowWithPastDate() { - let pastDate = Date().addingTimeInterval(-3600) // 1 hour ago - let result = SKDateDistanceCalculator.humanReadableDistance(fromNow: pastDate) - XCTAssertNotNil(result) - if let result = result { - XCTAssertTrue(result.contains("hour")) - XCTAssertTrue(result.contains("ago")) - } - } - - func testHumanReadableDistanceFromNowWithFutureDate() { - let futureDate = Date().addingTimeInterval(3600) // 1 hour from now - let result = SKDateDistanceCalculator.humanReadableDistance(fromNow: futureDate) - XCTAssertNotNil(result) - if let result = result { - XCTAssertTrue(result.contains("hour")) - XCTAssertTrue(result.contains("from now")) - } - } - - func testDistanceFromNowInUnitWithNilDate() { - let result = SKDateDistanceCalculator.distance(fromNow: nil, in: .hour) - XCTAssertEqual(result, 0) - } - - func testDistanceFromNowInUnitWithPastDate() { - let pastDate = Date().addingTimeInterval(-7200) // 2 hours ago - let result = SKDateDistanceCalculator.distance(fromNow: pastDate, in: .hour) - XCTAssertEqual(result, 2) - } - - func testDistanceFromNowInUnitWithFutureDate() { - let futureDate = Date().addingTimeInterval(7200) // 2 hours from now - let result = SKDateDistanceCalculator.distance(fromNow: futureDate, in: .hour) - XCTAssertEqual(result, -2) - } - - func testDateDistanceCalculationPerformance() { - let testDate = Date().addingTimeInterval(-86400) // 1 day ago - - measure { - for _ in 0..<1000 { - _ = SKDateDistanceCalculator.humanReadableDistance(fromNow: testDate) - } - } - } -} diff --git a/Example/HelloWorld/HelloWorldTests/TodoListManagerTests.swift b/Example/HelloWorld/HelloWorldTests/TodoListManagerTests.swift index e9dc21d5..15eca473 100644 --- a/Example/HelloWorld/HelloWorldTests/TodoListManagerTests.swift +++ b/Example/HelloWorld/HelloWorldTests/TodoListManagerTests.swift @@ -24,14 +24,21 @@ import XCTest class TodoListManagerTests: XCTestCase { + let fakeDefaults = UserDefaults(suiteName: "test")! + + override func tearDown() { + super.tearDown() + fakeDefaults.removeObject(forKey: "todo-items") + } + func testTodoListManagerInitialization() { - let manager = TodoListManager() + let manager = TodoListManager(userDefaults: fakeDefaults) XCTAssertNotNil(manager) XCTAssertTrue(manager.todoItems.isEmpty) } func testAddTodoItem() { - let manager = TodoListManager() + let manager = TodoListManager(userDefaults: fakeDefaults) let initialCount = manager.todoItems.count manager.addTodoItem(title: "New Task") @@ -42,7 +49,7 @@ class TodoListManagerTests: XCTestCase { } func testToggleTodoItem() { - let manager = TodoListManager() + let manager = TodoListManager(userDefaults: fakeDefaults) manager.addTodoItem(title: "Test Task") guard let item = manager.todoItems.first else { @@ -57,7 +64,7 @@ class TodoListManagerTests: XCTestCase { } func testDeleteTodoItem() { - let manager = TodoListManager() + let manager = TodoListManager(userDefaults: fakeDefaults) manager.addTodoItem(title: "Task to Delete") guard let item = manager.todoItems.first else { @@ -73,16 +80,16 @@ class TodoListManagerTests: XCTestCase { } func testTodoListManagerPersistence() { - let manager1 = TodoListManager() + let manager1 = TodoListManager(userDefaults: fakeDefaults) manager1.addTodoItem(title: "Persistent Task") - let manager2 = TodoListManager() + let manager2 = TodoListManager(userDefaults: fakeDefaults) XCTAssertEqual(manager2.todoItems.count, 1) XCTAssertEqual(manager2.todoItems.first?.title, "Persistent Task") } func testMultipleTodoItems() { - let manager = TodoListManager() + let manager = TodoListManager(userDefaults: fakeDefaults) manager.addTodoItem(title: "Task 1") manager.addTodoItem(title: "Task 2") @@ -95,7 +102,7 @@ class TodoListManagerTests: XCTestCase { } func testToggleNonExistentItem() { - let manager = TodoListManager() + let manager = TodoListManager(userDefaults: fakeDefaults) let fakeItem = TodoItem(title: "Fake Item") // Should not crash @@ -104,7 +111,7 @@ class TodoListManagerTests: XCTestCase { } func testDeleteNonExistentItem() { - let manager = TodoListManager() + let manager = TodoListManager(userDefaults: fakeDefaults) let fakeItem = TodoItem(title: "Fake Item") // Should not crash @@ -113,7 +120,7 @@ class TodoListManagerTests: XCTestCase { } func testEmptyTitleHandling() { - let manager = TodoListManager() + let manager = TodoListManager(userDefaults: fakeDefaults) manager.addTodoItem(title: "") XCTAssertEqual(manager.todoItems.count, 1) @@ -121,7 +128,7 @@ class TodoListManagerTests: XCTestCase { } func testVeryLongTitle() { - let manager = TodoListManager() + let manager = TodoListManager(userDefaults: fakeDefaults) let longTitle = String(repeating: "A", count: 1000) manager.addTodoItem(title: longTitle) @@ -130,7 +137,7 @@ class TodoListManagerTests: XCTestCase { } func testTodoListManagerPerformance() { - let manager = TodoListManager() + let manager = TodoListManager(userDefaults: fakeDefaults) measure { for i in 0..<100 { diff --git a/Example/HelloWorld/TodoModels/Sources/TodoListManager.swift b/Example/HelloWorld/TodoModels/Sources/TodoListManager.swift index d0a3296e..753534c6 100644 --- a/Example/HelloWorld/TodoModels/Sources/TodoListManager.swift +++ b/Example/HelloWorld/TodoModels/Sources/TodoListManager.swift @@ -23,10 +23,11 @@ import SwiftUI public final class TodoListManager: ObservableObject { @Published public var todoItems: [TodoItem] = [] - private let userDefaults = UserDefaults.standard + private let userDefaults: UserDefaults private let todoItemsKey = "todo-items" - public init() { + public init(userDefaults: UserDefaults = .standard) { + self.userDefaults = userDefaults loadTodoItems() } diff --git a/Example/MODULE.bazel b/Example/MODULE.bazel index 09ce8f5c..5fefb553 100644 --- a/Example/MODULE.bazel +++ b/Example/MODULE.bazel @@ -4,10 +4,17 @@ bazel_dep( repo_name = "build_bazel_rules_swift", ) +# We need a commit that has not yet made into a release, so overriding it for now bazel_dep( name = "rules_apple", - version = "4.0.1", repo_name = "build_bazel_rules_apple", ) +archive_override( + module_name = "rules_apple", + sha256 = "0abe1a851d65d64a1692bdc95a0649cba4218f0007d36eda030eb6bdfd6b6937", + strip_prefix = "rules_apple-a9fcb1c3ae6382534d773d73c035035e9764eadb", + url = "https://github.com/bazelbuild/rules_apple/archive/a9fcb1c3ae6382534d773d73c035035e9764eadb.zip", +) + bazel_dep(name = "apple_support", version = "1.22.1", repo_name = "build_bazel_apple_support") diff --git a/Example/MODULE.bazel.lock b/Example/MODULE.bazel.lock index 01e4adcf..cd7373a7 100644 --- a/Example/MODULE.bazel.lock +++ b/Example/MODULE.bazel.lock @@ -79,8 +79,6 @@ "https://bcr.bazel.build/modules/re2/2023-09-01/source.json": "e044ce89c2883cd957a2969a43e79f7752f9656f6b20050b62f90ede21ec6eb4", "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", "https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e", - "https://bcr.bazel.build/modules/rules_apple/4.0.1/MODULE.bazel": "dec3ca18ca4680c66a33341918ef5160ba37b51b282435e2ec01490b2f873475", - "https://bcr.bazel.build/modules/rules_apple/4.0.1/source.json": "8dab1eed33e306e60bd4a066e7ac14f660e0c389659fc80ab300dbfcb3182ad6", "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002", "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", @@ -170,7 +168,7 @@ "@@apple_support+//crosstool:setup.bzl%apple_cc_configure_extension": { "general": { "bzlTransitiveDigest": "gv4nokEMGNye4Jvoh7Tw0Lzs63zfklj+n4t0UegI7Ms=", - "usagesDigest": "2+lLQL24hXclVrnTX0EbCkvPg1v3is4M8/P7uFxNzgY=", + "usagesDigest": "2Syl6xg5PlRgZJHvhnwxR7fjNAae5AAxpdRREl5JlHU=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, diff --git a/Sources/SourceKitBazelBSP/Server/MessageHandler/BSPServerMessageHandlerImpl.swift b/Sources/SourceKitBazelBSP/Server/MessageHandler/BSPServerMessageHandlerImpl.swift index e73f4e50..69b1528a 100644 --- a/Sources/SourceKitBazelBSP/Server/MessageHandler/BSPServerMessageHandlerImpl.swift +++ b/Sources/SourceKitBazelBSP/Server/MessageHandler/BSPServerMessageHandlerImpl.swift @@ -218,7 +218,9 @@ final class BSPServerMessageHandlerImpl: @unchecked Sendable { var affectedTargets: Set = [] for change in changes { let targetsForSrc = workspaceBuildTargetsHandler.srcToTargetsMap[change] ?? [] - targetsForSrc.forEach { affectedTargets.insert($0) } + for target in targetsForSrc { + affectedTargets.insert(target) + } } let response = OnBuildTargetDidChangeNotification( changes: affectedTargets.map {