Skip to content

Commit 580b249

Browse files
Fix Task Dropdown Crash (#1956)
### Description - Fixes a crash when opening the task dropdown menu. - Improves accessibility in the activity viewer, dropdowns, and workspace settings window. - Adds UI tests for the dropdown menu and the entire task creation workflow. ### Related Issues - N/A ### Checklist <!--- Add things that are not yet implemented above --> - [x] I read and understood the [contributing guide](https://github.com/CodeEditApp/CodeEdit/blob/main/CONTRIBUTING.md) as well as the [code of conduct](https://github.com/CodeEditApp/CodeEdit/blob/main/CODE_OF_CONDUCT.md) - [x] The issues this PR addresses are related to each other - [x] My changes generate no new warnings - [x] My code builds and runs on my machine - [x] My changes are all related to the related issue above - [x] I documented my code ### Screenshots One of the new UI Tests https://github.com/user-attachments/assets/de6ce034-92b9-475d-b0d2-ddcf05e2518d
1 parent ca35a5d commit 580b249

21 files changed

+330
-78
lines changed

CodeEdit.xcodeproj/project.pbxproj

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@
357357
6C05A8AF284D0CA3007F4EAA /* WorkspaceDocument+Listeners.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C05A8AE284D0CA3007F4EAA /* WorkspaceDocument+Listeners.swift */; };
358358
6C05CF9E2CDE8699006AAECD /* CodeEditSourceEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 6C05CF9D2CDE8699006AAECD /* CodeEditSourceEditor */; };
359359
6C0617D62BDB4432008C9C42 /* LogStream in Frameworks */ = {isa = PBXBuildFile; productRef = 6C0617D52BDB4432008C9C42 /* LogStream */; };
360+
6C07383B2D284ECA0025CBE3 /* TasksMenuUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C07383A2D284ECA0025CBE3 /* TasksMenuUITests.swift */; };
360361
6C08249C2C556F7400A0751E /* TerminalCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C08249B2C556F7400A0751E /* TerminalCache.swift */; };
361362
6C08249E2C55768400A0751E /* UtilityAreaTerminal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C08249D2C55768400A0751E /* UtilityAreaTerminal.swift */; };
362363
6C0824A12C5C0C9700A0751E /* SwiftTerm in Frameworks */ = {isa = PBXBuildFile; productRef = 6C0824A02C5C0C9700A0751E /* SwiftTerm */; };
@@ -402,6 +403,7 @@
402403
6C48D8F72972E5F300D6D205 /* WindowObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C48D8F62972E5F300D6D205 /* WindowObserver.swift */; };
403404
6C4E37F62C73DA5200AEE7B5 /* UtilityAreaTerminalSidebar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4E37F52C73DA5200AEE7B5 /* UtilityAreaTerminalSidebar.swift */; };
404405
6C4E37FC2C73E00700AEE7B5 /* SwiftTerm in Frameworks */ = {isa = PBXBuildFile; productRef = 6C4E37FB2C73E00700AEE7B5 /* SwiftTerm */; };
406+
6C510CB82D2E4639006EBE85 /* XCUITest+waitForNonExistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C510CB72D2E4639006EBE85 /* XCUITest+waitForNonExistence.swift */; };
405407
6C5228B529A868BD00AC48F6 /* Environment+ContentInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C5228B429A868BD00AC48F6 /* Environment+ContentInsets.swift */; };
406408
6C53AAD829A6C4FD00EE9ED6 /* SplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C53AAD729A6C4FD00EE9ED6 /* SplitView.swift */; };
407409
6C578D8129CD294800DC73B2 /* ExtensionActivatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C578D8029CD294800DC73B2 /* ExtensionActivatorView.swift */; };
@@ -1052,6 +1054,7 @@
10521054
66F370332BEE537B00D3B823 /* NonTextFileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonTextFileView.swift; sourceTree = "<group>"; };
10531055
6C049A362A49E2DB00D42923 /* DirectoryEventStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectoryEventStream.swift; sourceTree = "<group>"; };
10541056
6C05A8AE284D0CA3007F4EAA /* WorkspaceDocument+Listeners.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WorkspaceDocument+Listeners.swift"; sourceTree = "<group>"; };
1057+
6C07383A2D284ECA0025CBE3 /* TasksMenuUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TasksMenuUITests.swift; sourceTree = "<group>"; };
10551058
6C08249B2C556F7400A0751E /* TerminalCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalCache.swift; sourceTree = "<group>"; };
10561059
6C08249D2C55768400A0751E /* UtilityAreaTerminal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilityAreaTerminal.swift; sourceTree = "<group>"; };
10571060
6C092ED92A53A58600489202 /* EditorLayout+StateRestoration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EditorLayout+StateRestoration.swift"; sourceTree = "<group>"; };
@@ -1094,6 +1097,7 @@
10941097
6C48D8F32972DB1A00D6D205 /* Env+Window.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Env+Window.swift"; sourceTree = "<group>"; };
10951098
6C48D8F62972E5F300D6D205 /* WindowObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowObserver.swift; sourceTree = "<group>"; };
10961099
6C4E37F52C73DA5200AEE7B5 /* UtilityAreaTerminalSidebar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilityAreaTerminalSidebar.swift; sourceTree = "<group>"; };
1100+
6C510CB72D2E4639006EBE85 /* XCUITest+waitForNonExistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUITest+waitForNonExistence.swift"; sourceTree = "<group>"; };
10971101
6C5228B429A868BD00AC48F6 /* Environment+ContentInsets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Environment+ContentInsets.swift"; sourceTree = "<group>"; };
10981102
6C53AAD729A6C4FD00EE9ED6 /* SplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.swift; sourceTree = "<group>"; };
10991103
6C578D8029CD294800DC73B2 /* ExtensionActivatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtensionActivatorView.swift; sourceTree = "<group>"; };
@@ -2708,14 +2712,14 @@
27082712
618725A22C29EFE200987354 /* Tasks */ = {
27092713
isa = PBXGroup;
27102714
children = (
2715+
B69D3EE22C5F536B005CF43A /* ActiveTaskView.swift */,
2716+
618725A52C29F02500987354 /* DropdownMenuItemStyleModifier.swift */,
2717+
618725A72C29F05500987354 /* OptionMenuItemView.swift */,
27112718
618725A02C29EFCC00987354 /* SchemeDropDownView.swift */,
27122719
618725AA2C29F2C000987354 /* TaskDropDownView.swift */,
2713-
B69D3EE02C5F5357005CF43A /* TaskView.swift */,
2714-
B69D3EE22C5F536B005CF43A /* ActiveTaskView.swift */,
27152720
B69D3EE42C5F54B3005CF43A /* TasksPopoverMenuItem.swift */,
2721+
B69D3EE02C5F5357005CF43A /* TaskView.swift */,
27162722
618725A32C29F00400987354 /* WorkspaceMenuItemView.swift */,
2717-
618725A72C29F05500987354 /* OptionMenuItemView.swift */,
2718-
618725A52C29F02500987354 /* DropdownMenuItemStyleModifier.swift */,
27192723
);
27202724
path = Tasks;
27212725
sourceTree = "<group>";
@@ -2841,6 +2845,22 @@
28412845
name = "Recovered References";
28422846
sourceTree = "<group>";
28432847
};
2848+
6C0738382D284EA20025CBE3 /* ActivityViewer */ = {
2849+
isa = PBXGroup;
2850+
children = (
2851+
6C0738392D284EAE0025CBE3 /* Tasks */,
2852+
);
2853+
path = ActivityViewer;
2854+
sourceTree = "<group>";
2855+
};
2856+
6C0738392D284EAE0025CBE3 /* Tasks */ = {
2857+
isa = PBXGroup;
2858+
children = (
2859+
6C07383A2D284ECA0025CBE3 /* TasksMenuUITests.swift */,
2860+
);
2861+
path = Tasks;
2862+
sourceTree = "<group>";
2863+
};
28442864
6C092EDC2A53A63E00489202 /* Views */ = {
28452865
isa = PBXGroup;
28462866
children = (
@@ -2951,6 +2971,14 @@
29512971
path = Environment;
29522972
sourceTree = "<group>";
29532973
};
2974+
6C510CB62D2E462D006EBE85 /* Extensions */ = {
2975+
isa = PBXGroup;
2976+
children = (
2977+
6C510CB72D2E4639006EBE85 /* XCUITest+waitForNonExistence.swift */,
2978+
);
2979+
path = Extensions;
2980+
sourceTree = "<group>";
2981+
};
29542982
6C6BD6ED29CD123000235D17 /* Extensions */ = {
29552983
isa = PBXGroup;
29562984
children = (
@@ -3007,6 +3035,7 @@
30073035
6C96191E2C3F27E3009733CE /* Features */ = {
30083036
isa = PBXGroup;
30093037
children = (
3038+
6C0738382D284EA20025CBE3 /* ActivityViewer */,
30103039
6C96191D2C3F27E3009733CE /* NavigatorArea */,
30113040
);
30123041
path = Features;
@@ -3016,10 +3045,11 @@
30163045
isa = PBXGroup;
30173046
children = (
30183047
6CFBA54A2C4E168A00E3A914 /* App.swift */,
3019-
6C9619232C3F2809009733CE /* ProjectPath.swift */,
3020-
6C9619212C3F27F1009733CE /* Query.swift */,
3048+
6C510CB62D2E462D006EBE85 /* Extensions */,
30213049
6C96191E2C3F27E3009733CE /* Features */,
30223050
6CFBA54E2C4E182100E3A914 /* Other Tests */,
3051+
6C9619232C3F2809009733CE /* ProjectPath.swift */,
3052+
6C9619212C3F27F1009733CE /* Query.swift */,
30233053
);
30243054
path = CodeEditUITests;
30253055
sourceTree = "<group>";
@@ -4581,10 +4611,12 @@
45814611
isa = PBXSourcesBuildPhase;
45824612
buildActionMask = 2147483647;
45834613
files = (
4614+
6C510CB82D2E4639006EBE85 /* XCUITest+waitForNonExistence.swift in Sources */,
45844615
6C9619242C3F2809009733CE /* ProjectPath.swift in Sources */,
45854616
6CFBA54B2C4E168A00E3A914 /* App.swift in Sources */,
45864617
6CFBA54D2C4E16C900E3A914 /* WindowCloseCommandTests.swift in Sources */,
45874618
6C9619222C3F27F1009733CE /* Query.swift in Sources */,
4619+
6C07383B2D284ECA0025CBE3 /* TasksMenuUITests.swift in Sources */,
45884620
6C9619202C3F27E3009733CE /* ProjectNavigatorUITests.swift in Sources */,
45894621
);
45904622
runOnlyForDeploymentPostprocessing = 0;

CodeEdit/Features/ActivityViewer/ActivityViewer.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,7 @@ struct ActivityViewer: View {
5959
.opacity(0.1)
6060
}
6161
}
62+
.accessibilityElement(children: .contain)
63+
.accessibilityLabel("Activity Viewer")
6264
}
6365
}

CodeEdit/Features/ActivityViewer/Notifications/CECircularProgressView.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ struct CECircularProgressView: View {
5050
.font(.caption)
5151
}
5252
}
53+
.accessibilityElement()
54+
.accessibilityAddTraits(.updatesFrequently)
55+
.accessibilityValue(
56+
progress != nil ? Text(progress!, format: .percent) : Text("working")
57+
)
5358
}
5459
}
5560

