tests/testthat/test-tool-git.R

test_that("btw_tool_git_status()", {
  skip_if_not_installed("gert")

  local_temp_git_repo()

  # Empty repo - no changes
  result <- btw_tool_git_status()
  expect_btw_tool_result(result, has_data = FALSE)
  expect_match(result@value, "No changes")

  # Create a new file
  writeLines("test content", "test.txt")

  result <- btw_tool_git_status()
  expect_btw_tool_result(result)
  expect_match(result@value, "test\\.txt")
  expect_snapshot(cli::cat_line(result@value), transform = scrub_git_details)

  # Check staged only (should be empty)
  result_staged_empty <- btw_tool_git_status(include = "staged")
  expect_btw_tool_result(result_staged_empty, has_data = FALSE)
  expect_match(result_staged_empty@value, "No changes")
  expect_snapshot(
    cli::cat_line(result_staged_empty@value),
    transform = scrub_git_details
  )

  # Stage the file
  gert::git_add("test.txt")

  # Check staged only
  result_staged <- btw_tool_git_status(include = "staged")
  expect_match(result_staged@value, "test\\.txt")
  expect_snapshot(
    cli::cat_line(result_staged@value),
    transform = scrub_git_details
  )

  # Check unstaged only (should be empty)
  result_unstaged <- btw_tool_git_status(include = "unstaged")
  expect_btw_tool_result(result_unstaged, has_data = FALSE)
  expect_match(result_unstaged@value, "No changes")

  cat("\nmore content", file = "test.txt", append = TRUE)
  expect_snapshot(
    cli::cat_line(btw_tool_git_status()@value),
    transform = scrub_git_details
  )
  expect_snapshot(
    cli::cat_line(btw_tool_git_status(include = "unstaged")@value),
    transform = scrub_git_details
  )

  gert::git_commit("Add test.txt")
  expect_snapshot(
    cli::cat_line(btw_tool_git_status()@value),
    transform = scrub_git_details
  )

  gert::git_add("test.txt")
  expect_snapshot(
    cli::cat_line(btw_tool_git_status()@value),
    transform = scrub_git_details
  )
})

test_that("btw_tool_git_diff()", {
  skip_if_not_installed("gert")

  local_temp_git_repo()

  # No changes initially
  result <- btw_tool_git_diff()
  expect_btw_tool_result(result, has_data = FALSE)
  expect_match(result@value, "No.*changes")

  # Create and stage a file
  writeLines("line 1", "test.txt")
  gert::git_add("test.txt")
  gert::git_commit("Initial commit")

  # Modify the file
  writeLines(c("line 1", "line 2"), "test.txt")

  # Check unstaged diff (working dir vs index)
  result <- btw_tool_git_diff()
  expect_btw_tool_result(result, has_data = FALSE)
  expect_true(any(grepl("line 2", result@value, fixed = TRUE)))
  expect_snapshot(cli::cat_line(result@value), transform = scrub_git_details)

  # Stage the changes
  gert::git_add("test.txt")

  # Check staged diff (index vs HEAD) - shows both lines being modified from HEAD
  result_staged <- btw_tool_git_diff(ref = "HEAD")
  expect_btw_tool_result(result_staged, has_data = FALSE)
  # The staged diff shows the change from HEAD, which includes adding line 2
  expect_true(any(grepl("@@", result_staged@value, fixed = TRUE))) # Has diff hunks

  # Commit and verify no more unstaged changes
  gert::git_commit("Add line 2")
  result_no_changes <- btw_tool_git_diff()
  expect_btw_tool_result(result_no_changes, has_data = FALSE)
  expect_match(result_no_changes@value, "No.*changes")
})

test_that("btw_tool_git_log()", {
  skip_if_not_installed("gert")

  local_temp_git_repo()

  # Create initial commit
  writeLines("test", "test.txt")
  gert::git_add("test.txt")
  gert::git_commit("Initial commit")

  result <- btw_tool_git_log()
  expect_btw_tool_result(result)
  expect_match(result@value, "Initial commit")
  expect_snapshot(cli::cat_line(result@value), transform = scrub_git_details)

  # Test max parameter
  writeLines("test2", "test2.txt")
  gert::git_add("test2.txt")
  gert::git_commit("Second commit")

  result_limited <- btw_tool_git_log(max = 1)
  expect_btw_tool_result(result_limited)
  expect_match(result_limited@value, "Second commit")
})

