defmodule GitGud.MarkdownCrossRefsTest do
use GitGud.DataCase, async: false
alias GitGud.Cache
alias GitGud.Markdown
import GitGud.AccountsFixtures
import GitGud.ForgeFixtures
setup do
Cache.delete_all()
{user, repo} = repository_fixture()
{:ok, user: user, repo: repo}
end
test "resolves #N to an issue link", %{user: user, repo: repo} do
issue = issue_fixture(repo, user, %{"title" => "the issue"})
html = Markdown.to_html("see ##{issue.number}", repo: repo)
assert html =~ "/issues/#{issue.number}"
assert html =~ "the issue"
assert html =~ "#" <> Integer.to_string(issue.number)
end
test "resolves #N to a pull request link when the number is a PR", %{user: user, repo: repo} do
pr =
pull_request_with_branches(repo, user, %{
"title" => "the pr",
"source_ref" => "feature",
"target_ref" => "main"
})
html = Markdown.to_html("see ##{pr.number}", repo: repo)
assert html =~ "/pulls/#{pr.number}"
assert html =~ "the pr"
end
test "leaves unresolved #N as plain text", %{repo: repo} do
html = Markdown.to_html("see #999", repo: repo)
refute html =~ "<a "
assert html =~ "#999"
end
test "#xHEX resolves the same row as the decimal", %{user: user, repo: repo} do
issue = issue_fixture(repo, user, %{"title" => "hex-form"})
hex = Integer.to_string(issue.number, 16) |> String.downcase()
html = Markdown.to_html("see #x#{hex}", repo: repo)
assert html =~ "/issues/#{issue.number}"
assert html =~ "hex-form"
# Link text preserves the form the user typed.
assert html =~ "#x" <> hex
end
test "#xnotahex falls back to plain text", %{repo: repo} do
html = Markdown.to_html("see #xnothex", repo: repo)
refute html =~ "<a "
end
test "does not expand inside backtick code spans", %{user: user, repo: repo} do
issue = issue_fixture(repo, user)
html = Markdown.to_html("plain ##{issue.number}, code `##{issue.number}`", repo: repo)
# The plain reference is linked, the inline-code one isn't.
[a_open] = Regex.scan(~r/<a /, html)
_ = a_open
refute html =~ "<a href=\"/r/" <> "#{issue.repository_id}"
# `#` inside a `<code>` shouldn't be wrapped in `<a>`.
assert html =~ "<code"
end
test "does not expand inside fenced code blocks", %{user: user, repo: repo} do
issue = issue_fixture(repo, user)
body = """
```
see ##{issue.number}
```
"""
html = Markdown.to_html(body, repo: repo)
refute html =~ "<a href=\"/r/"
end
test "expands owner/repo#N for cross-repo references", %{user: user} do
{other_user, other_repo} = repository_fixture()
issue = issue_fixture(other_repo, user, %{"title" => "across"})
{_, src_repo} = repository_fixture(%{owner: user_fixture()})
other_handle =
GitGud.Repositories.Storage.repo_handle(GitGud.Repo.preload(other_repo, [:owner, :organization]))
body = "see #{other_handle}/#{other_repo.name}##{issue.number}"
html = Markdown.to_html(body, repo: src_repo)
assert html =~ "/r/#{other_handle}/#{other_repo.name}/issues/#{issue.number}"
assert html =~ "across"
_ = other_user
end
test "no :repo opt → no expansion", %{user: user, repo: repo} do
issue = issue_fixture(repo, user)
html = Markdown.to_html("see ##{issue.number}")
refute html =~ "<a "
end
# ── helpers ──────────────────────────────────────────────────────────
defp pull_request_with_branches(repo, user, attrs) do
seed_branches(repo, user)
GitGud.ForgeFixtures.pull_request_fixture(repo, user, attrs["source_ref"], attrs["target_ref"], Map.delete(attrs, "source_ref") |> Map.delete("target_ref"))
end
defp seed_branches(repo, user) do
path = repo.disk_path
main_sha =
commit_file(path, user, nil, "README.md", "main\n", "initial")
:ok = GitGud.Git.update_ref(path, "refs/heads/main", main_sha, nil)
feature_sha =
commit_file(path, user, main_sha, "feature.txt", "added\n", "feature")
:ok = GitGud.Git.update_ref(path, "refs/heads/feature", feature_sha, nil)
end
defp commit_file(path, user, parent_sha, filename, contents, message) do
blob_sha = write_blob(path, contents)
tree_sha = make_tree(path, parent_sha, filename, blob_sha)
parents = if parent_sha, do: [parent_sha], else: []
email = Map.get(user, :email) || "test@example.com"
name = email |> String.split("@") |> hd()
{:ok, commit_sha} =
GitGud.Git.commit_tree(path, tree_sha, parents, message,
author: {name, email, DateTime.utc_now(:second)}
)
commit_sha
end
defp write_blob(path, contents) do
tmp = Path.join(System.tmp_dir!(), "md-blob-#{System.unique_integer([:positive])}")
File.write!(tmp, contents)
{hex, 0} = System.cmd("git", ["-C", path, "hash-object", "-w", tmp])
File.rm(tmp)
{:ok, sha} = GitGud.Git.from_hex(String.trim(hex))
sha
end
defp make_tree(path, parent_sha, filename, blob_sha) do
index = Path.join(System.tmp_dir!(), "md-idx-#{System.unique_integer([:positive])}")
env = [{"GIT_INDEX_FILE", index}]
try do
if parent_sha do
{:ok, parent_commit} = GitGud.Git.commit(path, parent_sha)
{_, 0} =
System.cmd("git", ["-C", path, "read-tree", GitGud.Git.to_hex(parent_commit.tree_sha)],
env: env
)
end
{_, 0} =
System.cmd(
"git",
[
"-C",
path,
"update-index",
"--add",
"--cacheinfo",
"100644," <> GitGud.Git.to_hex(blob_sha) <> "," <> filename
],
env: env
)
{hex, 0} = System.cmd("git", ["-C", path, "write-tree"], env: env)
{:ok, sha} = GitGud.Git.from_hex(String.trim(hex))
sha
after
_ = File.rm(index)
end
end
end
5.9 KiB · text
5af55d9