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
4 changes: 3 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
- ALWAYS read CONTRIBUTING.md for guidelines on how to run tools
- ALWAYS attempt to add a test case for changed behavior. Get your tests to pass — if you didn't run the tests, your code does not work.
- PREFER integration tests, e.g., at `it/...` over unit tests
- PREFER integration tests, e.g., at `it/...` over unit tests.
- ALWAYS use snapshot tests when running a command in integration tests.
- PREFER running specific tests over running the entire test suite
- ALWAYS run `just test` to run all tests.
- ALWAYS run `uvx prek run -a` at the end of a task.
Expand All @@ -12,4 +13,5 @@
- PREFER let chains (`if let` combined with `&&`) over nested `if let` statements
- PREFER short imports over fully-qualified paths for readability.
- AVOID redundant comments and section separators (e.g., `// --- Section ---`) in test files. Use comments to explain invariants and why something unusual was done, not to narrate code.
- AVOID useless inline comments in tests (e.g., `// Run tests`, `// Accept remaining`, `// Review again`). The code should speak for itself. Only add comments when the intent is non-obvious.
- PREFER function comments over inline comments.
377 changes: 377 additions & 0 deletions crates/karva/tests/it/extensions/snapshot/inline.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use std::io::Write;
use std::process::Stdio;

use insta_cmd::assert_cmd_snapshot;

use crate::common::TestContext;
Expand Down Expand Up @@ -396,3 +399,377 @@ def test_hello():
----- stderr -----
");
}

