Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
48 changes: 40 additions & 8 deletions CodeEdit/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,21 @@ final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
}

for index in 0..<CommandLine.arguments.count {
#if DEBUG
if CommandLine.arguments[index] == "--codeedit-uitest-open-temp-workspace"
&& (index + 1) < CommandLine.arguments.count {
let url = self.prepareUITestWorkspace(id: CommandLine.arguments[index+1])
self.openWorkspace(at: url)
needToHandleOpen = false
continue
}
#endif

if CommandLine.arguments[index] == "--open" && (index + 1) < CommandLine.arguments.count {
let path = CommandLine.arguments[index+1]
let url = URL(fileURLWithPath: path)

CodeEditDocumentController.shared.reopenDocument(
for: url,
withContentsOf: url,
display: true
) { document, _, _ in
document?.windowControllers.first?.synchronizeWindowTitleWithDocumentName()
}

self.openWorkspace(at: url)
needToHandleOpen = false
}
}
Expand All @@ -60,6 +63,35 @@ final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
}
}

private func openWorkspace(at url: URL) {
CodeEditDocumentController.shared.reopenDocument(
for: url,
withContentsOf: url,
display: true
) { document, _, _ in
document?.windowControllers.first?.synchronizeWindowTitleWithDocumentName()
}
}

#if DEBUG
private func prepareUITestWorkspace(id: String) -> URL {
let baseURL = FileManager.default.temporaryDirectory
.appending(path: "CodeEditUITests")
let url = baseURL.appending(path: id)

do {
if FileManager.default.fileExists(atPath: baseURL.path(percentEncoded: false)) {
try FileManager.default.removeItem(at: baseURL)
}
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true)
} catch {
logger.error("Failed to create UI test workspace: \(error.localizedDescription, privacy: .public)")
}
Comment thread
jkaunert marked this conversation as resolved.

return url
}
Comment thread
jkaunert marked this conversation as resolved.
#endif

func applicationWillTerminate(_ aNotification: Notification) {

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,13 @@ struct ProjectNavigatorToolbarBottom: View {
alert.runModal()
}
}
} label: {}
.background {
} label: {
Image(systemName: "plus")
.accessibilityHidden(true)
}
.menuStyle(.borderlessButton)
.menuIndicator(.hidden)
.frame(maxWidth: 18, alignment: .center)
.frame(width: 18, alignment: .center)
.opacity(activeState == .inactive ? 0.45 : 1)
.accessibilityLabel("Add Folder or File")
.accessibilityIdentifier("addButton")
Expand Down
14 changes: 14 additions & 0 deletions CodeEditUITests/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,20 @@ enum App {
return (application, tempDirURL)
}

// Launches CodeEdit with an app-writable directory that CodeEdit creates before opening.
static func launchWithAppWritableTempDir() -> (XCUIApplication, String) {
let tempDirID = appWritableTempProjectID()
let application = XCUIApplication()
application.launchArguments = [
"-ApplePersistenceIgnoreState",
"YES",
"--codeedit-uitest-open-temp-workspace",
tempDirID
]
application.launch()
return (application, tempDirID)
}
Comment thread
jkaunert marked this conversation as resolved.

