diff --git a/TableProMobile/TableProMobileTests/SSH/ConnectionPromptQueueTests.swift b/TableProMobile/TableProMobileTests/SSH/ConnectionPromptQueueTests.swift index 6dd2639e2a..0418223f12 100644 --- a/TableProMobile/TableProMobileTests/SSH/ConnectionPromptQueueTests.swift +++ b/TableProMobile/TableProMobileTests/SSH/ConnectionPromptQueueTests.swift @@ -101,6 +101,7 @@ struct ConnectionPromptQueueTests { let second = makePrompt("Second") async let firstAnswer = queue.ask(first) + await waitUntil({ queue.current?.id == first.id }, "the first question should be on screen") async let secondAnswer = queue.ask(second) await waitUntil({ queue.pending.count == 2 }, "both questions should be queued") diff --git a/TableProTests/Views/MenuDisclosureIndicatorTests.swift b/TableProTests/Views/MenuDisclosureIndicatorTests.swift index 9fb485cfcb..5c3c1acf0b 100644 --- a/TableProTests/Views/MenuDisclosureIndicatorTests.swift +++ b/TableProTests/Views/MenuDisclosureIndicatorTests.swift @@ -37,6 +37,11 @@ struct MenuDisclosureIndicatorTests { /// The modifier replaces what the label was providing, with nothing. Four controls in this app /// carried it and were silent to VoiceOver because of it, the result-set chooser among them, /// which is why two suites asserting on its name could never have passed. + /// + /// `.accessibilityElement(children: .contain)` ahead of the label is a different construction: + /// the label then names the container that modifier creates rather than the menu, and that is + /// the one form that names a pull-down whose label draws only an icon. The trailing pane's + /// ellipsis menu relies on it, and `TrailingPaneSurfaceUITests` reads its name on CI. @Test("No menu names itself with accessibilityLabel") func menusCarryTheirNameInTheirLabel() throws { let viewsRoot = Self.repositoryRoot.appendingPathComponent("TablePro/Views") @@ -47,7 +52,9 @@ struct MenuDisclosureIndicatorTests { var inspected = 0 var offenders: [String] = [] for case let url as URL in enumerator where url.pathExtension == "swift" { - let result = Self.scanForMisplacedNames(url, root: Self.repositoryRoot) + guard let source = try? String(contentsOf: url, encoding: .utf8) else { continue } + let relativePath = url.path.replacingOccurrences(of: Self.repositoryRoot.path + "/", with: "") + let result = Self.scanForMisplacedNames(source, relativePath: relativePath) inspected += result.inspected offenders += result.offenders } @@ -59,10 +66,37 @@ struct MenuDisclosureIndicatorTests { ) } - private static func scanForMisplacedNames(_ url: URL, root: URL) -> (inspected: Int, offenders: [String]) { - guard let source = try? String(contentsOf: url, encoding: .utf8) else { return (0, []) } + @Test("A label on the menu itself is flagged, a label on a container wrapping it is not") + func scannerTellsTheMenuFromItsContainer() { + let namedMenu = """ + Menu { + Button("Fields") {} + } label: { + Image(systemName: "ellipsis") + } + .menuStyle(.button) + .accessibilityLabel("Options") + """ + let namedContainer = """ + Menu { + Button("Fields") {} + } label: { + Image(systemName: "ellipsis") + } + .menuStyle(.button) + .accessibilityElement(children: .contain) + .accessibilityLabel("Options") + """ + + #expect(Self.scanForMisplacedNames(namedMenu, relativePath: "Menu.swift").offenders == ["Menu.swift:1"]) + #expect(Self.scanForMisplacedNames(namedContainer, relativePath: "Menu.swift").offenders.isEmpty) + } + + private static func scanForMisplacedNames( + _ source: String, + relativePath: String + ) -> (inspected: Int, offenders: [String]) { let lines = source.components(separatedBy: .newlines) - let relativePath = url.path.replacingOccurrences(of: root.path + "/", with: "") var inspected = 0 var offenders: [String] = [] @@ -72,9 +106,11 @@ struct MenuDisclosureIndicatorTests { let end = labelBlockEnd(lines, from: index) let chain = lines[end ..< min(end + 14, lines.count)].joined(separator: "\n") guard let modifiers = chain.range(of: ".accessibilityLabel") else { continue } + let precedingModifiers = chain[..