test_that("btw_tool_git_commit()", {
  skip_if_not_installed("gert")

  local_temp_git_repo()

  # Create a file
  writeLines("test content", "test.txt")

  # Commit with files parameter
  result <- btw_tool_git_commit(
    message = "Add test file",
    files = "test.txt"
  )
  expect_btw_tool_result(result, has_data = FALSE)
  expect_match(result@value, "Add test file")
  expect_snapshot(cli::cat_line(result@value), transform = scrub_git_details)

  # Verify commit was created
  log <- gert::git_log(max = 1)
  expect_equal(nrow(log), 1)
  expect_match(log$message, "Add test file")

  # Test committing already staged files
  writeLines("more content", "test2.txt")
  gert::git_add("test2.txt")

  result2 <- btw_tool_git_commit(
    message = "Add second file",
    files = NULL
  )
  expect_btw_tool_result(result2, has_data = FALSE)
  expect_match(result2@value, "Add second file")
})

test_that("btw_tool_git_branch_list()", {
  skip_if_not_installed("gert")

  local_temp_git_repo()

  # Create initial commit (needed for branches to show up)
  writeLines("test", "test.txt")
  gert::git_add("test.txt")
  gert::git_commit("Initial commit")

  result <- btw_tool_git_branch_list()
  expect_btw_tool_result(result)
  # Should show at least the default branch (main or master)
  expect_true(nrow(result@extra$data) >= 1)
  expect_snapshot(cli::cat_line(result@value), transform = scrub_git_details)

  # Create a new branch
  gert::git_branch_create("feature", checkout = FALSE)

  result2 <- btw_tool_git_branch_list()
  expect_btw_tool_result(result2)
  expect_true(nrow(result2@extra$data) >= 2)
  expect_match(result2@value, "feature")
})

test_that("btw_tool_git_branch_create()", {
  skip_if_not_installed("gert")

  local_temp_git_repo()

  # Create initial commit
  writeLines("test", "test.txt")
  gert::git_add("test.txt")
  gert::git_commit("Initial commit")

  result <- btw_tool_git_branch_create(
    branch = "feature-branch",
    checkout = FALSE
  )
  expect_btw_tool_result(result, has_data = FALSE)
  expect_match(result@value, "feature-branch")
  expect_snapshot(cli::cat_line(result@value), transform = scrub_git_details)

  # Verify branch exists
  branches <- gert::git_branch_list()
  expect_true("feature-branch" %in% branches$name)
})

test_that("btw_tool_git_branch_checkout()", {
  skip_if_not_installed("gert")

  local_temp_git_repo()

  # Create initial commit
  writeLines("test", "test.txt")
  gert::git_add("test.txt")
  gert::git_commit("Initial commit")

  # Get current branch name
  current_branch <- gert::git_branch()

  # Create and checkout new branch
  gert::git_branch_create("feature", checkout = FALSE)

  result <- btw_tool_git_branch_checkout(branch = "feature")
  expect_btw_tool_result(result, has_data = FALSE)
  expect_match(result@value, "feature")
  expect_snapshot(cli::cat_line(result@value), transform = scrub_git_details)

  # Verify we're on the new branch
  expect_equal(gert::git_branch(), "feature")

  # Checkout back to original branch
  result2 <- btw_tool_git_branch_checkout(branch = current_branch)
  expect_btw_tool_result(result2, has_data = FALSE)
  expect_equal(gert::git_branch(), current_branch)
})

test_that("git tools are registered only when in a git repo", {
  skip_if_not_installed("gert")
  local_mocked_bindings(
    btw_can_register_gh_tool = function() FALSE
  )

  # Not in a git repo
  withr::with_tempdir({
    expect_false(btw_can_register_git_tool())
    tool_names <- names(btw_tools())
    expect_no_match(tool_names, "btw_tool_git_")
  })

  # In a git repo
  local_temp_git_repo()
  expect_true(btw_can_register_git_tool())
  expect_true(all(grepl("btw_tool_git_", names(btw_tools("git")))))
})

test_that("git tools require gert to be installed", {
  skip_if_not_installed("gert")

  local_temp_git_repo()

  local_mocked_bindings(
    is_installed = function(pkg) pkg != "gert"
  )

  expect_error(
    btw_tool_git_status(),
    "gert"
  )

  expect_error(
    btw_tool_git_diff(),
    "gert"
  )

  expect_error(
    btw_tool_git_log(),
    "gert"
  )

  expect_error(
    btw_tool_git_commit(message = "test", files = "test.txt"),
    "gert"
  )

  expect_error(
    btw_tool_git_branch_list(),
    "gert"
  )

  expect_error(
    btw_tool_git_branch_create(branch = "test"),
    "gert"
  )

  expect_error(
    btw_tool_git_branch_checkout(branch = "main"),
    "gert"
  )
})

