tests/testthat/test-btw_this.R

use_latest_pandoc()

test_that("btw_this.function()", {
  skip_if_not_macos()
  expect_snapshot(cli::cat_line(btw_this(dplyr::mutate)))
})

test_that("btw_this('{pkg}')", {
  # Gets the intro vignette if one is available
  expect_equal(
    btw_this("{dplyr}"),
    c(
      btw_tool_docs_package_help_topics("dplyr")@value,
      "",
      btw_tool_docs_vignette("dplyr")@value
    )
  )

  # Otherwise returns the help index
  expect_equal(
    btw_this("{cli}"),
    btw_tool_docs_package_help_topics("cli")@value
  )
})

test_that("btw_this.btw_docs_topic()", {
  expect_equal(
    btw_this(?dplyr::mutate),
    btw_this("?dplyr::mutate")
  )
})

test_that("btw_this() handles literal strings", {
  expect_equal(
    as.character(btw_this("letters[3]")),
    "letters[3]"
  )
})

test_that("btw_this('@last_error')", {
  with_mocked_bindings(
    last_error = function() {
      stop(
        "Can't show last error because no error was recorded yet",
        call. = FALSE
      )
    },
    expect_warning(expect_equal(btw_this("@last_error"), btw_ignore()))
  )

  with_mocked_bindings(
    last_error = function() {
      rlang::catch_cnd(abort("That didn't work.", trace = FALSE, call = NULL))
    },
    # btw_this("@last_error")
    expect_snapshot(cat(btw_this("@last_error")))
  )
})

test_that('btw_this("@last_value")', {
  local_mocked_bindings(
    get_last_value = function() mtcars[2:4, ]
  )

  expect_equal(
    btw_this("@last_value"),
    btw_this(mtcars[2:4, ])
  )
})


# Test @pkg command -----------------------------------------------------------

test_that("@pkg command works like {pkg} syntax", {
  # Both syntaxes should produce identical results
  expect_equal(
    btw_this("@pkg dplyr"),
    btw_this("{dplyr}")
  )

  expect_equal(
    btw_this("@pkg cli"),
    btw_this("{cli}")
  )
})

test_that("@pkg requires package name", {
  expect_error(
    btw_this("@pkg"),
    "@pkg.*must be followed by a package name"
  )

  expect_error(
    btw_this("@pkg "),
    "@pkg.*must be followed by a package name"
  )
})

# Test @help command ----------------------------------------------------------

test_that("@help command works with pkg::topic syntax", {
  expect_equal(
    btw_this("@help dplyr::mutate"),
    btw_this("?dplyr::mutate")
  )
})

test_that("@help command works with space-separated syntax", {
  expect_equal(
    btw_this("@help dplyr mutate"),
    btw_this("?dplyr::mutate")
  )
})

test_that("@help command works with topic only", {
  # Should search all packages
  result <- btw_this("@help mutate")
  expect_true(is.character(result))
  expect_true(length(result) > 0)
})

test_that("@help requires topic", {
  expect_error(
    btw_this("@help"),
    "@help.*must be followed by a help topic"
  )

  expect_error(
    btw_this("@help "),
    "@help.*must be followed by a help topic"
  )
})

# Test @git commands ----------------------------------------------------------

test_that("@git status works with mocked implementation", {
  local_mocked_bindings(
    btw_tool_git_status_impl = function(include, pathspec) {
      btw_tool_result("Status: OK")
    }
  )
  local_git_info()

  result <- btw_this("@git status")
  expect_match(result, "Status: OK", all = FALSE)
  expect_match(result, "git status", all = FALSE)
})

test_that("@git status accepts include argument", {
  local_mocked_bindings(
    btw_tool_git_status_impl = function(include, pathspec) {
      btw_tool_result(paste("Include:", include))
    }
  )
  local_git_info()

  result <- btw_this("@git status staged")
  expect_match(result, "Include: staged", all = FALSE)

  result <- btw_this("@git status unstaged")
  expect_match(result, "Include: unstaged", all = FALSE)

  result <- btw_this("@git status both")
  expect_match(result, "Include: both", all = FALSE)
})

test_that("@git diff works", {
  local_mocked_bindings(
    btw_tool_git_diff_impl = function(ref) {
      ref_str <- if (is.null(ref)) "NULL" else ref
      btw_tool_result(paste("Ref:", ref_str))
    }
  )
  local_git_info()

  result <- btw_this("@git diff")
  expect_match(result, "Ref: NULL", all = FALSE)

  result <- btw_this("@git diff HEAD")
  expect_match(result, "Ref: HEAD", all = FALSE)
})

test_that("@git log works with defaults", {
  local_mocked_bindings(
    btw_tool_git_log_impl = function(ref, max, after = NULL) {
      btw_tool_result(paste("Log:", ref, max))
    }
  )
  local_git_info()

  result <- btw_this("@git log")
  expect_match(result, "Log: HEAD 10", all = FALSE)
})