static func launch() -> XCUIApplication {
let application = XCUIApplication()
application.launchArguments = ["-ApplePersistenceIgnoreState", "YES"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ final class ProjectNavigatorFileManagementUITests: XCTestCase {
override func setUp() async throws {
// MainActor required for async compatibility which is required to make this method throwing
try await MainActor.run {
(app, path) = try App.launchWithTempDir()
if name.contains("testCreateNewFiles") {
(app, path) = App.launchWithAppWritableTempDir()
} else {
(app, path) = try App.launchWithTempDir()
}

window = Query.getWindow(app)
XCTAssertTrue(window.exists, "Window not found")
Expand Down Expand Up @@ -81,29 +85,39 @@ final class ProjectNavigatorFileManagementUITests: XCTestCase {
func testCreateNewFiles() throws {
// Add a few files with the navigator button
for idx in 0..<5 {
let addButton = window.popUpButtons["addButton"]
let previousRowCount = Query.Navigator.getRows(navigator).count
let addButton = Query.Navigator.getAddButton(window)
XCTAssertTrue(addButton.waitForExistence(timeout: 2.0), "Add button not found")
addButton.click()
let addMenu = addButton.menus.firstMatch
addMenu.menuItems["Add File"].click()

let selectedRows = Query.Navigator.getSelectedRows(navigator)
guard selectedRows.firstMatch.waitForExistence(timeout: 0.5) else {
XCTFail("No new selected rows appeared")
return
}
let addFileMenuItem = addButton.menuItems["Add File"]
XCTAssertTrue(addFileMenuItem.waitForExistence(timeout: 2.0), "Add File menu item not found")
addFileMenuItem.click()

let title = idx > 0 ? "untitled\(idx)" : "untitled"
XCTAssertTrue(
Query.Navigator.waitForRowCount(navigator, greaterThan: previousRowCount, timeout: 5.0),
"No new navigator row appeared after adding \(title)"
)

guard let newFileRow = Query.Navigator.waitForProjectNavigatorRow(
fileTitle: title,
navigator,
timeout: 5.0
) else {
XCTFail("\(title) did not appear in the navigator")
return
}

let newFileRow = selectedRows.firstMatch
XCTAssertEqual(newFileRow.descendants(matching: .textField).firstMatch.value as? String, title)

let tabBar = Query.Window.getTabBar(window)
XCTAssertTrue(tabBar.exists)
let readmeTab = Query.TabBar.getTab(labeled: title, tabBar)
XCTAssertTrue(readmeTab.exists)
XCTAssertTrue(tabBar.waitForExistence(timeout: 2.0))
let newFileTab = Query.TabBar.getTab(labeled: title, tabBar)
XCTAssertTrue(newFileTab.waitForExistence(timeout: 2.0))

let newFileEditor = Query.Window.getFirstEditor(window)
XCTAssertTrue(newFileEditor.exists)
XCTAssertTrue(newFileEditor.waitForExistence(timeout: 2.0))
XCTAssertNotNil(newFileEditor.value as? String)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ final class ProjectNavigatorUITests: XCTestCase {

// Open the README.md
let readmeRow = Query.Navigator.getProjectNavigatorRow(fileTitle: "README.md", navigator)
XCTAssertTrue(readmeRow.waitForExistence(timeout: 5.0))
XCTAssertFalse(Query.Navigator.rowContainsDisclosureIndicator(readmeRow), "File has disclosure indicator")
readmeRow.click()

Expand All @@ -42,19 +43,27 @@ final class ProjectNavigatorUITests: XCTestCase {
let rowCount = navigator.descendants(matching: .outlineRow).count

// Open a folder
let codeEditFolderRow = Query.Navigator.getProjectNavigatorRow(fileTitle: "CodeEdit", index: 1, navigator)
XCTAssertTrue(codeEditFolderRow.exists)
let codeEditFolderRow = Query.Navigator.getLastProjectNavigatorRow(fileTitle: "CodeEdit", navigator)
XCTAssertTrue(codeEditFolderRow.waitForExistence(timeout: 5.0))
let folderDisclosureIndicator = Query.Navigator.disclosureIndicatorForRow(codeEditFolderRow)
XCTAssertTrue(
Query.Navigator.rowContainsDisclosureIndicator(codeEditFolderRow),
folderDisclosureIndicator.waitForExistence(timeout: 2.0),
"Folder doesn't have disclosure indicator"
)
let folderDisclosureIndicator = Query.Navigator.disclosureIndicatorForRow(codeEditFolderRow)
folderDisclosureIndicator.click()

XCTAssertTrue(
Query.Navigator.waitForRowCount(navigator, greaterThan: rowCount, timeout: 2.0),
"No new rows were loaded after opening the folder"
)
let newRowCount = navigator.descendants(matching: .outlineRow).count
XCTAssertTrue(newRowCount > rowCount, "No new rows were loaded after opening the folder")

folderDisclosureIndicator.click()
XCTAssertTrue(
Query.Navigator.waitForRowCount(navigator, equalTo: rowCount, timeout: 2.0),
"Rows were not hidden after closing a folder"
)
let finalRowCount = navigator.descendants(matching: .outlineRow).count
XCTAssertTrue(newRowCount > finalRowCount, "Rows were not hidden after closing a folder")
XCTAssertEqual(rowCount, finalRowCount, "Different Number of rows loaded")
Expand Down
4 changes: 4 additions & 0 deletions CodeEditUITests/ProjectPath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ func tempProjectPath() throws -> String {
return path.path(percentEncoded: false)
}

func appWritableTempProjectID() -> String {
makeTempID()
}

func cleanUpTempProjectPaths() throws {
let baseDir = FileManager.default.temporaryDirectory.appending(path: "CodeEditUITests")
try FileManager.default.removeItem(at: baseDir)
Expand Down
77 changes: 74 additions & 3 deletions CodeEditUITests/Query.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,23 +56,94 @@ enum Query {
navigator.descendants(matching: .outlineRow)
}

static func getAddButton(_ window: XCUIElement) -> XCUIElement {
if window.buttons["addButton"].exists {
return window.buttons["addButton"]
}

if window.popUpButtons["addButton"].exists {
return window.popUpButtons["addButton"]
}

return window.descendants(matching: .any).matching(identifier: "addButton").firstMatch
}
Comment thread
jkaunert marked this conversation as resolved.

static func getSelectedRows(_ navigator: XCUIElement) -> XCUIElementQuery {
getRows(navigator).matching(NSPredicate(format: "selected = true"))
}

static func getProjectNavigatorRow(fileTitle: String, index: Int = 0, _ navigator: XCUIElement) -> XCUIElement {
return getRows(navigator)
static func getProjectNavigatorRows(fileTitle: String, _ navigator: XCUIElement) -> XCUIElementQuery {
getRows(navigator)
.containing(.textField, identifier: "ProjectNavigatorTableViewCell-\(fileTitle)")
}

static func getProjectNavigatorRow(fileTitle: String, index: Int = 0, _ navigator: XCUIElement) -> XCUIElement {
return getProjectNavigatorRows(fileTitle: fileTitle, navigator)
.element(boundBy: index)
}

static func waitForProjectNavigatorRow(
fileTitle: String,
index: Int = 0,
_ navigator: XCUIElement,
timeout: TimeInterval
) -> XCUIElement? {
let deadline = Date().addingTimeInterval(timeout)
while Date() < deadline {
let row = getProjectNavigatorRow(fileTitle: fileTitle, index: index, navigator)
if row.exists {
return row
}
RunLoop.current.run(until: Date().addingTimeInterval(0.05))
}

let row = getProjectNavigatorRow(fileTitle: fileTitle, index: index, navigator)
return row.exists ? row : nil
}

static func getLastProjectNavigatorRow(fileTitle: String, _ navigator: XCUIElement) -> XCUIElement {
let matchingRows = getProjectNavigatorRows(fileTitle: fileTitle, navigator)
return matchingRows.element(boundBy: max(matchingRows.count - 1, 0))
}

static func disclosureIndicatorForRow(_ row: XCUIElement) -> XCUIElement {
row.descendants(matching: .disclosureTriangle).element
row.descendants(matching: .disclosureTriangle).firstMatch
}

static func rowContainsDisclosureIndicator(_ row: XCUIElement) -> Bool {
disclosureIndicatorForRow(row).exists
}

static func waitForRowCount(
_ navigator: XCUIElement,
greaterThan rowCount: Int,
timeout: TimeInterval
) -> Bool {
waitForRowCount(navigator, timeout: timeout) { $0 > rowCount }
}

static func waitForRowCount(
_ navigator: XCUIElement,
equalTo rowCount: Int,
timeout: TimeInterval
) -> Bool {
waitForRowCount(navigator, timeout: timeout) { $0 == rowCount }
}

private static func waitForRowCount(
_ navigator: XCUIElement,
timeout: TimeInterval,
predicate: (Int) -> Bool
) -> Bool {
let deadline = Date().addingTimeInterval(timeout)
while Date() < deadline {
if predicate(getRows(navigator).count) {
return true
}
RunLoop.current.run(until: Date().addingTimeInterval(0.05))
}
return predicate(getRows(navigator).count)
}
}

enum TabBar {
Expand Down