The board settings sheet — setup leaves the popover for a home a stray click can't dismiss
The popover/sheet split lands: a board-scoped, titled, sectioned sheet on the board window hosts everything setup-shaped, opened from the popover's Board Settings… row and the new Board ▸ Board Settings… menu row. The three existing setup controls relocate — add-git (whose noteFormVisible lines now make the sheet the form-anchored failure surface), branch creation (a standing field; create-and-switch runs the identical settle sequence), and the commit-identity fields (the 2s visibility-scoped poll rides with them). The popover keeps the daily face and its postures; its Pro/mode-none section becomes header + door (.addGit renamed .noRepository). Availability is derived from the section inventory (Pro + mode none or git), so pro-m2's sections can't drift from the doors; the sheet's fields join the caret-chord disable set. The audit suite pins what the free fixture can reach; the sheet's own audit is manual until a tier override is ruled. Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
@@ -5,12 +5,24 @@ import XCTest
|
||||
///
|
||||
/// > **Automated audits are test failures**: Xcode's accessibility audit (`performAccessibilityAudit`)
|
||||
/// > runs in UI tests over every surface — board (trash shown and hidden), card window (Preview, Edit,
|
||||
/// > raw source), welcome, template chooser, board popover.
|
||||
/// > raw source), welcome, template chooser, board popover, board settings sheet.
|
||||
///
|
||||
/// Eight surfaces, eight tests, one audit call each. `performAccessibilityAudit` audits **the app's
|
||||
/// One test per surface, one audit call each. `performAccessibilityAudit` audits **the app's
|
||||
/// currently displayed UI** rather than a subtree, so each test's job is entirely navigation: get the
|
||||
/// surface on screen, then let the audit look at whatever is there.
|
||||
///
|
||||
/// ### The one surface this suite cannot reach, and why it is not fixable here
|
||||
///
|
||||
/// The **board settings sheet** is Pro-only (03-board-ui.md ▸ Board settings sheet;
|
||||
/// `BoardSettingsAvailability`), and the fixture launch has no tier control: `AppModel.currentTier`
|
||||
/// reads `ProEntitlement`, which reads StoreKit, and there is deliberately **no launch argument that
|
||||
/// grants Pro**. Adding one would be worse than the gap it closed — `UITestLaunch` is compiled into
|
||||
/// the shipping binary on purpose ("the thing being audited must be the app that ships"), so a
|
||||
/// `--ui-test-pro` flag would be a subscription bypass anyone could type into Terminal. So the sheet's
|
||||
/// own audit is **deferred to the manual VoiceOver pass** (`AccessibilityVerification.md`), and what
|
||||
/// is automated here instead is the posture that *is* reachable: the free tier's, where the row exists
|
||||
/// and is disabled. See `testBoardSettingsRowIsPresentAndDisabledOnTheFreeFixture`.
|
||||
///
|
||||
/// ### No waiving
|
||||
///
|
||||
/// The audit's issue handler is where a false positive would be excused, and every test here passes
|
||||
@@ -188,4 +200,46 @@ final class AccessibilityAuditTests: XCTestCase {
|
||||
)
|
||||
try app.performAccessibilityAudit()
|
||||
}
|
||||
|
||||
// MARK: - The board settings sheet
|
||||
|
||||
/// **The reachable half of the settings sheet's audit** — the free tier's, which is the tier the
|
||||
/// fixture launch runs in (see this file's header for why there is no Pro fixture and why adding
|
||||
/// one would be a subscription bypass).
|
||||
///
|
||||
/// What it pins is the design's deliberate asymmetry between the sheet's two doors
|
||||
/// (`BoardSettingsAvailability`): the **menu row stays visible and disabled** where the sheet
|
||||
/// cannot exist — standard menu validation, and 11-command-nexus.md's "a command absent here
|
||||
/// doesn't exist" read in the other direction — while the popover's row is *absent* there instead.
|
||||
/// A row that vanished from the menu on the free tier would be the app hiding its own inventory;
|
||||
/// one that was enabled would present an empty sheet.
|
||||
///
|
||||
/// The audit call is the board's, taken with the Board menu closed again: the sheet is what this
|
||||
/// test cannot open, so auditing the surface that *is* on screen keeps the test honest about what
|
||||
/// it checked.
|
||||
@MainActor
|
||||
func testBoardSettingsRowIsPresentAndDisabledOnTheFreeFixture() throws {
|
||||
let app = XCUIApplication.launchedWithFixtureBoard()
|
||||
|
||||
let bar = app.menuBars.firstMatch
|
||||
let boardMenu = bar.menuBarItems["Board"]
|
||||
XCTAssertTrue(
|
||||
boardMenu.waitForExistence(timeout: XCUIApplication.uiTimeout),
|
||||
"the Board menu is missing from the menu bar"
|
||||
)
|
||||
boardMenu.click()
|
||||
|
||||
let row = bar.menuItems["Board Settings…"]
|
||||
XCTAssertTrue(
|
||||
row.waitForExistence(timeout: XCUIApplication.uiTimeout),
|
||||
"Board ▸ Board Settings… is missing — the row ships whether or not this board can open it"
|
||||
)
|
||||
XCTAssertFalse(
|
||||
row.isEnabled,
|
||||
"the free tier has no board settings sheet, so the row must be disabled rather than open an empty one"
|
||||
)
|
||||
app.typeKey(.escape, modifierFlags: [])
|
||||
|
||||
try app.performAccessibilityAudit()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ Have a scratch board to hand for Part 2 — a new one from File ▸ New Board…
|
||||
|
||||
## Part 1 — run the audit suite
|
||||
|
||||
`KanbanUITests/AccessibilityAuditTests.swift` runs Xcode's accessibility audit over all nine surfaces the design names. **Violations are test failures, not warnings**, and nothing is waived: the audits pass no issue handler at all.
|
||||
`KanbanUITests/AccessibilityAuditTests.swift` runs Xcode's accessibility audit over the surfaces the design names — nine of the ten automatically, the tenth (the **board settings sheet**) by hand in Part 3, because it is Pro-only and the fixture launch has no tier control. **Violations are test failures, not warnings**, and nothing is waived: the audits pass no issue handler at all.
|
||||
|
||||
```
|
||||
xcodebuild test -project Kanban.xcodeproj -scheme Kanban \
|
||||
@@ -51,11 +51,23 @@ The nine surfaces, and how each test gets there:
|
||||
| `testWelcomeWindow` | Welcome, with a recents row | Window ▸ Welcome to Lanework |
|
||||
| `testTemplateChooser` | Template chooser | File ▸ New Board… |
|
||||
| `testBoardInfoPopover` | Board popover | File ▸ Board Info |
|
||||
| `testBoardSettingsRowIsPresentAndDisabledOnTheFreeFixture` | Board window, with Board ▸ Board Settings… checked | Opens the Board menu, asserts the row is present and disabled, closes it |
|
||||
|
||||
Every test launches the app with `--ui-test-fixture-board`, which makes the app build a known board inside its own container and open it — three lanes ("To Do", "Doing", "Done"), six cards, one card with a rich Markdown body, an attachment and a three-comment thread (one unattributed, one edited), one card already in the trash. That is the `standard` fixture variant; the bare flag means it, and the other two shapes (`large`, `malformed`) belong to the end-to-end pass. The board and the registry both live in a scratch directory that is wiped on every launch, so an audit run never touches your real boards or your recents list. See `Kanban/App/UITestLaunch.swift` for why the board cannot simply be handed to the app on the command line (the sandbox).
|
||||
|
||||
If a test fails, read the issue's `compactDescription` and fix the app. Adding a waiver is a design change and needs an entry on the Redesign board first.
|
||||
|
||||
### The board settings sheet, by hand
|
||||
|
||||
The sheet (03-board-ui.md ▸ Board settings sheet) needs **Pro on a board with app-managed git**, and there is deliberately no launch argument that grants Pro: `UITestLaunch` is compiled into the shipping binary, so a tier flag would be a subscription bypass anyone could type into Terminal. Until a fixture can reach Pro honestly, run this by hand once per release on a Pro build, with a git-mode board open:
|
||||
|
||||
- [ ] **Open both doors.** Board ▸ Board Settings…, and the popover's **Board Settings…** row (File ▸ Board Info ▸ Git). The popover dismisses as the sheet appears — never both at once.
|
||||
- [ ] **Sectioned, and navigable by heading.** With VoiceOver on, the rotor's heading list holds "Board Settings" and each section's title ("Branch", "Commit Identity"; "Git" on a board with no repository yet).
|
||||
- [ ] **Tab reaches every control** with VoiceOver off and Full Keyboard Access on — the branch name field, Create, both identity fields, Done.
|
||||
- [ ] **⌘Z inside a field is the field's**, not the board's: type into Commit Identity ▸ Name, press ⌘Z, and the *typing* reverts — no tree checkout, no board step consumed (06-history-undo.md ▸ Undo routing).
|
||||
- [ ] **Escape and Done both dismiss**; Escape in a dirty branch-name field clears the field first (one layer per press).
|
||||
- [ ] **The read-only lock disables in place**: the sheet stays open and its controls grey out, with the banner naming why.
|
||||
|
||||
## Part 2 — the VoiceOver smoke script
|
||||
|
||||
Nine steps, in one sitting, on a scratch board. Expected speech is quoted from `Kanban/UI/AccessibilityPhrases.swift`; where VoiceOver adds its own words (role names, "selected", "button") they are shown in [brackets].
|
||||
|
||||
Reference in New Issue
Block a user