test_that("@git log works with custom ref", {
  local_mocked_bindings(
    btw_tool_git_log_impl = function(ref, max, after = NULL) {
      btw_tool_result(paste("Log:", ref, max))
    }
  )
  local_git_info()

  result <- btw_this("@git log main")
  expect_match(result, "Log: main 10", all = FALSE)
})

test_that("@git log works with custom max", {
  local_mocked_bindings(
    btw_tool_git_log_impl = function(ref, max, after = NULL) {
      btw_tool_result(paste("Log:", ref, max))
    }
  )
  local_git_info()

  result <- btw_this("@git log main 20")
  expect_match(result, "Log: main 20", all = FALSE)
})

test_that("@git requires gert package", {
  local_mocked_bindings(
    is_installed = function(pkg) pkg != "gert"
  )
  local_git_info()

  expect_error(
    btw_this("@git status"),
    "gert"
  )
})

test_that("@git requires subcommand", {
  local_git_info()

  expect_error(
    btw_this("@git"),
    "@git.*must be followed by a subcommand"
  )
})

test_that("@git rejects unknown subcommands", {
  local_git_info()

  expect_error(
    btw_this("@git unknown"),
    "Unknown git subcommand"
  )
})

test_that("@git log validates max argument", {
  local_git_info()

  expect_error(
    btw_this("@git log main abc"),
    "Invalid max value"
  )

  expect_error(
    btw_this("@git log main -5"),
    "Invalid max value"
  )
})

# Test @issue and @pr commands ------------------------------------------------

test_that("@issue and @pr parse number-only format", {
  result <- parse_github_reference("#65")
  expect_null(result$owner)
  expect_null(result$repo)
  expect_equal(result$number, 65)

  result <- parse_github_reference("123")
  expect_null(result$owner)
  expect_null(result$repo)
  expect_equal(result$number, 123)
})

test_that("@issue and @pr parse owner/repo#number format", {
  result <- parse_github_reference("posit-dev/btw#65")
  expect_equal(result$owner, "posit-dev")
  expect_equal(result$repo, "btw")
  expect_equal(result$number, 65)

  result <- parse_github_reference("tidyverse/dplyr#1234")
  expect_equal(result$owner, "tidyverse")
  expect_equal(result$repo, "dplyr")
  expect_equal(result$number, 1234)
})

test_that("@issue and @pr parse owner/repo number format", {
  result <- parse_github_reference("posit-dev/btw 65")
  expect_equal(result$owner, "posit-dev")
  expect_equal(result$repo, "btw")
  expect_equal(result$number, 65)

  result <- parse_github_reference("tidyverse/dplyr 1234")
  expect_equal(result$owner, "tidyverse")
  expect_equal(result$repo, "dplyr")
  expect_equal(result$number, 1234)

  # Multiple spaces should be handled
  result <- parse_github_reference("tidyverse/dplyr  1234")
  expect_equal(result$owner, "tidyverse")
  expect_equal(result$repo, "dplyr")
  expect_equal(result$number, 1234)
})

test_that("parse_github_reference handles edge cases", {
  # Format with # but no number
  expect_error(
    parse_github_reference("owner/repo#"),
    "Invalid GitHub reference format"
  )

  # Format with / but no # or space
  expect_error(
    parse_github_reference("owner/repo"),
    "Invalid GitHub reference format"
  )
})

test_that("@issue and @pr handle invalid formats", {
  expect_error(
    parse_github_reference("invalid"),
    "Invalid GitHub reference format"
  )

  expect_error(
    parse_github_reference("owner#123"), # Missing repo
    "Invalid GitHub reference format"
  )

  expect_error(
    parse_github_reference("invalid/format/here"),
    "Invalid GitHub reference format"
  )
})

test_that("@issue and @pr require issue number", {
  skip_if_not(is_installed("gh"))

  expect_error(
    btw_this("@issue"),
    "@issue.*must be followed"
  )

  expect_error(
    btw_this("@pr "),
    "@pr.*must be followed"
  )
})

test_that("@issue detects current repo when only number provided", {
  skip_if_not(is_installed("gh"))

  local_posit_dev_btw_repo()
  local_mocked_gh(
    list(
      number = 65,
      title = "Test Issue",
      html_url = "https://github.com/posit-dev/btw/issues/65",
      state = "open",
      user = list(login = "testuser"),
      created_at = "2025-01-01T00:00:00Z",
      updated_at = "2025-01-01T00:00:00Z",
      body = "Test body",
      labels = list(),
      pull_request = NULL,
      closed_at = NULL,
      merged_at = NULL,
      milestone = NULL
    )
  )

  result <- btw_this("@issue #65")
  expect_match(result, "github-issue")
  expect_match(result, 'owner="posit-dev"')
  expect_match(result, 'repo="btw"')
  expect_match(result, 'number="65"')
  expect_match(result, "Test Issue")
  expect_match(result, "Test body")

  skip_if_not_macos()
  expect_snapshot(cli::cat_line(result))
})

