Skip to content

Commit 8818cdf

Browse files
Use NSApp.keyWindow Over Global keyWindow (#1819)
1 parent 19b65cd commit 8818cdf

File tree

8 files changed

+194
-24
lines changed

8 files changed

+194
-24
lines changed

CodeEdit.xcodeproj/project.pbxproj

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,8 @@
433433
6CE6226B2A2A1C730013085C /* UtilityAreaTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE6226A2A2A1C730013085C /* UtilityAreaTab.swift */; };
434434
6CE6226E2A2A1CDE0013085C /* NavigatorTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CE6226D2A2A1CDE0013085C /* NavigatorTab.swift */; };
435435
6CED16E42A3E660D000EC962 /* String+Lines.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CED16E32A3E660D000EC962 /* String+Lines.swift */; };
436+
6CFBA54B2C4E168A00E3A914 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CFBA54A2C4E168A00E3A914 /* App.swift */; };
437+
6CFBA54D2C4E16C900E3A914 /* WindowCloseCommandTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CFBA54C2C4E16C900E3A914 /* WindowCloseCommandTests.swift */; };
436438
6CFF967429BEBCC300182D6F /* FindCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CFF967329BEBCC300182D6F /* FindCommands.swift */; };
437439
6CFF967629BEBCD900182D6F /* FileCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CFF967529BEBCD900182D6F /* FileCommands.swift */; };
438440
6CFF967829BEBCF600182D6F /* MainCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CFF967729BEBCF600182D6F /* MainCommands.swift */; };
@@ -1047,6 +1049,8 @@
10471049
6CE6226A2A2A1C730013085C /* UtilityAreaTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilityAreaTab.swift; sourceTree = "<group>"; };
10481050
6CE6226D2A2A1CDE0013085C /* NavigatorTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigatorTab.swift; sourceTree = "<group>"; };
10491051
6CED16E32A3E660D000EC962 /* String+Lines.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Lines.swift"; sourceTree = "<group>"; };
1052+
6CFBA54A2C4E168A00E3A914 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
1053+
6CFBA54C2C4E16C900E3A914 /* WindowCloseCommandTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowCloseCommandTests.swift; sourceTree = "<group>"; };
10501054
6CFF967329BEBCC300182D6F /* FindCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindCommands.swift; sourceTree = "<group>"; };
10511055
6CFF967529BEBCD900182D6F /* FileCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileCommands.swift; sourceTree = "<group>"; };
10521056
6CFF967729BEBCF600182D6F /* MainCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainCommands.swift; sourceTree = "<group>"; };
@@ -2758,9 +2762,11 @@
27582762
6C96191F2C3F27E3009733CE /* CodeEditUITests */ = {
27592763
isa = PBXGroup;
27602764
children = (
2765+
6CFBA54A2C4E168A00E3A914 /* App.swift */,
27612766
6C9619232C3F2809009733CE /* ProjectPath.swift */,
27622767
6C9619212C3F27F1009733CE /* Query.swift */,
27632768
6C96191E2C3F27E3009733CE /* Features */,
2769+
6CFBA54E2C4E182100E3A914 /* Other Tests */,
27642770
);
27652771
path = CodeEditUITests;
27662772
sourceTree = "<group>";
@@ -2813,6 +2819,14 @@
28132819
path = CodeFileDocument;
28142820
sourceTree = "<group>";
28152821
};
2822+
6CFBA54E2C4E182100E3A914 /* Other Tests */ = {
2823+
isa = PBXGroup;
2824+
children = (
2825+
6CFBA54C2C4E16C900E3A914 /* WindowCloseCommandTests.swift */,
2826+
);
2827+
path = "Other Tests";
2828+
sourceTree = "<group>";
2829+
};
28162830
77A01E1A2BB33F1E00F0EA38 /* Views */ = {
28172831
isa = PBXGroup;
28182832
children = (
@@ -4150,6 +4164,8 @@
41504164
buildActionMask = 2147483647;
41514165
files = (
41524166
6C9619242C3F2809009733CE /* ProjectPath.swift in Sources */,
4167+
6CFBA54B2C4E168A00E3A914 /* App.swift in Sources */,
4168+
6CFBA54D2C4E16C900E3A914 /* WindowCloseCommandTests.swift in Sources */,
41534169
6C9619222C3F27F1009733CE /* Query.swift in Sources */,
41544170
6C9619202C3F27E3009733CE /* ProjectNavigatorUITests.swift in Sources */,
41554171
);

CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate, Obs
172172
if workspace?.editorManager?.editorLayout.findSomeEditor(
173173
except: workspace?.editorManager?.activeEditor
174174
) == nil {
175-
NSApp.sendAction(#selector(NSWindow.performClose(_:)), to: nil, from: nil)
175+
NSApp.sendAction(#selector(NSWindow.performClose(_:)), to: NSApp.keyWindow, from: nil)
176176
} else {
177177
workspace?.editorManager?.activeEditor.close()
178178
}

CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceDocument.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ final class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate {
100100
window.setFrame(NSRect(x: 0, y: 0, width: 1400, height: 900), display: true, animate: false)
101101
window.center()
102102
}
103+
104+
window.setAccessibilityIdentifier("workspace")
105+
window.setAccessibilityDocument(self.fileURL?.absoluteString)
106+
103107
self.addWindowController(windowController)
104108

105109
window.makeKeyAndOrderFront(nil)

CodeEdit/Features/WindowCommands/FileCommands.swift

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ struct FileCommands: Commands {
4444
if NSApp.target(forAction: #selector(CodeEditWindowController.closeCurrentTab(_:))) != nil {
4545
NSApp.sendAction(#selector(CodeEditWindowController.closeCurrentTab(_:)), to: nil, from: nil)
4646
} else {
47-
NSApp.sendAction(#selector(NSWindow.performClose(_:)), to: keyWindow, from: nil)
47+
NSApp.sendAction(#selector(NSWindow.performClose(_:)), to: NSApp.keyWindow, from: nil)
4848
}
4949
}
5050
.keyboardShortcut("w")
@@ -57,19 +57,18 @@ struct FileCommands: Commands {
5757
from: nil
5858
)
5959
} else {
60-
NSApp.sendAction(#selector(NSWindow.performClose(_:)), to: keyWindow, from: nil)
60+
NSApp.sendAction(#selector(NSWindow.performClose(_:)), to: NSApp.keyWindow, from: nil)
6161
}
6262
}
6363
.keyboardShortcut("w", modifiers: [.control, .shift, .command])
6464

6565
Button("Close Window") {
66-
NSApp.sendAction(#selector(NSWindow.performClose(_:)), to: keyWindow, from: nil)
66+
NSApp.sendAction(#selector(NSWindow.performClose(_:)), to: NSApp.keyWindow, from: nil)
6767
}
6868
.keyboardShortcut("w", modifiers: [.shift, .command])
6969

7070
Button("Close Workspace") {
71-
guard let keyWindow = NSApplication.shared.keyWindow else { return }
72-
NSApp.sendAction(#selector(NSWindow.performClose(_:)), to: keyWindow, from: nil)
71+
NSApp.sendAction(#selector(NSWindow.performClose(_:)), to: NSApp.keyWindow, from: nil)
7372
}
7473
.keyboardShortcut("w", modifiers: [.control, .option, .command])
7574
.disabled(!(NSApplication.shared.keyWindow?.windowController is CodeEditWindowController))

CodeEditUITests/App.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//
2+
// App.swift
3+
// CodeEditUITests
4+
//
5+
// Created by Khan Winter on 7/21/24.
6+
//
7+
8+
import XCTest
9+
10+
enum App {
11+
static func launchWithCodeEditWorkspace() -> XCUIApplication {
12+
let application = XCUIApplication()
13+
application.launchArguments = ["--open", projectPath()]
14+
application.launch()
15+
return application
16+
}
17+
18+
static func launch() -> XCUIApplication {
19+
let application = XCUIApplication()
20+
application.launch()
21+
return application
22+
}
23+
}

CodeEditUITests/Features/NavigatorArea/ProjectNavigator/ProjectNavigatorUITests.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,32 +12,34 @@ final class ProjectNavigatorUITests: XCTestCase {
1212
var application: XCUIApplication!
1313

1414
override func setUp() {
15-
application = XCUIApplication()
16-
application.launchArguments = ["--open", projectPath()]
17-
application.launch()
15+
application = App.launchWithCodeEditWorkspace()
1816
}
1917

2018
func testNavigatorOpenFilesAndFolder() {
2119
let window = Query.getWindow(application)
20+
XCTAssertTrue(window.exists, "Window not found")
2221
// Focus the window
2322
window.toolbars.firstMatch.click()
2423

2524
// Get the navigator
26-
let navigator = Query.Window.getNavigator(window)
25+
let navigator = Query.Window.getProjectNavigator(window)
26+
XCTAssertTrue(navigator.exists, "Navigator not found")
2727

2828
// Open the README.md
2929
let readmeRow = Query.Navigator.getProjectNavigatorRow(fileTitle: "README.md", navigator)
3030
XCTAssertFalse(Query.Navigator.rowContainsDisclosureIndicator(readmeRow), "File has disclosure indicator")
3131
readmeRow.click()
3232

3333
let tabBar = Query.Window.getTabBar(window)
34+
XCTAssertTrue(tabBar.exists)
3435
let readmeTab = Query.TabBar.getTab(labeled: "README.md", tabBar)
3536
XCTAssertTrue(readmeTab.exists)
3637

3738
let rowCount = navigator.descendants(matching: .outlineRow).count
3839

3940
// Open a folder
4041
let codeEditFolderRow = Query.Navigator.getProjectNavigatorRow(fileTitle: "CodeEdit", index: 1, navigator)
42+
XCTAssertTrue(codeEditFolderRow.exists)
4143
XCTAssertTrue(
4244
Query.Navigator.rowContainsDisclosureIndicator(codeEditFolderRow),
4345
"Folder doesn't have disclosure indicator"
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
//
2+
// WindowCloseCommandTests.swift
3+
// CodeEditUITests
4+
//
5+
// Created by Khan Winter on 7/21/24.
6+
//
7+
8+
import XCTest
9+
10+
/// Tests for window closing commands.
11+
/// - Note: feel free to add on in the future and change this test name.
12+
final class WindowCloseCommandTests: XCTestCase {
13+
// swiftier api (expectation(that: , on:, willEqual:) doesn't work :(
14+
let notExistsPredicate = NSPredicate(format: "exists == false")
15+
16+
var application: XCUIApplication!
17+
18+
func testWorkspaceWindowCloses() {
19+
application = App.launchWithCodeEditWorkspace()
20+
let window = Query.getWindow(application)
21+
XCTAssertTrue(window.waitForExistence(timeout: 5.0), "Workspace didn't open")
22+
window.toolbars.firstMatch.click()
23+
24+
let expectation = expectation(for: notExistsPredicate, evaluatedWith: window)
25+
application.typeKey("w", modifierFlags: .command)
26+
wait(for: [expectation], timeout: 5.0)
27+
}
28+
29+
func testWorkspaceTabCloses() {
30+
application = App.launchWithCodeEditWorkspace()
31+
let window = Query.getWindow(application)
32+
XCTAssertTrue(window.waitForExistence(timeout: 5.0), "Workspace didn't open")
33+
34+
window.toolbars.firstMatch.click()
35+
36+
let navigator = Query.Window.getProjectNavigator(window)
37+
let readmeRow = Query.Navigator.getProjectNavigatorRow(fileTitle: "README.md", navigator)
38+
XCTAssertTrue(navigator.exists)
39+
XCTAssertTrue(readmeRow.exists)
40+
readmeRow.click()
41+
42+
let tabBar = Query.Window.getTabBar(window)
43+
XCTAssertTrue(tabBar.exists)
44+
let readmeTab = Query.TabBar.getTab(labeled: "README.md", tabBar)
45+
XCTAssertTrue(readmeTab.exists)
46+
XCTAssertEqual(tabBar.descendants(matching: .group).count, 1)
47+
48+
let tabCloseExpectation = expectation(for: notExistsPredicate, evaluatedWith: readmeTab)
49+
application.typeKey("w", modifierFlags: .command)
50+
wait(for: [tabCloseExpectation], timeout: 5.0)
51+
XCTAssertEqual(tabBar.descendants(matching: .group).count, 0)
52+
53+
let windowCloseExpectation = expectation(for: notExistsPredicate, evaluatedWith: window)
54+
application.typeKey("w", modifierFlags: .command)
55+
wait(for: [windowCloseExpectation], timeout: 5.0)
56+
}
57+
58+
func testWorkspaceClosesWithTabStillOpen() {
59+
application = App.launchWithCodeEditWorkspace()
60+
let window = Query.getWindow(application)
61+
XCTAssertTrue(window.waitForExistence(timeout: 5.0), "Workspace didn't open")
62+
63+
window.toolbars.firstMatch.click()
64+
65+
let navigator = Query.Window.getProjectNavigator(window)
66+
let readmeRow = Query.Navigator.getProjectNavigatorRow(fileTitle: "README.md", navigator)
67+
XCTAssertTrue(navigator.exists)
68+
XCTAssertTrue(readmeRow.exists)
69+
readmeRow.click()
70+
71+
let tabBar = Query.Window.getTabBar(window)
72+
XCTAssertTrue(tabBar.exists)
73+
let readmeTab = Query.TabBar.getTab(labeled: "README.md", tabBar)
74+
XCTAssertTrue(readmeTab.exists)
75+
XCTAssertEqual(tabBar.descendants(matching: .group).count, 1)
76+
77+
let windowCloseExpectation = expectation(for: notExistsPredicate, evaluatedWith: window)
78+
application.typeKey("w", modifierFlags: [.shift, .command])
79+
wait(for: [windowCloseExpectation], timeout: 5.0)
80+
}
81+
82+
func testSettingsWindowCloses() {
83+
application = App.launch()
84+
let window = Query.getSettingsWindow(application)
85+
application.typeKey(",", modifierFlags: .command)
86+
XCTAssertTrue(window.waitForExistence(timeout: 5.0), "Settings didn't open")
87+
88+
let expectation = expectation(for: notExistsPredicate, evaluatedWith: window)
89+
application.typeKey("w", modifierFlags: .command)
90+
wait(for: [expectation], timeout: 5.0)
91+
}
92+
93+
func testWelcomeWindowCloses() {
94+
application = App.launch()
95+
let window = Query.getWelcomeWindow(application)
96+
application.typeKey("1", modifierFlags: [.shift, .command])
97+
XCTAssertTrue(window.waitForExistence(timeout: 5.0), "Welcome didn't open")
98+
99+
let expectation = expectation(for: notExistsPredicate, evaluatedWith: window)
100+
application.typeKey("w", modifierFlags: .command)
101+
wait(for: [expectation], timeout: 5.0)
102+
}
103+
104+
func testAboutWindowCloses() {
105+
application = App.launch()
106+
let window = Query.getAboutWindow(application)
107+
application.typeKey("2", modifierFlags: [.shift, .command])
108+
XCTAssertTrue(window.waitForExistence(timeout: 5.0), "About didn't open")
109+
110+
let expectation = expectation(for: notExistsPredicate, evaluatedWith: window)
111+
application.typeKey("w", modifierFlags: .command)
112+
wait(for: [expectation], timeout: 5.0)
113+
}
114+
115+
}

CodeEditUITests/Query.swift

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,35 +7,46 @@
77

88
import XCTest
99

10+
/// Query helpers for querying for specific UI elements. Organized by category in the app.
11+
/// Queries should not evaluate if an element exists. This allows for tests to expect an element to exist,
12+
/// perform an action, and then wait for that element to exist.
1013
enum Query {
11-
static func getWindow(_ application: XCUIApplication) -> XCUIElement {
12-
let window = application.windows["CodeEdit"]
13-
XCTAssertTrue(window.exists, "Window not found")
14-
return window
14+
static func getWindow(_ application: XCUIApplication, named: String? = nil) -> XCUIElement {
15+
if let named {
16+
return application.windows[named]
17+
} else {
18+
return application.windows.element(matching: .window, identifier: "workspace")
19+
}
20+
}
21+
22+
static func getSettingsWindow(_ application: XCUIApplication) -> XCUIElement {
23+
return application.windows.element(matching: .window, identifier: "settings")
24+
}
25+
26+
static func getWelcomeWindow(_ application: XCUIApplication) -> XCUIElement {
27+
return application.windows.element(matching: .window, identifier: "welcome")
28+
}
29+
30+
static func getAboutWindow(_ application: XCUIApplication) -> XCUIElement {
31+
return application.windows.element(matching: .window, identifier: "about")
1532
}
1633

1734
enum Window {
18-
static func getNavigator(_ window: XCUIElement) -> XCUIElement {
19-
let navigator = window.descendants(matching: .any).matching(identifier: "ProjectNavigator").element
20-
XCTAssertTrue(navigator.exists, "Navigator not found")
21-
return navigator
35+
static func getProjectNavigator(_ window: XCUIElement) -> XCUIElement {
36+
return window.descendants(matching: .any).matching(identifier: "ProjectNavigator").element
2237
}
2338

2439
static func getTabBar(_ window: XCUIElement) -> XCUIElement {
25-
let scrollArea = window.descendants(matching: .any).matching(identifier: "TabBar").element
26-
XCTAssertTrue(scrollArea.exists)
27-
return scrollArea
40+
return window.descendants(matching: .any).matching(identifier: "TabBar").element
2841
}
2942
}
3043

3144
enum Navigator {
3245
static func getProjectNavigatorRow(fileTitle: String, index: Int = 0, _ navigator: XCUIElement) -> XCUIElement {
33-
let row = navigator
46+
return navigator
3447
.descendants(matching: .outlineRow)
3548
.containing(.textField, identifier: "ProjectNavigatorTableViewCell-\(fileTitle)")
3649
.element(boundBy: index)
37-
XCTAssertTrue(row.exists)
38-
return row
3950
}
4051

4152
static func disclosureIndicatorForRow(_ row: XCUIElement) -> XCUIElement {

0 commit comments

Comments
 (0)