defmodule GitGudWeb.CrossReferenceTest do
@moduledoc """
Linking to another issue or PR leaves a backlink in that item's
history, so the reference is visible from both ends.
"""
use GitGudWeb.ConnCase, async: false
import Phoenix.LiveViewTest
import GitGud.ForgeFixtures
alias GitGud.Events
alias GitGud.Issues
alias GitGud.Markdown
alias GitGud.Repositories
defp handle(repo),
do: Repositories.Storage.repo_handle(GitGud.Repo.preload(repo, [:owner, :organization]))
defp issue_path(repo, issue),
do: ~p"/r/#{handle(repo)}/#{repo.name}/issues/#{issue.number}"
defp edit_body(conn, user, repo, issue, body) do
{:ok, lv, _html} = live(log_in_user(conn, user), issue_path(repo, issue))
_ = lv |> element("button[phx-click=edit_body]") |> render_click()
lv |> form("form[phx-submit=save_body]", %{body: body}) |> render_submit()
end
describe "extract_refs/2" do
test "finds a local reference", %{conn: _conn} do
{user, repo} = repository_fixture()
target = issue_fixture(repo, user, %{"title" => "The target"})
assert [{:issue, item, ^repo}] = Markdown.extract_refs("see ##{target.number}", repo)
assert item.number == target.number
end
test "ignores references inside code", %{conn: _conn} do
{user, repo} = repository_fixture()
target = issue_fixture(repo, user)
assert Markdown.extract_refs("`##{target.number}`", repo) == []
assert Markdown.extract_refs("```\n##{target.number}\n```", repo) == []
end
test "ignores a number that resolves to nothing", %{conn: _conn} do
{_user, repo} = repository_fixture()
assert Markdown.extract_refs("see #9999", repo) == []
end
test "deduplicates repeated mentions", %{conn: _conn} do
{user, repo} = repository_fixture()
target = issue_fixture(repo, user)
body = "##{target.number} and again ##{target.number}"
assert [_one] = Markdown.extract_refs(body, repo)
end
test "resolves a cross-repo reference", %{conn: _conn} do
{user_a, repo_a} = repository_fixture(%{visibility: "public"})
target = issue_fixture(repo_a, user_a, %{"title" => "Over there"})
{_user_b, repo_b} = repository_fixture(%{visibility: "public"})
body = "see #{handle(repo_a)}/#{repo_a.name}##{target.number}"
assert [{:issue, item, found_repo}] = Markdown.extract_refs(body, repo_b)
assert item.number == target.number
assert found_repo.id == repo_a.id
end
end
describe "backlinks" do
test "editing a description backlinks the referenced issue", %{conn: conn} do
{user, repo} = repository_fixture()
target = issue_fixture(repo, user, %{"title" => "The target"})
source = issue_fixture(repo, user, %{"title" => "The source", "body" => "nothing yet"})
_ = edit_body(conn, user, repo, source, "fixes ##{target.number}")
assert [%{kind: "cross_referenced", data: data}] = Events.list_for(target)
assert data["from_number"] == source.number
assert data["from_title"] == "The source"
assert data["from_type"] == "issue"
end
test "the backlink shows on the target's history page", %{conn: conn} do
{user, repo} = repository_fixture()
target = issue_fixture(repo, user)
source = issue_fixture(repo, user, %{"title" => "Mentions it", "body" => ""})
_ = edit_body(conn, user, repo, source, "see ##{target.number}")
{:ok, _hist, html} =
live(log_in_user(conn, user), issue_path(repo, target) <> "/history")
assert html =~ "referenced this from issue"
assert html =~ "Mentions it"
assert html =~ "##{source.number}"
# And it links back to the source, not just naming it.
assert html =~ ~s(href="/r/#{handle(repo)}/#{repo.name}/issues/#{source.number}")
end
test "a reference already present isn't re-announced on re-save", %{conn: conn} do
{user, repo} = repository_fixture()
target = issue_fixture(repo, user)
source = issue_fixture(repo, user, %{"body" => "see ##{target.number}"})
# Same reference, different surrounding prose.
_ = edit_body(conn, user, repo, source, "still see ##{target.number}")
assert Events.list_for(target) == []
end
test "an edit that adds a second reference backlinks only the new one", %{conn: conn} do
{user, repo} = repository_fixture()
first = issue_fixture(repo, user)
second = issue_fixture(repo, user)
source = issue_fixture(repo, user, %{"body" => "see ##{first.number}"})
_ = edit_body(conn, user, repo, source, "see ##{first.number} and ##{second.number}")
assert Events.list_for(first) == []
assert [%{kind: "cross_referenced"}] = Events.list_for(second)
end
test "referencing the same target twice over two edits records once", %{conn: conn} do
{user, repo} = repository_fixture()
target = issue_fixture(repo, user)
source = issue_fixture(repo, user, %{"body" => ""})
_ = edit_body(conn, user, repo, source, "see ##{target.number}")
_ = edit_body(conn, user, repo, source, "dropped it")
_ = edit_body(conn, user, repo, source, "see ##{target.number} again")
assert [_one] = Events.list_for(target)
end
test "an issue referring to itself records nothing", %{conn: conn} do
{user, repo} = repository_fixture()
source = issue_fixture(repo, user, %{"body" => ""})
_ = edit_body(conn, user, repo, source, "this is ##{source.number}")
# The edit itself is recorded; the self-reference isn't.
kinds = source |> Events.list_for() |> Enum.map(& &1.kind)
assert kinds == ["description_changed"]
end
test "opening an issue with a reference backlinks it", %{conn: conn} do
{user, repo} = repository_fixture()
target = issue_fixture(repo, user, %{"title" => "Pre-existing"})
{:ok, lv, _html} =
live(log_in_user(conn, user), ~p"/r/#{handle(repo)}/#{repo.name}/issues/new")
_ =
lv
|> form("form[phx-submit=save]", %{
"issue" => %{"title" => "Brand new", "body" => "relates to ##{target.number}"}
})
|> render_submit()
assert [%{kind: "cross_referenced", data: data}] = Events.list_for(target)
assert data["from_title"] == "Brand new"
end
test "a reference in code doesn't backlink", %{conn: conn} do
{user, repo} = repository_fixture()
target = issue_fixture(repo, user)
source = issue_fixture(repo, user, %{"body" => ""})
_ = edit_body(conn, user, repo, source, "the literal `##{target.number}` in code")
assert Events.list_for(target) == []
end
test "a backlink from a PR links to the PR", %{conn: conn} do
{user, repo} = repository_fixture()
target = issue_fixture(repo, user)
pr = pr_with_branches(repo, user, %{"title" => "Source PR", "body" => ""})
{:ok, lv, _html} =
live(log_in_user(conn, user), ~p"/r/#{handle(repo)}/#{repo.name}/pulls/#{pr.number}")
_ = lv |> element("button[phx-click=edit_body]") |> render_click()
_ =
lv
|> form("form[phx-submit=save_body]", %{body: "closes ##{target.number}"})
|> render_submit()
{:ok, _hist, html} =
live(log_in_user(conn, user), issue_path(repo, target) <> "/history")
assert html =~ "referenced this from pull request"
assert html =~ ~s(href="/r/#{handle(repo)}/#{repo.name}/pulls/#{pr.number}")
end
test "a PR referenced from an issue is backlinked too", %{conn: conn} do
{user, repo} = repository_fixture()
pr = pr_with_branches(repo, user, %{"title" => "The PR"})
source = issue_fixture(repo, user, %{"body" => ""})
_ = edit_body(conn, user, repo, source, "implemented in ##{pr.number}")
assert [%{kind: "cross_referenced", data: data}] = Events.list_for(pr)
assert data["from_type"] == "issue"
end
test "the backlink names the source repo", %{conn: conn} do
{user, repo} = repository_fixture()
target = issue_fixture(repo, user)
source = issue_fixture(repo, user, %{"body" => ""})
_ = edit_body(conn, user, repo, source, "see ##{target.number}")
assert [%{data: data}] = Events.list_for(target)
assert data["from_repo"] == "#{handle(repo)}/#{repo.name}"
end
test "the source's own history is untouched", %{conn: conn} do
{user, repo} = repository_fixture()
target = issue_fixture(repo, user)
source = issue_fixture(repo, user, %{"body" => ""})
_ = edit_body(conn, user, repo, source, "see ##{target.number}")
kinds = source |> Events.list_for() |> Enum.map(& &1.kind)
# The edit itself is recorded; the backlink belongs to the target.
assert kinds == ["description_changed"]
assert Issues.get_issue!(repo, source.number).body =~ "##{target.number}"
end
end
end
neiam /gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Packages
⭐
Log in to mark this repository.
8.7 KiB · text
History
6280797