test_that("@pr marks pull requests correctly", {
  skip_if_not(is_installed("gh"))

  local_posit_dev_btw_repo()
  local_mocked_gh(
    list(
      number = 64,
      title = "Test PR",
      html_url = "https://github.com/posit-dev/btw/pull/64",
      state = "closed",
      user = list(login = "testuser"),
      created_at = "2025-01-01T00:00:00Z",
      updated_at = "2025-01-01T00:00:00Z",
      merged_at = "2025-01-02T00:00:00Z",
      body = "Test PR body",
      labels = list(
        list(name = "bug"),
        list(name = "urgent")
      ),
      pull_request = list(
        url = "https://api.github.com/repos/posit-dev/btw/pulls/64"
      ),
      closed_at = "2025-01-02T00:00:00Z",
      milestone = list(title = "v1.0")
    )
  )

  result <- btw_this("@pr #64")
  expect_match(result, "github-pull-request")
  expect_match(result, "Pull Request")
  expect_match(result, "merged: 2025-01-02T00:00:00Z")

  skip_if_not_macos()
  expect_snapshot(cli::cat_line(result))
})

test_that("format_github_item handles empty body", {
  skip_if_not(is_installed("gh"))

  item <- list(
    number = 1,
    title = "Test",
    html_url = "https://github.com/test/test/issues/1",
    state = "open",
    user = list(login = "user"),
    created_at = "2025-01-01T00:00:00Z",
    updated_at = "2025-01-01T00:00:00Z",
    body = "",
    labels = list(),
    pull_request = NULL,
    closed_at = NULL,
    merged_at = NULL,
    milestone = NULL
  )

  result <- format_github_item(item, "owner", "repo", "issue")
  expect_match(result, "_No description provided._")
})

# Test backward compatibility -------------------------------------------------

test_that("legacy {pkg} syntax still works", {
  expect_equal(
    btw_this("{dplyr}"),
    c(
      btw_tool_docs_package_help_topics("dplyr")@value,
      "",
      btw_tool_docs_vignette("dplyr")@value
    )
  )
})

test_that("legacy ?topic syntax still works", {
  expect_equal(
    btw_this(?dplyr::mutate),
    btw_this("?dplyr::mutate")
  )
})

test_that("legacy ./path syntax still works", {
  skip_on_cran()

  # Create a temporary file for testing
  temp_file <- tempfile(fileext = ".txt")
  writeLines("test content", temp_file)
  on.exit(unlink(temp_file))

  # Change to temp directory
  old_dir <- getwd()
  temp_dir <- dirname(temp_file)
  setwd(temp_dir)
  on.exit(setwd(old_dir), add = TRUE)

  # Test reading file
  result <- btw_this(paste0("./", basename(temp_file)))
  expect_match(result, "test content")
})

# Test unknown @ commands -----------------------------------------------------

test_that("unknown @ commands return user prompt", {
  # Unknown commands should fall through to user prompt
  result <- btw_this("@unknown_command")
  expect_s3_class(result, "btw_user_prompt")
  expect_equal(as.character(result), "@unknown_command")
})

test_that("@unknown_with_args returns user prompt", {
  result <- btw_this("@unknown_command with args")
  expect_s3_class(result, "btw_user_prompt")
  expect_equal(as.character(result), "@unknown_command with args")
})

# Test @ command edge cases ---------------------------------------------------

test_that("@ commands handle extra whitespace", {
  expect_equal(
    btw_this("@pkg  dplyr"), # Extra space
    btw_this("@pkg dplyr")
  )

  expect_equal(
    btw_this("  @help dplyr::mutate  "), # Leading/trailing
    btw_this("@help dplyr::mutate")
  )
})

test_that("@ commands are case-sensitive", {
  # @PKG should not match @pkg - should be treated as user prompt
  result <- btw_this("@PKG dplyr")
  expect_s3_class(result, "btw_user_prompt")
})

# Test @news command ----------------------------------------------------------

test_that("@news command works", {
  local_mocked_bindings(
    btw_tool_docs_package_news_impl = function(package_name, search_term) {
      btw_tool_result(paste("News for", package_name, search_term))
    }
  )

  result <- btw_this("@news dplyr")
  expect_match(result, "News for dplyr")

  result <- btw_this("@news dplyr join_by")
  expect_match(result, "News for dplyr join_by")
})

test_that("@news requires package name", {
  expect_error(
    btw_this("@news"),
    "@news.*must be followed by a package name"
  )

  expect_error(
    btw_this("@news "),
    "@news.*must be followed by a package name"
  )
})

# Test @url command -----------------------------------------------------------

test_that("@url requires chromote", {
  local_mocked_bindings(
    has_chromote = function() FALSE
  )

  expect_error(
    btw_this("@url https://example.com"),
    "chromote"
  )
})

test_that("@url requires URL", {
  local_mocked_bindings(
    has_chromote = function() TRUE,
    read_url_main_content = function(...) NULL
  )

  expect_error(
    btw_this("@url"),
    "@url.*must be followed by a valid URL"
  )

  expect_error(
    btw_this("@url "),
    "@url.*must be followed by a valid URL"
  )
})

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.