Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
7 changes: 7 additions & 0 deletions tests/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,13 @@ Two traps:
- **Give each half its own wait.** Sleeping once and then asserting both "X happened" and "Y didn't" makes the presence half flaky. Poll for X, then hold the window for Y.
- **Structural absence needs no window at all.** When the event is gated on a condition the test never sets up, it can't fire regardless of timing. Drop the sleep: poll the positive precondition and the absence holds by construction. A watchdog whose escalation is gated on `command.is_some()` can't escalate with no command, so the test polls for the first render and asserts `!escalated` with no window.

## No Retries

Tests run once. Worktrunk configures no nextest `retries` and writes no retry loops: a test that passes only on a second attempt is a bug report, and retrying it discards the report while leaving the bug. A green suite has to mean the code is green, not that the run's flakes stayed under a retry budget. Fix the flake at its root:

- A racy assertion is a timing bug. Make it deterministic, per Timing Tests above: poll for the event, or drive it causally through a callback.
- Resource pressure is a concurrency bug. Windows process creation intermittently fails with STATUS_DLL_INIT_FAILED (exit `-1073741502`) when many tests spawn git/wt children at once. Bound how many heavy tests run together; that removes the pressure instead of retrying past it.

## Testing with --execute Commands

Use `--yes` to skip interactive prompts in tests. Don't pipe input to stdin.
Expand Down
47 changes: 28 additions & 19 deletions tests/integration_tests/switch_picker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2991,31 +2991,40 @@ fn test_switch_picker_alt_x_keeps_unmerged_branch_row(mut repo: TestRepo) {
repo.run_git(&["checkout", &default_branch]);

let env_vars = repo.test_env_vars();
let result = exec_in_pty_capture_before_abort(
let PickerSession {
child,
_master,
writer,
rx,
mut parser,
} = boot_picker_pty(
wt_bin().to_str().unwrap(),
&["switch", "--branches"],
repo.root_path(),
&env_vars,
&[
("unmerged-orphan", Some("unmerged-orphan")), // filter to the branch
("\x1bx", Some("unmerged-orphan")), // alt-x keeps it: still visible
],
);

assert_valid_abort_exit_code(result.exit_code);
let (list, _preview) = result.panels();
// `list` (cols 0..LIST_WIDTH of every row) includes skim's query-echo prompt
// line `> unmerged-orphan`, which holds the branch name whether or not the row
// survives. So `contains` alone is tautological — assert the name appears at
// least twice (the prompt echo PLUS the data row). A regression that dropped
// the row optimistically would empty the filtered list, leaving only the
// prompt's single occurrence, and fail here.
let occurrences = list.matches("unmerged-orphan").count();
assert!(
occurrences >= 2,
"the unmerged branch-only row survives alt-x — expected the branch name in \
both the prompt echo and a data row, got {occurrences} occurrence(s).\nList:\n{list}"
);
// Filter to the branch-only row, then wait for the cursor (`>`) to land on it.
// A local branch with no worktree carries the `/` gutter, so `/ unmerged-orphan`
// names the data row specifically — skim's query-echo prompt line is
// `> unmerged-orphan` (no gutter), which this gate ignores. Keying off the
// gutter rather than the bare name is what makes the wait robust: under Windows
// CI load the prompt echo trails its keystrokes (the final character can still
// be unrendered once the row is already on screen), and an assertion that
// counted the name across both the prompt and the row flaked when the prompt
// came up a character short.
send_input_awaiting_content(&writer, &rx, &mut parser, "unmerged-orphan", None);
wait_for_cursor_on_row(&rx, &mut parser, "/ unmerged-orphan");

// alt-x: `SafeDelete` refuses to drop an unmerged branch, so the row stays and
// the cursor holds on `/ unmerged-orphan`. A regression that dropped the row
// would empty the filtered list, the `/` data row would never reappear, and
// this wait would time out with a screen dump.
send_input_awaiting_content(&writer, &rx, &mut parser, "\x1bx", None);
wait_for_cursor_on_row(&rx, &mut parser, "/ unmerged-orphan");

let exit_code = abort_and_exit_code(child, writer, rx);
assert_valid_abort_exit_code(exit_code);
}

/// alt-x on a *worktree* row whose branch is unmerged morphs the row to
Expand Down
Loading