CodeEdit/Features/ActivityViewer/Notifications/TaskNotificationView.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ struct TaskNotificationView: View {
5151
.padding(.trailing, 3)
5252
.popover(isPresented: $isPresented, arrowEdge: .bottom) {
5353
TaskNotificationsDetailView(taskNotificationHandler: taskNotificationHandler)
54-
}.onTapGesture {
54+
}
55+
.onTapGesture {
5556
self.isPresented.toggle()
5657
}
5758
}

CodeEdit/Features/ActivityViewer/Tasks/OptionMenuItemView.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import SwiftUI
1010
struct OptionMenuItemView: View {
1111
var label: String
1212
var action: () -> Void
13+
1314
var body: some View {
1415
HStack {
1516
Text(label)
@@ -22,6 +23,11 @@ struct OptionMenuItemView: View {
2223
.onTapGesture {
2324
action()
2425
}
26+
.accessibilityElement()
27+
.accessibilityAction {
28+
action()
29+
}
30+
.accessibilityLabel(label)
2531
}
2632
}
2733

CodeEdit/Features/ActivityViewer/Tasks/SchemeDropDownView.swift

Lines changed: 45 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,18 @@ struct SchemeDropDownView: View {
2121
workspaceSettingsManager.settings.project.projectName
2222
}
2323

24+
/// Resolves the name one step further than `workspaceName`.
25+
var workspaceDisplayName: String {
26+
workspaceName.isEmpty
27+
? (workspaceFileManager?.workspaceItem.fileName() ?? "No Project found")
28+
: workspaceName
29+
}
30+
2431
var body: some View {
2532
HStack(spacing: 6) {
2633
Image(systemName: "folder.badge.gearshape")
2734
.imageScale(.medium)
28-
Text(
29-
workspaceName.isEmpty
30-
? (workspaceFileManager?.workspaceItem.fileName() ?? "No Project found")
31-
: workspaceName
32-
)
35+
Text(workspaceDisplayName)
3336
}
3437
.font(.subheadline)
3538
.padding(.trailing, 11.5)
@@ -54,31 +57,19 @@ struct SchemeDropDownView: View {
5457
self.isHoveringScheme = hovering
5558
})
5659
.instantPopover(isPresented: $isSchemePopOverPresented, arrowEdge: .bottom) {
57-
VStack(alignment: .leading, spacing: 0) {
58-
WorkspaceMenuItemView(
59-
workspaceFileManager: workspaceFileManager,
60-
item: workspaceFileManager?.workspaceItem
61-
)
62-
Divider()
63-
.padding(.vertical, 5)
64-
Group {
65-
OptionMenuItemView(label: "Add Folder...") {
66-
// TODO: Implment Add Folder
67-
print("NOT IMPLEMENTED")
68-
}
69-
OptionMenuItemView(label: "Workspace Settings...") {
70-
NSApp.sendAction(
71-
#selector(CodeEditWindowController.openWorkspaceSettings(_:)), to: nil, from: nil
72-
)
73-
}
74-
}
75-
}
76-
.font(.subheadline)
77-
.padding(5)
78-
.frame(minWidth: 215)
60+
popoverContent
7961
}
8062
.onTapGesture {
81-
self.isSchemePopOverPresented.toggle()
63+
isSchemePopOverPresented.toggle()
64+
}
65+
.accessibilityElement(children: .combine)
66+
.accessibilityAddTraits(.isButton)
67+
.accessibilityIdentifier("SchemeDropdown")
68+
.accessibilityValue(workspaceDisplayName)
69+
.accessibilityLabel("Active Scheme")
70+
.accessibilityHint("Open the active scheme menu")
71+
.accessibilityAction {
72+
isSchemePopOverPresented.toggle()
8273
}
8374
}
8475

@@ -97,6 +88,32 @@ struct SchemeDropDownView: View {
9788
.font(.system(size: 8, weight: .semibold, design: .default))
9889
.padding(.top, 0.5)
9990
}
91+
92+
@ViewBuilder var popoverContent: some View {
93+
VStack(alignment: .leading, spacing: 0) {
94+
WorkspaceMenuItemView(
95+
workspaceFileManager: workspaceFileManager,
96+
item: workspaceFileManager?.workspaceItem
97+
)
98+
Divider()
99+
.padding(.vertical, 5)
100+
Group {
101+
OptionMenuItemView(label: "Add Folder...") {
102+
// TODO: Implment Add Folder
103+
print("NOT IMPLEMENTED")
104+
}
105+
.disabled(true)
106+
OptionMenuItemView(label: "Workspace Settings...") {
107+
NSApp.sendAction(
108+
#selector(CodeEditWindowController.openWorkspaceSettings(_:)), to: nil, from: nil
109+
)
110+
}
111+
}
112+
}
113+
.font(.subheadline)
114+
.padding(5)
115+
.frame(minWidth: 215)
116+
}
100117
}
101118