#[test]
fn test_inline_review_accept_first_then_review_accept_second() {
let context = TestContext::with_file(
"test.py",
r#"
import karva

def test_first():
karva.assert_snapshot("hello", inline="")

def test_second():
karva.assert_snapshot("world", inline="")
"#,
);

let _ = context.command_no_parallel().output();

let mut child = context
.snapshot("review")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("failed to spawn review");
child
.stdin
.take()
.expect("no stdin")
.write_all(b"a\ns\n")
.expect("write failed");
let _ = child.wait_with_output();

let source = context.read_file("test.py");
assert!(
source.contains(r#"inline="hello""#),
"Expected first inline rewritten to 'hello', got:\n{source}"
);
assert!(
source.contains(r#"karva.assert_snapshot("world", inline="")"#),
"Expected second inline still empty, got:\n{source}"
);

let pending = context
.root()
.join("snapshots/test__test_second_inline_8.snap.new");
assert!(pending.exists(), "Expected second .snap.new to still exist");

let mut child = context
.snapshot("review")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("failed to spawn review");
child
.stdin
.take()
.expect("no stdin")
.write_all(b"a\n")
.expect("write failed");
let _ = child.wait_with_output();

let source = context.read_file("test.py");
assert!(
source.contains(r#"inline="hello""#),
"Expected first inline still 'hello', got:\n{source}"
);
assert!(
source.contains(r#"inline="world""#),
"Expected second inline rewritten to 'world', got:\n{source}"
);
assert!(
!context
.root()
.join("snapshots/test__test_first_inline_5.snap.new")
.exists(),
"Expected no pending first snapshot"
);
assert!(
!context
.root()
.join("snapshots/test__test_second_inline_8.snap.new")
.exists(),
"Expected no pending second snapshot"
);
}

#[test]
fn test_inline_review_accept_first_then_rerun_accept_second() {
let context = TestContext::with_file(
"test.py",
r#"
import karva

def test_first():
karva.assert_snapshot("hello", inline="")

def test_second():
karva.assert_snapshot("world", inline="")
"#,
);

let _ = context.command_no_parallel().output();

let mut child = context
.snapshot("review")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("failed to spawn review");
child
.stdin
.take()
.expect("no stdin")
.write_all(b"a\ns\n")
.expect("write failed");
let _ = child.wait_with_output();

let source = context.read_file("test.py");
assert!(
source.contains(r#"inline="hello""#),
"Expected first inline rewritten, got:\n{source}"
);

let _ = context.command_no_parallel().output();

let output = context.snapshot("accept").output().expect("accept failed");
assert!(output.status.success(), "Expected accept to succeed");

let source = context.read_file("test.py");
assert!(
source.contains(r#"inline="hello""#),
"Expected first inline still correct, got:\n{source}"
);
assert!(
source.contains(r#"inline="world""#),
"Expected second inline rewritten to 'world', got:\n{source}"
);
assert!(
!context
.root()
.join("snapshots/test__test_second_inline_8.snap.new")
.exists(),
"Expected no pending second snapshot"
);
}

#[test]
fn test_inline_accept_multiline_shifts_lines() {
let context = TestContext::with_file(
"test.py",
r#"
import karva

def test_first():
karva.assert_snapshot("line1\nline2\nline3", inline="")

def test_second():
karva.assert_snapshot("world", inline="")
"#,
);

let _ = context.command_no_parallel().output();

let mut child = context
.snapshot("review")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("failed to spawn review");
child
.stdin
.take()
.expect("no stdin")
.write_all(b"a\ns\n")
.expect("write failed");
let _ = child.wait_with_output();

let source = context.read_file("test.py");
assert!(
source.contains("inline=\"\"\""),
"Expected first inline rewritten to triple-quoted, got:\n{source}"
);
assert!(
source.contains(r#"karva.assert_snapshot("world", inline="")"#),
"Expected second inline still empty, got:\n{source}"
);

let pending = context
.root()
.join("snapshots/test__test_second_inline_8.snap.new");
assert!(pending.exists(), "Expected second .snap.new to still exist");

let mut child = context
.snapshot("review")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("failed to spawn review");
child
.stdin
.take()
.expect("no stdin")
.write_all(b"a\n")
.expect("write failed");
let _ = child.wait_with_output();

let source = context.read_file("test.py");
assert!(
source.contains(r#"inline="world""#),
"Expected second inline rewritten to 'world', got:\n{source}"
);
assert!(!pending.exists(), "Expected no pending second snapshot");
}

#[test]
fn test_inline_multiline_accept_rerun_duplicate_pending() {
let context = TestContext::with_file(
"test.py",
r#"
import karva

def test_first():
karva.assert_snapshot("line1\nline2\nline3", inline="")

def test_second():
karva.assert_snapshot("world", inline="")
"#,
);

let _ = context.command_no_parallel().output();

let mut child = context
.snapshot("review")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("failed to spawn review");
child
.stdin
.take()
.expect("no stdin")
.write_all(b"a\ns\n")
.expect("write failed");
let _ = child.wait_with_output();

let old_pending = context
.root()
.join("snapshots/test__test_second_inline_8.snap.new");
assert!(old_pending.exists(), "Expected old .snap.new at line 8");

// Re-run: second test now fails at shifted line 12, creating a second .snap.new
let _ = context.command_no_parallel().output();

let new_pending = context
.root()
.join("snapshots/test__test_second_inline_12.snap.new");
assert!(
new_pending.exists(),
"Expected new .snap.new at shifted line 12"
);
assert!(
old_pending.exists(),
"Expected old .snap.new at line 8 to still exist"
);

let output = context.snapshot("accept").output().expect("accept failed");
assert!(output.status.success(), "Expected accept to succeed");

let source = context.read_file("test.py");
assert!(
source.contains(r#"inline="world""#),
"Expected second inline rewritten to 'world', got:\n{source}"
);
assert!(!old_pending.exists(), "Expected old .snap.new removed");
assert!(!new_pending.exists(), "Expected new .snap.new removed");
}

/// Accepting a multiline inline shifts line numbers. `test_third`'s `.snap.new`
/// has a stale line that lands before `test_middle` — `find_inline_argument` must
/// skip `test_middle`'s call and find `test_third`'s.
#[test]
fn test_inline_multiline_accept_does_not_corrupt_intervening_inline() {
let context = TestContext::with_file(
"test.py",
r#"
import karva

def test_first():
karva.assert_snapshot("a\nb\nc", inline="")

def test_middle():
karva.assert_snapshot("fixed", inline="fixed")

def test_third():
karva.assert_snapshot("hello", inline="")
"#,
);

let _ = context.command_no_parallel().output();

let output = context.snapshot("accept").output().expect("accept failed");
assert!(output.status.success(), "Expected accept to succeed");

let source = context.read_file("test.py");
assert!(
source.contains(r#"karva.assert_snapshot("fixed", inline="fixed")"#),
"Middle inline was corrupted! Got:\n{source}"
);
assert!(
source.contains(r#"karva.assert_snapshot("hello", inline="hello")"#),
"Third inline not rewritten correctly! Got:\n{source}"
);
}

/// Same corruption scenario as above, but via review (accept first, skip third,
/// then review again to accept third with stale line number).
#[test]
fn test_inline_multiline_review_does_not_corrupt_intervening_inline() {
let context = TestContext::with_file(
"test.py",
r#"
import karva

def test_first():
karva.assert_snapshot("a\nb\nc", inline="")

def test_middle():
karva.assert_snapshot("fixed", inline="fixed")

def test_third():
karva.assert_snapshot("hello", inline="")
"#,
);

let _ = context.command_no_parallel().output();

let mut child = context
.snapshot("review")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("failed to spawn review");
child
.stdin
.take()
.expect("no stdin")
.write_all(b"a\ns\n")
.expect("write failed");
let _ = child.wait_with_output();

let mut child = context
.snapshot("review")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("failed to spawn review");
child
.stdin
.take()
.expect("no stdin")
.write_all(b"a\n")
.expect("write failed");
let _ = child.wait_with_output();

let source = context.read_file("test.py");
assert!(
source.contains(r#"karva.assert_snapshot("fixed", inline="fixed")"#),
"Middle inline was corrupted by review! Got:\n{source}"
);
assert!(
source.contains(r#"karva.assert_snapshot("hello", inline="hello")"#),
"Third inline not rewritten by review! Got:\n{source}"
);
}
Loading