Skip to content

Commit

Permalink
Merge pull request #9 from Kyome22/auto-update
Browse files Browse the repository at this point in the history
Added Unit Test
  • Loading branch information
Kyome22 authored Nov 6, 2024
2 parents 04ab2e0 + 6b76128 commit 4e0fd73
Show file tree
Hide file tree
Showing 15 changed files with 283 additions and 17 deletions.
37 changes: 37 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Test

on:
workflow_dispatch:
push:
branches:
- "main"
paths-ignore:
- "**/README.md"

jobs:
unit-test:
name: Unit Test
runs-on: macos-14
env:
DEVELOPER_DIR: "/Applications/Xcode_16.app/Contents/Developer"

steps:
- uses: actions/checkout@v4

- name: Show Xcode version
run: xcodebuild -version

- name: Run Test
working-directory: ShiftWindowPackages
run: |
xcodebuild -scheme ShiftWindowPackages-Package test \
-destination "platform=macOS,arch=arm64" \
-resultBundlePath TestResults/result_bundle |\
xcpretty -c && exit ${PIPESTATUS[0]}
- name: Archive test results
if: success() || failure()
uses: kishikawakatsumi/xcresulttool@v1
with:
path: TestResults/result_bundle.xcresult
show-passed-tests: false
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
# Mac
.DS_Store

# Xcode
xcuserdata/
*.xcuserstate
*.xccheckout

# Swift Package Manager
Packages.resolved
.swiftpm/
.build/
ShiftWindowPackages/Package.resolved

