diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index f5ed17227..185fb23e1 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -325,10 +325,10 @@ 61A53A812B4449F00093BF8A /* WorkspaceDocument+Index.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61A53A802B4449F00093BF8A /* WorkspaceDocument+Index.swift */; }; 661EF7B82BEE215300C3E577 /* ImageFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 661EF7B72BEE215300C3E577 /* ImageFileView.swift */; }; 661EF7BD2BEE215300C3E577 /* LoadingFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 661EF7BC2BEE215300C3E577 /* LoadingFileView.swift */; }; - 669A50512C380C1800304CD8 /* String+escapedWhiteSpaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669A50502C380C1800304CD8 /* String+escapedWhiteSpaces.swift */; }; - 669A50532C380C8E00304CD8 /* Collection+subscript_safe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669A50522C380C8E00304CD8 /* Collection+subscript_safe.swift */; }; 664935422C35A5BC00461C35 /* NSTableViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664935412C35A5BC00461C35 /* NSTableViewWrapper.swift */; }; 6653EE552C34817900B82DE2 /* QuickSearchResultLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6653EE542C34817900B82DE2 /* QuickSearchResultLabel.swift */; }; + 669A50512C380C1800304CD8 /* String+escapedWhiteSpaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669A50502C380C1800304CD8 /* String+escapedWhiteSpaces.swift */; }; + 669A50532C380C8E00304CD8 /* Collection+subscript_safe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669A50522C380C8E00304CD8 /* Collection+subscript_safe.swift */; }; 669BC4082BED306400D1197C /* AnyFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669BC4072BED306400D1197C /* AnyFileView.swift */; }; 66AF6CE22BF17CC300D83C9D /* StatusBarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66AF6CE12BF17CC300D83C9D /* StatusBarViewModel.swift */; }; 66AF6CE42BF17F6800D83C9D /* StatusBarFileInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66AF6CE32BF17F6800D83C9D /* StatusBarFileInfoView.swift */; }; @@ -405,6 +405,9 @@ 6C85BB412C21061A00EB5DEF /* GitHubComment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587B9E3C29301D8F00AC7927 /* GitHubComment.swift */; }; 6C85BB442C210EFD00EB5DEF /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = 6C85BB432C210EFD00EB5DEF /* SwiftUIIntrospect */; }; 6C91D57229B176FF0059A90D /* EditorManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C91D57129B176FF0059A90D /* EditorManager.swift */; }; + 6C9619202C3F27E3009733CE /* ProjectNavigatorUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C96191B2C3F27E3009733CE /* ProjectNavigatorUITests.swift */; }; + 6C9619222C3F27F1009733CE /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C9619212C3F27F1009733CE /* Query.swift */; }; + 6C9619242C3F2809009733CE /* ProjectPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C9619232C3F2809009733CE /* ProjectPath.swift */; }; 6C97EBCC2978760400302F95 /* AcknowledgementsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C97EBCB2978760400302F95 /* AcknowledgementsWindowController.swift */; }; 6CA1AE952B46950000378EAB /* EditorInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CA1AE942B46950000378EAB /* EditorInstance.swift */; }; 6CAAF68A29BC9C2300A1F48A /* (null) in Sources */ = {isa = PBXBuildFile; }; @@ -1015,6 +1018,10 @@ 6C82D6BB29C00CD900495C54 /* FirstResponderPropertyWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstResponderPropertyWrapper.swift; sourceTree = ""; }; 6C82D6C529C012AD00495C54 /* NSApp+openWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSApp+openWindow.swift"; sourceTree = ""; }; 6C91D57129B176FF0059A90D /* EditorManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorManager.swift; sourceTree = ""; }; + 6C96191B2C3F27E3009733CE /* ProjectNavigatorUITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProjectNavigatorUITests.swift; sourceTree = ""; }; + 6C9619212C3F27F1009733CE /* Query.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Query.swift; sourceTree = ""; }; + 6C9619232C3F2809009733CE /* ProjectPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectPath.swift; sourceTree = ""; }; + 6C9619262C3F285C009733CE /* CodeEditTestPlan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = CodeEditTestPlan.xctestplan; sourceTree = ""; }; 6C97EBCB2978760400302F95 /* AcknowledgementsWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcknowledgementsWindowController.swift; sourceTree = ""; }; 6CA1AE942B46950000378EAB /* EditorInstance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorInstance.swift; sourceTree = ""; }; 6CABB1A029C5593800340467 /* SearchPanelView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchPanelView.swift; sourceTree = ""; }; @@ -1676,8 +1683,8 @@ 043C321327E31FF6006AE443 /* CodeEditDocumentController.swift */, 04660F6927E51E5C00477777 /* CodeEditWindowController.swift */, B6152B7F2ADAE421004C6012 /* CodeEditWindowControllerExtensions.swift */, - 4E7F066529602E7B00BB3C12 /* CodeEditSplitViewController.swift */, 0FD96BCD2BEF42530025A697 /* CodeEditWindowController+Toolbar.swift */, + 4E7F066529602E7B00BB3C12 /* CodeEditSplitViewController.swift */, ); path = Controllers; sourceTree = ""; @@ -2707,6 +2714,40 @@ path = NSApplication; sourceTree = ""; }; + 6C96191C2C3F27E3009733CE /* ProjectNavigator */ = { + isa = PBXGroup; + children = ( + 6C96191B2C3F27E3009733CE /* ProjectNavigatorUITests.swift */, + ); + path = ProjectNavigator; + sourceTree = ""; + }; + 6C96191D2C3F27E3009733CE /* NavigatorArea */ = { + isa = PBXGroup; + children = ( + 6C96191C2C3F27E3009733CE /* ProjectNavigator */, + ); + path = NavigatorArea; + sourceTree = ""; + }; + 6C96191E2C3F27E3009733CE /* Features */ = { + isa = PBXGroup; + children = ( + 6C96191D2C3F27E3009733CE /* NavigatorArea */, + ); + path = Features; + sourceTree = ""; + }; + 6C96191F2C3F27E3009733CE /* CodeEditUITests */ = { + isa = PBXGroup; + children = ( + 6C9619232C3F2809009733CE /* ProjectPath.swift */, + 6C9619212C3F27F1009733CE /* Query.swift */, + 6C96191E2C3F27E3009733CE /* Features */, + ); + path = CodeEditUITests; + sourceTree = ""; + }; 6CAAF68F29BCC6F900A1F48A /* WindowCommands */ = { isa = PBXGroup; children = ( @@ -2863,10 +2904,12 @@ children = ( B658FB2E27DA9E0F00EA4DBD /* CodeEdit */, 587B60F329340A8000D5CD8F /* CodeEditTests */, + 6C96191F2C3F27E3009733CE /* CodeEditUITests */, 28052E0129730F2F00F4F90A /* Configs */, B6FF04772B6C08AC002C2C78 /* DefaultThemes */, 58F2EACE292FB2B0004A9BDE /* Documentation.docc */, 2BE487ED28245162003F3F64 /* OpenWithCodeEdit */, + 6C9619262C3F285C009733CE /* CodeEditTestPlan.xctestplan */, 284DC8502978BA2600BF2770 /* .all-contributorsrc */, 283BDCBC2972EEBD002AFF81 /* Package.resolved */, B658FB2D27DA9E0F00EA4DBD /* Products */, @@ -4059,6 +4102,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 6C9619242C3F2809009733CE /* ProjectPath.swift in Sources */, + 6C9619222C3F27F1009733CE /* Query.swift in Sources */, + 6C9619202C3F27E3009733CE /* ProjectNavigatorUITests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4234,6 +4280,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 38; @@ -4250,7 +4297,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_OBJC_BRIDGING_HEADER = "CodeEditUITests/Features/CodeEditUI/CodeEditUITests-Bridging-Header.h"; SWIFT_VERSION = 5.0; TEST_TARGET_NAME = CodeEdit; }; @@ -4425,6 +4471,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 38; @@ -4441,7 +4488,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_OBJC_BRIDGING_HEADER = "CodeEditUITests/Features/CodeEditUI/CodeEditUITests-Bridging-Header.h"; SWIFT_VERSION = 5.0; TEST_TARGET_NAME = CodeEdit; }; @@ -4684,6 +4730,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 38; @@ -4700,7 +4747,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_OBJC_BRIDGING_HEADER = "CodeEditUITests/Features/CodeEditUI/CodeEditUITests-Bridging-Header.h"; SWIFT_VERSION = 5.0; TEST_TARGET_NAME = CodeEdit; }; @@ -5010,6 +5056,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 38; @@ -5026,7 +5073,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_OBJC_BRIDGING_HEADER = "CodeEditUITests/Features/CodeEditUI/CodeEditUITests-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TEST_TARGET_NAME = CodeEdit; @@ -5039,6 +5085,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 38; @@ -5055,7 +5102,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_OBJC_BRIDGING_HEADER = "CodeEditUITests/Features/CodeEditUI/CodeEditUITests-Bridging-Header.h"; SWIFT_VERSION = 5.0; TEST_TARGET_NAME = CodeEdit; }; diff --git a/CodeEdit.xcodeproj/xcshareddata/xcschemes/CodeEdit.xcscheme b/CodeEdit.xcodeproj/xcshareddata/xcschemes/CodeEdit.xcscheme index c6868c5f6..0fe688535 100644 --- a/CodeEdit.xcodeproj/xcshareddata/xcschemes/CodeEdit.xcscheme +++ b/CodeEdit.xcodeproj/xcshareddata/xcschemes/CodeEdit.xcscheme @@ -1,7 +1,7 @@ + version = "1.7"> @@ -27,6 +27,12 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> + + + + @@ -100,6 +106,16 @@ + + + + : View { - @Environment(\.controlActiveState) - private var activeState - @Binding var items: [Tab] @Binding var selection: Tab? @@ -78,14 +75,14 @@ struct AreaTabBar: View { ? AnyLayout(HStackLayout(spacing: 0)) : AnyLayout(VStackLayout(spacing: 0)) layout { - ForEach(items) { icon in - makeIcon(tab: icon, size: size) + ForEach(items) { tab in + makeIcon(tab: tab, size: size) .offset( - x: (position == .top) ? (tabOffsets[icon] ?? 0) : 0, - y: (position == .side) ? (tabOffsets[icon] ?? 0) : 0 + x: (position == .top) ? (tabOffsets[tab] ?? 0) : 0, + y: (position == .side) ? (tabOffsets[tab] ?? 0) : 0 ) - .background(makeTabItemGeometryReader(tab: icon)) - .simultaneousGesture(makeAreaTabDragGesture(tab: icon)) + .background(makeTabItemGeometryReader(tab: tab)) + .simultaneousGesture(makeAreaTabDragGesture(tab: tab)) } if position == .side { Spacer() @@ -115,6 +112,9 @@ struct AreaTabBar: View { ) ) ) + .focusable(false) + .accessibilityIdentifier("TabAreaTab-\(tab.title)") + .accessibilityLabel(tab.title) } private func makeAreaTabDragGesture(tab: Tab) -> some Gesture { diff --git a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorFileTabCloseButton.swift b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorFileTabCloseButton.swift index ed7a85926..a53d4d1e6 100644 --- a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorFileTabCloseButton.swift +++ b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorFileTabCloseButton.swift @@ -16,6 +16,7 @@ struct EditorFileTabCloseButton: View { var closeAction: () -> Void @Binding var closeButtonGestureActive: Bool var item: CEWorkspaceFile + @Binding var isHoveringClose: Bool @State private var isDocumentEdited: Bool = false @State private var id: Int = 0 @@ -27,7 +28,8 @@ struct EditorFileTabCloseButton: View { isDragging: isDragging, closeAction: closeAction, closeButtonGestureActive: $closeButtonGestureActive, - isDocumentEdited: isDocumentEdited + isDocumentEdited: isDocumentEdited, + isHoveringClose: $isHoveringClose ) .id(id) // Detects if file document changed, when this view created item.fileDocument is nil diff --git a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabBackground.swift b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabBackground.swift index 7e922db1a..5330a85dc 100644 --- a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabBackground.swift +++ b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabBackground.swift @@ -27,36 +27,42 @@ struct EditorTabBackground: View { var body: some View { ZStack { - // Content background (visible if active) - EffectView(.contentBackground) - .opacity(isActive ? 1 : 0) + if isActive { + // Content background (visible if active) + EffectView(.contentBackground) + .opacity(isActive ? 1 : 0) - // Accent color (visible if active) - Color(.controlAccentColor) - .hueRotation(.degrees(-5)) - .opacity( - isActive - ? colorScheme == .dark - ? activeState == .inactive ? 0.22 : inHoldingState ? 0.33 : 0.26 - : activeState == .inactive ? 0.1 : inHoldingState ? 0.27 : 0.2 - : 0 - ) - .saturation(isActiveEditor ? 1.0 : 0.0) + // Accent color (visible if active) + Color(.controlAccentColor) + .hueRotation(.degrees(-5)) + .opacity( + colorScheme == .dark + ? activeState == .inactive ? 0.22 : inHoldingState ? 0.33 : 0.26 + : activeState == .inactive ? 0.1 : inHoldingState ? 0.27 : 0.2 + ) + .saturation(isActiveEditor ? 1.0 : 0.0) + } - // Highlight (if in dark mode) - Color(.white) - .blendMode(.plusLighter) - .opacity( - colorScheme == .dark - ? isActive - ? activeState == .inactive ? 0.04 : inHoldingState ? 0.14 : 0.09 - : isPressing ? 0.05 : 0 - : 0 - ) + if colorScheme == .dark { + // Highlight (if in dark mode) + Color(.white) + .blendMode(.plusLighter) + .opacity( + isActive + ? activeState == .inactive ? 0.04 : inHoldingState ? 0.14 : 0.09 + : isPressing ? 0.05 : 0 + ) + } - // Dragging color (if not active) - Color(.unemphasizedSelectedTextBackgroundColor) - .opacity(isDragging && !isActive ? 0.85 : 0) + if isDragging && !isActive { + // Dragging color (if not active) + Color(.unemphasizedSelectedTextBackgroundColor) + .opacity(0.85) + } + + if !isActive && isPressing { + Color(.unemphasizedSelectedTextBackgroundColor) + } } } } diff --git a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabCloseButton.swift b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabCloseButton.swift index 0a69d0f56..1e4fba468 100644 --- a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabCloseButton.swift +++ b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabCloseButton.swift @@ -19,7 +19,7 @@ struct EditorTabCloseButton: View { var colorScheme @State private var isPressingClose: Bool = false - @State private var isHoveringClose: Bool = false + @Binding var isHoveringClose: Bool let buttonSize: CGFloat = 16 @@ -40,19 +40,7 @@ struct EditorTabCloseButton: View { ) } .frame(width: buttonSize, height: buttonSize) - .background( - colorScheme == .dark - ? Color(nsColor: .white) - .opacity(isPressingClose ? 0.10 : isHoveringClose ? 0.05 : 0) - : ( - Color(nsColor: isActive ? .controlAccentColor : .black) - .opacity( - isPressingClose - ? 0.25 - : (isHoveringClose ? (isActive ? 0.10 : 0.06) : 0) - ) - ) - ) + .background(backgroundColor) .foregroundColor(isPressingClose ? .primary : .secondary) .clipShape(RoundedRectangle(cornerRadius: 2)) .contentShape(Rectangle()) @@ -78,24 +66,55 @@ struct EditorTabCloseButton: View { .onHover { hover in isHoveringClose = hover } + .accessibilityAddTraits(.isButton) .accessibilityLabel(Text("Close")) // Only show when the mouse is hovering and there is no tab dragging. .opacity((isHoveringTab || isDocumentEdited == true) && !isDragging ? 1 : 0) .animation(.easeInOut(duration: 0.08), value: isHoveringTab) .padding(.leading, 4) } -} -struct EditorTabCloseButton_Previews: PreviewProvider { - @State static var closeButtonGestureActive = true + @ViewBuilder var backgroundColor: some View { + if colorScheme == .dark { + let opacity: Double = if isPressingClose { + 0.10 + } else if isHoveringClose { + 0.05 + } else { + 0 + } - static var previews: some View { - EditorTabCloseButton( - isActive: false, - isHoveringTab: false, - isDragging: false, - closeAction: { print("Close tab") }, - closeButtonGestureActive: $closeButtonGestureActive - ) + Color(nsColor: .white) + .opacity(opacity) + } else { + let opacity: Double = if isPressingClose { + 0.25 + } else if isHoveringClose { + if isActive { + 0.10 + } else { + 0.06 + } + } else { + 0.0 + } + + Color(nsColor: isActive ? .controlAccentColor : .systemGray) + .opacity(opacity) + } } } + +#Preview { + @State var closeButtonGestureActive: Bool = false + @State var isHoveringClose: Bool = false + + return EditorTabCloseButton( + isActive: false, + isHoveringTab: false, + isDragging: false, + closeAction: { print("Close tab") }, + closeButtonGestureActive: $closeButtonGestureActive, + isHoveringClose: $isHoveringClose + ) +} diff --git a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabView.swift b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabView.swift index 2299c20e0..7e46b7879 100644 --- a/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabView.swift +++ b/CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabView.swift @@ -139,10 +139,10 @@ struct EditorTabView: View { ) .lineLimit(1) } - .frame( - // To max-out the parent (tab bar) area. - maxHeight: .infinity - ) + .frame(maxHeight: .infinity) // To max-out the parent (tab bar) area. + .accessibilityElement(children: .ignore) + .accessibilityAddTraits(.isStaticText) + .accessibilityLabel(item.name) .padding(.horizontal, 20) .overlay { ZStack { @@ -153,7 +153,8 @@ struct EditorTabView: View { isDragging: draggingTabId != nil || onDragTabId != nil, closeAction: closeAction, closeButtonGestureActive: $closeButtonGestureActive, - item: item + item: item, + isHoveringClose: $isHoveringClose ) } .frame(maxWidth: .infinity, alignment: .leading) @@ -165,9 +166,7 @@ struct EditorTabView: View { : isActive ? 0.6 : 0.4 ) EditorTabDivider() - .opacity( - (isActive || inHoldingState) ? 0.0 : 1.0 - ) + .opacity((isActive || inHoldingState) ? 0.0 : 1.0) } .foregroundColor( isActive && isActiveEditor @@ -193,10 +192,8 @@ struct EditorTabView: View { } var body: some View { - Button(action: switchAction) { - ZStack { - content - } + // We don't use a button here so that accessibility isn't broken. + content .background { EditorTabBackground(isActive: isActive, isPressing: isPressing, isDragging: isDragging) .animation(.easeInOut(duration: 0.08), value: isHovering) @@ -207,34 +204,34 @@ struct EditorTabView: View { // onDragTabId = item.tabID // return .init(object: NSString(string: "\(item.tabID)")) // }) - } - .buttonStyle(EditorTabButtonStyle(isPressing: $isPressing)) - .simultaneousGesture( - TapGesture(count: 2) - .onEnded { _ in - if isTemporary { - editor.temporaryTab = nil + // } + .simultaneousGesture( + DragGesture(minimumDistance: 0) // simultaneousGesture means this won't move the view. + .onChanged({ _ in + if !isHoveringClose { + isPressing = true + } + }) + .onEnded({ _ in + if isPressing { + switchAction() + } + isPressing = false + }) + ) + .simultaneousGesture( + TapGesture(count: 2) + .onEnded { _ in + if isTemporary { + editor.temporaryTab = nil + } } - } - ) - // This padding is to avoid background color overlapping with top divider. - .padding(.top, 1) -// .offset( -// x: isAppeared ? 0 : -14, -// y: 0 -// ) -// .opacity(isAppeared && onDragTabId != item.id ? 1.0 : 0.0) - .zIndex( - isActive - ? 2 - : (isDragging ? 3 : (isPressing ? 1 : 0)) - ) - .onAppear { - withAnimation(.easeOut(duration: 0.20)) { -// isAppeared = true - } - } - .id(item.id) - .tabBarContextMenu(item: item, isTemporary: isTemporary) + ) + // This padding is to avoid background color overlapping with top divider. + .padding(.top, 1) + .zIndex(isActive ? 2 : (isDragging ? 3 : (isPressing ? 1 : 0))) + .id(item.id) + .tabBarContextMenu(item: item, isTemporary: isTemporary) + .accessibilityElement(children: .contain) } } diff --git a/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarView.swift b/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarView.swift index 50616e1d0..5f289cfd7 100644 --- a/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarView.swift +++ b/CodeEdit/Features/Editor/TabBar/Views/EditorTabBarView.swift @@ -16,6 +16,9 @@ struct EditorTabBarView: View { HStack(alignment: .center, spacing: 0) { EditorTabBarLeadingAccessories() EditorTabs() + .accessibilityElement(children: .contain) + .accessibilityLabel("Tab Bar") + .accessibilityIdentifier("TabBar") EditorTabBarTrailingAccessories() } .frame(height: EditorTabBarView.height) diff --git a/CodeEdit/Features/InspectorArea/Views/InspectorAreaView.swift b/CodeEdit/Features/InspectorArea/Views/InspectorAreaView.swift index b46742f64..2baa2f081 100644 --- a/CodeEdit/Features/InspectorArea/Views/InspectorAreaView.swift +++ b/CodeEdit/Features/InspectorArea/Views/InspectorAreaView.swift @@ -71,5 +71,7 @@ struct InspectorAreaView: View { } } .formStyle(.grouped) + .accessibilityElement(children: .contain) + .accessibilityLabel("inspector") } } diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorTableViewCell.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorTableViewCell.swift index 538e41492..64dab481f 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorTableViewCell.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorTableViewCell.swift @@ -29,7 +29,7 @@ final class ProjectNavigatorTableViewCell: FileSystemTableViewCell { delegate: OutlineTableViewCellDelegate? = nil ) { super.init(frame: frameRect, item: item, isEditable: isEditable) - + self.label.setAccessibilityIdentifier("ProjectNavigatorTableViewCell-\(item?.name ?? "")") self.delegate = delegate } diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift index df961ef06..2761c2382 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift @@ -70,6 +70,9 @@ final class ProjectNavigatorViewController: NSViewController { self.outlineView.menu?.delegate = self self.outlineView.doubleAction = #selector(onItemDoubleClicked) + self.outlineView.setAccessibilityIdentifier("ProjectNavigator") + self.outlineView.setAccessibilityLabel("Project Navigator") + let column = NSTableColumn(identifier: .init(rawValue: "Cell")) column.title = "Cell" outlineView.addTableColumn(column) diff --git a/CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift b/CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift index 074985d38..8feffa6ce 100644 --- a/CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift +++ b/CodeEdit/Features/NavigatorArea/Views/NavigatorAreaView.swift @@ -61,5 +61,7 @@ struct NavigatorAreaView: View { } } .environmentObject(workspace) + .accessibilityElement(children: .contain) + .accessibilityLabel("navigator") } } diff --git a/CodeEdit/WorkspaceView.swift b/CodeEdit/WorkspaceView.swift index 8c4dcb35d..b26d9f4c3 100644 --- a/CodeEdit/WorkspaceView.swift +++ b/CodeEdit/WorkspaceView.swift @@ -91,6 +91,8 @@ struct WorkspaceView: View { } } .background(EffectView(.contentBackground)) + .accessibilityElement(children: .contain) + .accessibilityLabel("workspace area") } } } diff --git a/CodeEditTestPlan.xctestplan b/CodeEditTestPlan.xctestplan new file mode 100644 index 000000000..a8dbd06ac --- /dev/null +++ b/CodeEditTestPlan.xctestplan @@ -0,0 +1,58 @@ +{ + "configurations" : [ + { + "id" : "54F3AF04-2CB5-4C73-888F-75ABD9B93999", + "name" : "Configuration 1", + "options" : { + + } + } + ], + "defaultOptions" : { + "codeCoverage" : false, + "targetForVariableExpansion" : { + "containerPath" : "container:CodeEdit.xcodeproj", + "identifier" : "B658FB2B27DA9E0F00EA4DBD", + "name" : "CodeEdit" + } + }, + "testTargets" : [ + { + "skippedTests" : [ + "CodeEditUIUnitTests", + "CodeEditUIUnitTests\/testBranchPickerDark()", + "CodeEditUIUnitTests\/testBranchPickerLight()", + "CodeEditUIUnitTests\/testEffectViewDark()", + "CodeEditUIUnitTests\/testEffectViewLight()", + "CodeEditUIUnitTests\/testFontPickerViewDark()", + "CodeEditUIUnitTests\/testFontPickerViewLight()", + "CodeEditUIUnitTests\/testHelpButtonDark()", + "CodeEditUIUnitTests\/testHelpButtonLight()", + "CodeEditUIUnitTests\/testSegmentedControlDark()", + "CodeEditUIUnitTests\/testSegmentedControlLight()", + "CodeEditUIUnitTests\/testSegmentedControlProminentDark()", + "CodeEditUIUnitTests\/testSegmentedControlProminentLight()", + "WelcomeModuleUnitTests", + "WelcomeModuleUnitTests\/testRecentJSFileDarkSnapshot()", + "WelcomeModuleUnitTests\/testRecentJSFileLightSnapshot()", + "WelcomeModuleUnitTests\/testRecentProjectItemDarkSnapshot()", + "WelcomeModuleUnitTests\/testRecentProjectItemLightSnapshot()", + "WelcomeModuleUnitTests\/testWelcomeActionViewDarkSnapshot()", + "WelcomeModuleUnitTests\/testWelcomeActionViewLightSnapshot()" + ], + "target" : { + "containerPath" : "container:CodeEdit.xcodeproj", + "identifier" : "B658FB3C27DA9E1000EA4DBD", + "name" : "CodeEditTests" + } + }, + { + "target" : { + "containerPath" : "container:CodeEdit.xcodeproj", + "identifier" : "B658FB4627DA9E1000EA4DBD", + "name" : "CodeEditUITests" + } + } + ], + "version" : 1 +} diff --git a/CodeEditUITests/Features/NavigatorArea/ProjectNavigator/ProjectNavigatorUITests.swift b/CodeEditUITests/Features/NavigatorArea/ProjectNavigator/ProjectNavigatorUITests.swift new file mode 100644 index 000000000..99a1c1128 --- /dev/null +++ b/CodeEditUITests/Features/NavigatorArea/ProjectNavigator/ProjectNavigatorUITests.swift @@ -0,0 +1,56 @@ +// +// ProjectNavigatorUITests.swift +// CodeEditTests +// +// Created by Khan Winter on 7/9/24. +// + +import XCTest + +final class ProjectNavigatorUITests: XCTestCase { + + var application: XCUIApplication! + + override func setUp() { + application = XCUIApplication() + application.launchArguments = ["--open", projectPath()] + application.launch() + } + + func testNavigatorOpenFilesAndFolder() { + let window = Query.getWindow(application) + // Focus the window + window.toolbars.firstMatch.click() + + // Get the navigator + let navigator = Query.Window.getNavigator(window) + + // Open the README.md + let readmeRow = Query.Navigator.getProjectNavigatorRow(fileTitle: "README.md", navigator) + XCTAssertFalse(Query.Navigator.rowContainsDisclosureIndicator(readmeRow), "File has disclosure indicator") + readmeRow.click() + + let tabBar = Query.Window.getTabBar(window) + let readmeTab = Query.TabBar.getTab(labeled: "README.md", tabBar) + XCTAssertTrue(readmeTab.exists) + + let rowCount = navigator.descendants(matching: .outlineRow).count + + // Open a folder + let codeEditFolderRow = Query.Navigator.getProjectNavigatorRow(fileTitle: "CodeEdit", index: 1, navigator) + XCTAssertTrue( + Query.Navigator.rowContainsDisclosureIndicator(codeEditFolderRow), + "Folder doesn't have disclosure indicator" + ) + let folderDisclosureIndicator = Query.Navigator.disclosureIndicatorForRow(codeEditFolderRow) + folderDisclosureIndicator.click() + + let newRowCount = navigator.descendants(matching: .outlineRow).count + XCTAssertTrue(newRowCount > rowCount, "No new rows were loaded after opening the folder") + + folderDisclosureIndicator.click() + 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") + } +} diff --git a/CodeEditUITests/ProjectPath.swift b/CodeEditUITests/ProjectPath.swift new file mode 100644 index 000000000..272adc42a --- /dev/null +++ b/CodeEditUITests/ProjectPath.swift @@ -0,0 +1,18 @@ +// +// ProjectPath.swift +// CodeEditUITests +// +// Created by Khan Winter on 7/10/24. +// + +import Foundation + +func projectPath() -> String { + return String( + URL(fileURLWithPath: #filePath) + .pathComponents + .prefix(while: { $0 != "CodeEditUITests" }) + .joined(separator: "/") + .dropFirst() + ) +} diff --git a/CodeEditUITests/Query.swift b/CodeEditUITests/Query.swift new file mode 100644 index 000000000..adb0ec2e0 --- /dev/null +++ b/CodeEditUITests/Query.swift @@ -0,0 +1,55 @@ +// +// Query.swift +// CodeEditUITests +// +// Created by Khan Winter on 7/10/24. +// + +import XCTest + +enum Query { + static func getWindow(_ application: XCUIApplication) -> XCUIElement { + let window = application.windows["CodeEdit"] + XCTAssertTrue(window.exists, "Window not found") + return window + } + + enum Window { + static func getNavigator(_ window: XCUIElement) -> XCUIElement { + let navigator = window.descendants(matching: .any).matching(identifier: "ProjectNavigator").element + XCTAssertTrue(navigator.exists, "Navigator not found") + return navigator + } + + static func getTabBar(_ window: XCUIElement) -> XCUIElement { + let scrollArea = window.descendants(matching: .any).matching(identifier: "TabBar").element + XCTAssertTrue(scrollArea.exists) + return scrollArea + } + } + + enum Navigator { + static func getProjectNavigatorRow(fileTitle: String, index: Int = 0, _ navigator: XCUIElement) -> XCUIElement { + let row = navigator + .descendants(matching: .outlineRow) + .containing(.textField, identifier: "ProjectNavigatorTableViewCell-\(fileTitle)") + .element(boundBy: index) + XCTAssertTrue(row.exists) + return row + } + + static func disclosureIndicatorForRow(_ row: XCUIElement) -> XCUIElement { + row.descendants(matching: .disclosureTriangle).element + } + + static func rowContainsDisclosureIndicator(_ row: XCUIElement) -> Bool { + disclosureIndicatorForRow(row).exists + } + } + + enum TabBar { + static func getTab(labeled title: String, _ tabBar: XCUIElement) -> XCUIElement { + tabBar.descendants(matching: .group).containing(NSPredicate(format: "value = %@", title)).firstMatch + } + } +}