test_that("git tools validate arguments", {
  skip_if_not_installed("gert")
  local_temp_git_repo()

  # btw_tool_git_status
  expect_error(btw_tool_git_status(include = "invalid"))
  expect_error(btw_tool_git_status(pathspec = 123))

  # btw_tool_git_diff
  expect_error(btw_tool_git_diff(ref = 123))

  # btw_tool_git_log
  expect_error(btw_tool_git_log(ref = 123))
  expect_error(btw_tool_git_log(max = "invalid"))
  expect_error(btw_tool_git_log(max = 0))

  # btw_tool_git_commit
  expect_error(btw_tool_git_commit(message = 123))
  expect_error(btw_tool_git_commit(message = "test", files = 123))

  # btw_tool_git_branch_list
  expect_error(btw_tool_git_branch_list(local = "invalid"))

  # btw_tool_git_branch_create
  expect_error(btw_tool_git_branch_create(branch = 123))
  expect_error(btw_tool_git_branch_create(branch = "test", ref = 123))
  expect_error(
    btw_tool_git_branch_create(branch = "test", checkout = "invalid")
  )

  # btw_tool_git_branch_checkout
  expect_error(btw_tool_git_branch_checkout(branch = 123))
  expect_error(btw_tool_git_branch_checkout(branch = "test", force = "invalid"))
})

test_that("git tools work together", {
  skip_if_not_installed("gert")
  local_temp_git_repo()

  # Workflow 1: Check status, stage, commit, verify log
  # Create some files
  writeLines("Initial content", "file1.txt")
  writeLines("More content", "file2.txt")

  # Check status to see untracked files
  status1 <- btw_tool_git_status()
  expect_match(status1@value, "file1\\.txt")
  expect_match(status1@value, "file2\\.txt")

  # Stage and commit first file
  commit1 <- btw_tool_git_commit(
    message = "Add file1",
    files = "file1.txt"
  )
  expect_btw_tool_result(commit1, has_data = FALSE)

  # Verify log shows the commit
  log1 <- btw_tool_git_log(max = 1)
  expect_match(log1@value, "Add file1")

  # Extract commit SHA from log data to use in diff
  commit_sha <- log1@extra$data$commit[1]
  expect_false(grepl("[^a-f0-9]", commit_sha)) # Should be hex SHA

  # Workflow 2: Make changes, check diff, stage, commit
  # Modify file1
  writeLines(c("Initial content", "Added line"), "file1.txt")

  # Check diff shows unstaged changes
  diff_unstaged <- btw_tool_git_diff()
  expect_btw_tool_result(diff_unstaged, has_data = FALSE)
  # Should show a diff with changes
  expect_true(any(grepl("@@", diff_unstaged@value, fixed = TRUE)))
  expect_snapshot(
    cli::cat_line(diff_unstaged@value),
    transform = scrub_git_details
  )

  # Stage the changes
  gert::git_add("file1.txt")

  # Check diff shows staged changes
  diff_staged <- btw_tool_git_diff(ref = "HEAD")
  expect_btw_tool_result(diff_staged, has_data = FALSE)
  # Should show a diff with changes
  expect_true(any(grepl("@@", diff_staged@value, fixed = TRUE)))
  expect_snapshot(
    cli::cat_line(diff_staged@value),
    transform = scrub_git_details
  )

  # Commit the changes
  commit2 <- btw_tool_git_commit(
    message = "Update file1",
    files = NULL # Already staged
  )
  expect_btw_tool_result(commit2, has_data = FALSE)

  # Workflow 3: Use log to find commits
  # Get log of both commits
  log_all <- btw_tool_git_log(max = 10)
  expect_btw_tool_result(log_all)
  expect_equal(nrow(log_all@extra$data), 2)
  expect_match(log_all@value, "Add file1")
  expect_match(log_all@value, "Update file1")
  expect_snapshot(cli::cat_line(log_all@value), transform = scrub_git_details)

  # Get commits SHAs from log - verifies we can use log data
  commit_shas <- log_all@extra$data$commit
  expect_equal(length(commit_shas), 2)
  # All should be hex SHAs
  expect_true(!any(grepl("[^a-f0-9]", commit_shas)))

  # Workflow 4: Multiple file workflow with status tracking
  # Add second file
  commit3 <- btw_tool_git_commit(
    message = "Add file2",
    files = "file2.txt"
  )
  expect_btw_tool_result(commit3, has_data = FALSE)

  # Create third file but don't commit
  writeLines("Third file", "file3.txt")

  # Status should show file3 as untracked, others committed
  status_final <- btw_tool_git_status()
  expect_match(status_final@value, "file3\\.txt")
  expect_no_match(status_final@value, "file1\\.txt")
  expect_no_match(status_final@value, "file2\\.txt")
  expect_snapshot(
    cli::cat_line(status_final@value),
    transform = scrub_git_details
  )

  # Verify log shows all three commits
  log_final <- btw_tool_git_log(max = 10)
  expect_equal(nrow(log_final@extra$data), 3)
  expect_match(log_final@value, "Add file1")
  expect_match(log_final@value, "Update file1")
  expect_match(log_final@value, "Add file2")
  expect_snapshot(cli::cat_line(log_final@value), transform = scrub_git_details)
})

Try the btw package in your browser

Any scripts or data that you put into this service are public.

btw documentation built on Nov. 5, 2025, 7:45 p.m.