# Test
TestResults/
5 changes: 4 additions & 1 deletion ShiftWindowPackages/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ let package = Package(
),
.testTarget(
name: "DomainTests",
dependencies: ["Domain"],
dependencies: [
"DataLayer",
"Domain",
],
swiftSettings: swiftSettings
),
.target(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
CGDirectDisplayClient.swift
DataLayer

Created by Takuto Nakamura on 2024/11/06.
Copyright 2022 Takuto Nakamura (Kyome22)

Licensed 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 CoreGraphics

public struct CGDirectDisplayClient: DependencyClient {
public var bounds: @Sendable (CGDirectDisplayID) -> CGRect

public static let liveValue = Self(
bounds: { CGDisplayBounds($0) }
)

public static let testValue = Self(
bounds: { _ in CGRect.zero }
)
}
8 changes: 7 additions & 1 deletion ShiftWindowPackages/Sources/Domain/AppDependency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public final class AppDependency: Sendable {
public let shortcutService: ShortcutService

public nonisolated init(
cgDirectDisplayClient: CGDirectDisplayClient = .liveValue,
executeClient: ExecuteClient = .liveValue,
hiServicesClient: HIServicesClient = .liveValue,
loggingSystemClient: LoggingSystemClient = .liveValue,
Expand All @@ -51,13 +52,18 @@ public final class AppDependency: Sendable {
userDefaultsRepository = .init(userDefaultsClient, reset: needsResetUserDefaults)
launchAtLoginRepository = .init(smAppServiceClient)
logService = .init(loggingSystemClient)
shiftService = .init(hiServicesClient, nsAppClient, nsScreenClient, nsWorkspaceClient)
shiftService = .init(cgDirectDisplayClient,
hiServicesClient,
nsAppClient,
nsScreenClient,
nsWorkspaceClient)
shortcutService = .init(userDefaultsRepository, shiftService)
}
}

struct AppDependencyKey: EnvironmentKey {
static let defaultValue = AppDependency(
cgDirectDisplayClient: .testValue,
executeClient: .testValue,
hiServicesClient: .testValue,
loggingSystemClient: .testValue,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,20 @@ import DataLayer
import Foundation

public actor ShiftService {
private let cgDirectDisplayClient: CGDirectDisplayClient
private let hiServicesClient: HIServicesClient
private let nsAppClient: NSAppClient
private let nsScreenClient: NSScreenClient
private let nsWorkspaceClient: NSWorkspaceClient

public init(
_ cgDirectDisplayClient: CGDirectDisplayClient,
_ hiServicesClient: HIServicesClient,
_ nsAppClient: NSAppClient,
_ nsScreenClient: NSScreenClient,
_ nsWorkspaceClient: NSWorkspaceClient
) {
self.cgDirectDisplayClient = cgDirectDisplayClient
self.hiServicesClient = hiServicesClient
self.nsAppClient = nsAppClient
self.nsScreenClient = nsScreenClient
Expand Down Expand Up @@ -61,7 +64,7 @@ public actor ShiftService {

func getValidFrame() async -> CGRect? {
guard let screen = nsScreenClient.mainScreen() else { return nil }
let bounds = CGDisplayBounds(screen.displayID)
let bounds = cgDirectDisplayClient.bounds(screen.displayID)
let visibleFrame = screen.visibleFrame
let menuBarHeight = await MainActor.run {
nsAppClient.mainMenu()?.menuBarHeight
Expand Down Expand Up @@ -125,13 +128,15 @@ public actor ShiftService {
}

// MARK: Get Attribute Names of an AXUIElement
#if DEBUG
func getAttributeNames(element: AXUIElement) -> [String]? {
var ref: CFArray? = nil
guard hiServicesClient.copyAttributeNames(element, &ref) == .success, let ref else {
return nil
}
return ref as [AnyObject] as? [String]
}
#endif

// MARK: Get Window Attributes
func copyAttributeValue(_ element: AXUIElement, attribute: String) -> CFTypeRef? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public actor ShortcutService {
PanelSceneMessenger.request(
panelAction: .open,
with: .shortcutPanel,
userInfo: [String.keyEquivalent: keyCombo.string]
userInfo: [.keyEquivalent: keyCombo.string]
)
}
await shiftService.shiftWindow(shiftType: pattern.shiftType)
Expand All @@ -83,7 +83,7 @@ public actor ShortcutService {
PanelSceneMessenger.request(
panelAction: .open,
with: .shortcutPanel,
userInfo: [String.keyEquivalent: keyCombo.string]
userInfo: [.keyEquivalent: keyCombo.string]
)
}
await shiftService.shiftWindow(shiftType: pattern.shiftType)
Expand Down
4 changes: 3 additions & 1 deletion ShiftWindowPackages/Sources/Domain/String+Extension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ import Foundation

extension String {
public static let shortcutPanel = "shortcutPanel"
public static let keyEquivalent = "keyEquivalent"
public static let kAXFullScreen = "AXFullScreen"
}

extension AnyHashable {
public static let keyEquivalent = "keyEquivalent"
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ public struct ShortcutPanelScene: Scene {

public var body: some Scene {
PanelScene(isPresented: $isPresented, type: ShortcutPanel.self) { userInfo in
let keyEquivalent = (userInfo?[String.keyEquivalent] as? String) ?? ""
ShortcutView(keyEquivalent: keyEquivalent)
if let keyEquivalent = userInfo?[.keyEquivalent] as? String {
ShortcutView(keyEquivalent: keyEquivalent)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ struct MenuView: View {
}

#Preview {
let shiftService = ShiftService(.testValue, .testValue, .testValue, .testValue)
let shiftService = ShiftService(.testValue, .testValue, .testValue, .testValue, .testValue)
MenuView(
executeClient: .testValue,
nsAppClient: .testValue,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ struct ShortcutSettingsView: View {

#Preview {
let userDefaultsRepository = UserDefaultsRepository(.testValue, reset: false)
let shiftService = ShiftService(.testValue, .testValue, .testValue, .testValue)
let shiftService = ShiftService(.testValue, .testValue, .testValue, .testValue, .testValue)
ShortcutSettingsView(
userDefaultsRepository: userDefaultsRepository,
logService: .init(.testValue),
Expand Down
7 changes: 0 additions & 7 deletions ShiftWindowPackages/Tests/DomainTests/DomainTests.swift

This file was deleted.

20 changes: 20 additions & 0 deletions ShiftWindowPackages/Tests/DomainTests/LoggerServiceTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import DataLayer
import os
import XCTest

@testable import Domain

final class LoggerServiceTests: XCTestCase {
func test_bootstrapは一度しか実行されない() async throws {
let count = OSAllocatedUnfairLock(initialState: 0)
var mock: LoggingSystemClient = .testValue
mock.bootstrap = { _ in
count.withLock { $0 += 1 }
}
let sut = LogService(mock)
await sut.bootstrap()
await sut.bootstrap()
let actual = count.withLock { $0 }
XCTAssertEqual(actual, 1)
}
}
130 changes: 130 additions & 0 deletions ShiftWindowPackages/Tests/DomainTests/ShiftServiceTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import DataLayer
import os
import XCTest

@testable import Domain

final class ShiftServiceTests: XCTestCase {
func test_getValidFrame_MainScreenが取得不能_nilが返される() async {
let sut = ShiftService(.testValue, .testValue, .testValue, .testValue, .testValue)
let actual = await sut.getValidFrame()
XCTAssertNil(actual)
}

func test_getValidFrame_MainScreenが取得可能_Dockが下部に存在_有効領域が返される() async {
var nsScreenClient = NSScreenClient.testValue
nsScreenClient.mainScreen = { NSScreenMock(x: 0, y: 0, width: 100, height: 95) }
var cgDirectDisplayClient = CGDirectDisplayClient.testValue
cgDirectDisplayClient.bounds = { _ in CGRect(x: 0, y: 0, width: 100, height: 100) }
var nsAppClient = NSAppClient.testValue
nsAppClient.mainMenu = { NSMenuMock() }
let sut = ShiftService(cgDirectDisplayClient, .testValue, nsAppClient, nsScreenClient, .testValue)
let actual = await sut.getValidFrame()
XCTAssertEqual(actual, CGRect(x: 0, y: 5, width: 100, height: 95))
}

func test_getValidFrame_MainScreenが取得可能_Dockが右側に存在_有効領域が返される() async {
var nsScreenClient = NSScreenClient.testValue
nsScreenClient.mainScreen = { NSScreenMock(x: 0, y: 0, width: 95, height: 95) }
var cgDirectDisplayClient = CGDirectDisplayClient.testValue
cgDirectDisplayClient.bounds = { _ in CGRect(x: 0, y: 0, width: 100, height: 100) }
var nsAppClient = NSAppClient.testValue
nsAppClient.mainMenu = { NSMenuMock() }
let sut = ShiftService(cgDirectDisplayClient, .testValue, nsAppClient, nsScreenClient, .testValue)
let actual = await sut.getValidFrame()
XCTAssertEqual(actual, CGRect(x: 0, y: 5, width: 94, height: 95))
}

func test_getValidFrame_MainScreenが取得可能_Dockが左側に存在_有効領域が返される() async {
var nsScreenClient = NSScreenClient.testValue
nsScreenClient.mainScreen = { NSScreenMock(x: 5, y: 0, width: 95, height: 95) }
var cgDirectDisplayClient = CGDirectDisplayClient.testValue
cgDirectDisplayClient.bounds = { _ in CGRect(x: 0, y: 0, width: 100, height: 100) }
var nsAppClient = NSAppClient.testValue
nsAppClient.mainMenu = { NSMenuMock() }
let sut = ShiftService(cgDirectDisplayClient, .testValue, nsAppClient, nsScreenClient, .testValue)
let actual = await sut.getValidFrame()
XCTAssertEqual(actual, CGRect(x: 6, y: 5, width: 94, height: 95))
}

func test_makeNewFrame_幅が負_nilが返される() async {
let sut = ShiftService(.testValue, .testValue, .testValue, .testValue, .testValue)
let actual = await sut.makeNewFrame(shiftType: .maximize, validFrame: CGRect(x: 0, y: 0, width: -1, height: 0))
XCTAssertNil(actual)
}

func test_makeNewFrame_高さが負_nilが返される() async {
let sut = ShiftService(.testValue, .testValue, .testValue, .testValue, .testValue)
let actual = await sut.makeNewFrame(shiftType: .maximize, validFrame: CGRect(x: 0, y: 0, width: 0, height: -1))
XCTAssertNil(actual)
}

func test_makeNewFrame_上側1/2_領域が返される() async {
let sut = ShiftService(.testValue, .testValue, .testValue, .testValue, .testValue)
let actual = await sut.makeNewFrame(shiftType: .topHalf, validFrame: CGRect(x: 0, y: 0, width: 100, height: 100))
XCTAssertEqual(actual, CGRect(x: 0, y: 0, width: 100, height: 50))
}

func test_makeNewFrame_下側1/2_領域が返される() async {
let sut = ShiftService(.testValue, .testValue, .testValue, .testValue, .testValue)
let actual = await sut.makeNewFrame(shiftType: .bottomHalf, validFrame: CGRect(x: 0, y: 0, width: 100, height: 100))
XCTAssertEqual(actual, CGRect(x: 0, y: 50, width: 100, height: 50))
}

func test_makeNewFrame_左側1/2_領域が返される() async {
let sut = ShiftService(.testValue, .testValue, .testValue, .testValue, .testValue)
let actual = await sut.makeNewFrame(shiftType: .leftHalf, validFrame: CGRect(x: 0, y: 0, width: 100, height: 100))
XCTAssertEqual(actual, CGRect(x: 0, y: 0, width: 50, height: 100))
}

func test_makeNewFrame_右側1/2_領域が返される() async {
let sut = ShiftService(.testValue, .testValue, .testValue, .testValue, .testValue)
let actual = await sut.makeNewFrame(shiftType: .rightHalf, validFrame: CGRect(x: 0, y: 0, width: 100, height: 100))
XCTAssertEqual(actual, CGRect(x: 50, y: 0, width: 50, height: 100))
}

func test_makeNewFrame_左側1/3_領域が返される() async {
let sut = ShiftService(.testValue, .testValue, .testValue, .testValue, .testValue)
let actual = await sut.makeNewFrame(shiftType: .leftThird, validFrame: CGRect(x: 0, y: 0, width: 100, height: 100))
XCTAssertEqual(actual, CGRect(x: 0, y: 0, width: 33, height: 100))
}

func test_makeNewFrame_左側2/3_領域が返される() async {
let sut = ShiftService(.testValue, .testValue, .testValue, .testValue, .testValue)
let actual = await sut.makeNewFrame(shiftType: .leftTwoThirds, validFrame: CGRect(x: 0, y: 0, width: 100, height: 100))
XCTAssertEqual(actual, CGRect(x: 0, y: 0, width: 66, height: 100))
}

func test_makeNewFrame_中央1/3_領域が返される() async {
let sut = ShiftService(.testValue, .testValue, .testValue, .testValue, .testValue)
let actual = await sut.makeNewFrame(shiftType: .middleThird, validFrame: CGRect(x: 0, y: 0, width: 100, height: 100))
XCTAssertEqual(actual, CGRect(x: 33, y: 0, width: 33, height: 100))
}

func test_makeNewFrame_右側2/3_領域が返される() async {
let sut = ShiftService(.testValue, .testValue, .testValue, .testValue, .testValue)
let actual = await sut.makeNewFrame(shiftType: .rightTwoThirds, validFrame: CGRect(x: 0, y: 0, width: 100, height: 100))
XCTAssertEqual(actual, CGRect(x: 33, y: 0, width: 67, height: 100))
}

func test_makeNewFrame_右側1/3_領域が返される() async {
let sut = ShiftService(.testValue, .testValue, .testValue, .testValue, .testValue)
let actual = await sut.makeNewFrame(shiftType: .rightThird, validFrame: CGRect(x: 0, y: 0, width: 100, height: 100))
XCTAssertEqual(actual, CGRect(x: 66, y: 0, width: 34, height: 100))
}
}

fileprivate class NSScreenMock: NSScreen {
let _visibleFrame: NSRect

init(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat) {
_visibleFrame = NSRect(x: x, y: y, width: width, height: height)
super.init()
}

override var visibleFrame: NSRect { _visibleFrame }
}

fileprivate class NSMenuMock: NSMenu {
override var menuBarHeight: CGFloat { 5 }
}
Loading

0 comments on commit 4e0fd73

Please sign in to comment.