102119
// #Preview {

CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,21 @@ struct TaskDropDownView: View {
3838
.onHover { hovering in
3939
self.isHoveringTasks = hovering
4040
}
41-
.instantPopover(isPresented: $isTaskPopOverPresented, arrowEdge: .bottom) {
41+
.instantPopover(isPresented: $isTaskPopOverPresented, arrowEdge: .top) {
4242
taskPopoverContent
4343
}
4444
.onTapGesture {
4545
self.isTaskPopOverPresented.toggle()
4646
}
47+
.accessibilityElement(children: .combine)
48+
.accessibilityAddTraits(.isButton)
49+
.accessibilityIdentifier("TaskDropdown")
50+
.accessibilityValue(taskManager.selectedTask?.name ?? "Create Tasks")
51+
.accessibilityLabel("Active Task")
52+
.accessibilityHint("Open the active task menu")
53+
.accessibilityAction {
54+
isTaskPopOverPresented = true
55+
}
4756
}
4857

4958
private var backgroundColor: some View {
@@ -71,7 +80,9 @@ struct TaskDropDownView: View {
7180
VStack(alignment: .leading, spacing: 0) {
7281
if !taskManager.availableTasks.isEmpty {
7382
ForEach(taskManager.availableTasks, id: \.id) { task in
74-
TasksPopoverMenuItem(taskManager: taskManager, task: task)
83+
TasksPopoverMenuItem(taskManager: taskManager, task: task) {
84+
isTaskPopOverPresented = false
85+
}
7586
}
7687
Divider()
7788
.padding(.vertical, 5)

CodeEdit/Features/ActivityViewer/Tasks/TaskView.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,7 @@ struct TaskView: View {
2727
.frame(width: 5, height: 5)
2828
.padding(.trailing, 2.5)
2929
}
30+
.accessibilityElement()
31+
.accessibilityLabel(task.name)
3032
}
3133
}

CodeEdit/Features/ActivityViewer/Tasks/TasksPopoverMenuItem.swift

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77

88
import SwiftUI
99

10+
/// - Note: This view **cannot** use the `dismiss` environment value to dismiss the sheet. It has to negate the boolean
11+
/// value that presented it initially.
12+
/// See ``SwiftUI/View/instantPopover(isPresented:arrowEdge:content:)``
1013
struct TasksPopoverMenuItem: View {
11-
@Environment(\.dismiss)
12-
private var dismiss
13-
1414
@ObservedObject var taskManager: TaskManager
1515
var task: CETask
16+
var dismiss: () -> Void
1617

1718
var body: some View {
1819
HStack(spacing: 5) {
@@ -22,11 +23,12 @@ struct TasksPopoverMenuItem: View {
2223
.padding(.vertical, 4)
2324
.padding(.horizontal, 8)
2425
.modifier(DropdownMenuItemStyleModifier())
25-
.onTapGesture {
26-
taskManager.selectedTaskID = task.id
27-
dismiss()
28-
}
26+
.onTapGesture(perform: selectAction)
2927
.clipShape(RoundedRectangle(cornerRadius: 5))
28+
.accessibilityElement()
29+
.accessibilityLabel(task.name)
30+
.accessibilityAction(.default, selectAction)
31+
.accessibilityAddTraits(taskManager.selectedTaskID == task.id ? [.isSelected] : [])
3032
}
3133

3234
private var selectionIndicator: some View {
@@ -52,4 +54,9 @@ struct TasksPopoverMenuItem: View {
5254
}
5355
}
5456
}
57+
58+
private func selectAction() {
59+
taskManager.selectedTaskID = task.id
60+
dismiss()
61+
}
5562
}

CodeEdit/Features/ActivityViewer/Tasks/WorkspaceMenuItemView.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ struct WorkspaceMenuItemView: View {
3030
.padding(.vertical, 4)
3131
.padding(.horizontal, 8)
3232
.modifier(DropdownMenuItemStyleModifier())
33-
.onTapGesture { }
33+
.onTapGesture { } // add accessibility action when this is filled in
3434
.clipShape(RoundedRectangle(cornerRadius: 5))
35+
.accessibilityElement()
36+
.accessibilityLabel(item?.name ?? "")
3537
}
3638
}
3739

0 commit comments

Comments
 (0)