Skip to content
Open
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
14 changes: 10 additions & 4 deletions Rectangle/PrefsWindow/Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ extension Defaults {
return try? decoder.decode(Config.self, from: jsonData)
}

static func load(fileUrl: URL) {
static func load(fileUrl: URL, notificationCenter: NotificationCenter = .default) {
guard let dictTransformer = ValueTransformer(forName: NSValueTransformerName(rawValue: MASDictionaryTransformerName)) else { return }

// Size cap: legitimate configs are ~tens of KB; refuse anything that
Expand All @@ -74,19 +74,25 @@ extension Defaults {

for action in WindowAction.active {
let importedShortcut = config.shortcuts[action.name] ?? action.aliasName.flatMap { config.shortcuts[$0] }
if let shortcut = importedShortcut?.toMASSHortcut() {
if let importedShortcut, importedShortcut.keyCode >= 0 {
let shortcut = importedShortcut.toMASSHortcut()
let dictValue = dictTransformer.reverseTransformedValue(shortcut)
UserDefaults.standard.setValue(dictValue, forKey: action.name)
} else {
UserDefaults.standard.removeObject(forKey: action.name)
}
}
for defaultsKey in TodoManager.defaultsKeys {
if let shortcut = config.shortcuts[defaultsKey]?.toMASSHortcut() {
if let importedShortcut = config.shortcuts[defaultsKey], importedShortcut.keyCode >= 0 {
let shortcut = importedShortcut.toMASSHortcut()
let dictValue = dictTransformer.reverseTransformedValue(shortcut)
UserDefaults.standard.setValue(dictValue, forKey: defaultsKey)
} else {
UserDefaults.standard.removeObject(forKey: defaultsKey)
}
}

Notification.Name.configImported.post()
Notification.Name.configImported.post(center: notificationCenter)
}

static func loadFromSupportDir() {
Expand Down
101 changes: 101 additions & 0 deletions RectangleTests/RectangleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,107 @@ class DefaultsExportTests: XCTestCase {
}
}

class ConfigImportTests: XCTestCase {

private static let shortcutKeys = WindowAction.active.map(\.name) + TodoManager.defaultsKeys
private var storedValues = [String: Any]()
private var absentKeys = Set<String>()

override func setUp() {
super.setUp()
for key in Self.shortcutKeys {
if let value = UserDefaults.standard.object(forKey: key) {
storedValues[key] = value
} else {
absentKeys.insert(key)
}
}
}

override func tearDown() {
for key in Self.shortcutKeys {
if let value = storedValues[key] {
UserDefaults.standard.set(value, forKey: key)
} else if absentKeys.contains(key) {
UserDefaults.standard.removeObject(forKey: key)
}
}
storedValues.removeAll()
absentKeys.removeAll()
super.tearDown()
}

func testImportClearsOmittedActiveShortcut() throws {
let action = WindowAction.almostMaximize
store(Shortcut(NSEvent.ModifierFlags.command.rawValue, 10), forKey: action.name)

try loadConfig(shortcuts: [:])

XCTAssertNil(UserDefaults.standard.object(forKey: action.name))
}

func testImportClearsOmittedTodoShortcut() throws {
let defaultsKey = TodoManager.toggleDefaultsKey
store(Shortcut(NSEvent.ModifierFlags.command.rawValue, 11), forKey: defaultsKey)

try loadConfig(shortcuts: [:])

XCTAssertNil(UserDefaults.standard.object(forKey: defaultsKey))
}

func testImportAppliesSuppliedActiveShortcut() throws {
let action = WindowAction.almostMaximize
let importedShortcut = Shortcut(NSEvent.ModifierFlags.command.rawValue, 12)

try loadConfig(shortcuts: [action.name: importedShortcut])

let storedShortcut = try XCTUnwrap(ShortcutCycle.shortcut(for: action))
XCTAssertEqual(storedShortcut.keyCode, importedShortcut.keyCode)
XCTAssertEqual(storedShortcut.modifierFlags.rawValue, importedShortcut.modifierFlags)
}

func testImportUsesActiveShortcutAlias() throws {
let action = WindowAction.leftHalf
let alias = try XCTUnwrap(action.aliasName)
let importedShortcut = Shortcut(NSEvent.ModifierFlags.command.rawValue, 13)

try loadConfig(shortcuts: [alias: importedShortcut])

let storedShortcut = try XCTUnwrap(ShortcutCycle.shortcut(for: action))
XCTAssertEqual(storedShortcut.keyCode, importedShortcut.keyCode)
XCTAssertEqual(storedShortcut.modifierFlags.rawValue, importedShortcut.modifierFlags)
}

func testImportClearsInvalidActiveShortcut() throws {
let action = WindowAction.almostMaximize
store(Shortcut(NSEvent.ModifierFlags.command.rawValue, 14), forKey: action.name)

try loadConfig(shortcuts: [action.name: Shortcut(NSEvent.ModifierFlags.command.rawValue, -1)])

XCTAssertNil(UserDefaults.standard.object(forKey: action.name))
}

private func store(_ shortcut: Shortcut, forKey key: String) {
let transformer = ValueTransformer(forName: NSValueTransformerName(rawValue: MASDictionaryTransformerName))!
let value = transformer.reverseTransformedValue(shortcut.toMASSHortcut())
UserDefaults.standard.set(value, forKey: key)
}

private func loadConfig(shortcuts: [String: Shortcut]) throws {
let config = Config(bundleId: "com.knollsoft.Rectangle",
version: "ConfigImportTests",
shortcuts: shortcuts,
defaults: [:])
let data = try JSONEncoder().encode(config)
let fileURL = FileManager.default.temporaryDirectory
.appendingPathComponent("ConfigImportTests-\(UUID().uuidString).json")
defer { try? FileManager.default.removeItem(at: fileURL) }
try data.write(to: fileURL, options: .atomic)

Defaults.load(fileUrl: fileURL, notificationCenter: NotificationCenter())
}
}

class StackBadgeGeometryTests: XCTestCase {

private let laptopFrame = CGRect(x: 0, y: 0, width: 2336, height: 1510)
Expand Down
Loading