Skip to content

Commit bdc15be

Browse files
authored
Fix broken JS line-number handlers and add DiffComponent tests (#113)
Replace standalone line-number event listeners that ran once at page load (before diffs existed in the DOM) with a DiffList LiveView hook using event delegation. Add test coverage for DiffComponent rendering.
1 parent 1b081cb commit bdc15be

File tree

3 files changed

+157
-24
lines changed

3 files changed

+157
-24
lines changed

assets/js/app.js

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -60,30 +60,42 @@ window.Hooks.InfiniteScroll = {
6060
}
6161
}
6262

63-
let liveSocket = new LiveSocket("/live", Socket, { hooks: window.Hooks })
64-
liveSocket.connect()
65-
66-
/*
67-
Make it possible to click line numbers to update the address bar to a
68-
link directly to that line.
69-
*/
70-
if (location.hash) {
71-
document.getElementById(location.hash.replace('#', '')).classList.add('selected')
72-
}
73-
74-
const lines = document.querySelectorAll('.ghd-line-number')
75-
lines.forEach(line => {
76-
line.addEventListener('click', e => {
77-
const parent = line.parentNode
63+
window.Hooks.DiffList = {
64+
mounted() {
65+
this.el.addEventListener('click', e => {
66+
const lineNumber = e.target.closest('.ghd-line-number')
67+
if (!lineNumber) return
68+
69+
const parent = lineNumber.parentNode
70+
if (parent && parent.id) {
71+
this.el.querySelectorAll('.ghd-line.selected').forEach(el => {
72+
el.classList.remove('selected')
73+
})
74+
parent.classList.add('selected')
75+
history.replaceState(null, null, '#' + parent.id)
76+
}
77+
})
7878

79-
if (parent && parent.id) {
80-
document.querySelectorAll('.ghd-line.selected').forEach(line => {
81-
line.classList.remove('selected')
82-
})
79+
this.selectHash()
80+
},
8381

84-
parent.classList.add('selected')
82+
updated() {
83+
this.selectHash()
84+
},
8585

86-
history.replaceState(null, null, '#' + parent.id)
86+
selectHash() {
87+
if (location.hash) {
88+
const el = document.getElementById(location.hash.replace('#', ''))
89+
if (el) {
90+
this.el.querySelectorAll('.ghd-line.selected').forEach(el => {
91+
el.classList.remove('selected')
92+
})
93+
el.classList.add('selected')
94+
el.scrollIntoView({ block: 'center' })
95+
}
8796
}
88-
})
89-
})
97+
}
98+
}
99+
100+
let liveSocket = new LiveSocket("/live", Socket, { hooks: window.Hooks })
101+
liveSocket.connect()

lib/diff_web/templates/live/diff.html.leex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
</div>
2929
<% end %>
3030

31-
<div class="ghd-container" id="diff-list">
31+
<div class="ghd-container" id="diff-list" phx-hook="DiffList">
3232
<%= if @generating do %>
3333
<div class="loading-spinner">
3434
Generating diffs...
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
defmodule DiffWeb.DiffComponentTest do
2+
use ExUnit.Case, async: true
3+
4+
alias DiffWeb.DiffComponent
5+
6+
defp render_component(diff, diff_id) do
7+
assigns = %{diff: diff, diff_id: diff_id}
8+
9+
assigns
10+
|> DiffComponent.render()
11+
|> Phoenix.HTML.Safe.to_iodata()
12+
|> IO.iodata_to_binary()
13+
end
14+
15+
defp line(opts \\ []) do
16+
%GitDiff.Line{
17+
type: Keyword.get(opts, :type, :context),
18+
from_line_number: Keyword.get(opts, :from, 1),
19+
to_line_number: Keyword.get(opts, :to, 1),
20+
text: Keyword.get(opts, :text, " context line")
21+
}
22+
end
23+
24+
defp chunk(lines) do
25+
%GitDiff.Chunk{
26+
header: "@@ -1,3 +1,3 @@",
27+
lines: lines
28+
}
29+
end
30+
31+
describe "render/1" do
32+
test "changed file" do
33+
diff = %GitDiff.Patch{
34+
from: "lib/app.ex",
35+
to: "lib/app.ex",
36+
chunks: [chunk([line()])]
37+
}
38+
39+
html = render_component(diff, "diff-0")
40+
41+
assert html =~ "changed"
42+
assert html =~ "lib/app.ex"
43+
assert html =~ "diff-0-body"
44+
end
45+
46+
test "added file" do
47+
diff = %GitDiff.Patch{
48+
from: nil,
49+
to: "lib/new.ex",
50+
chunks: [chunk([line(type: :add, from: "", to: 1, text: "+new line")])]
51+
}
52+
53+
html = render_component(diff, "diff-1")
54+
55+
assert html =~ "added"
56+
assert html =~ "lib/new.ex"
57+
assert html =~ "diff-1-body"
58+
end
59+
60+
test "removed file" do
61+
diff = %GitDiff.Patch{
62+
from: "lib/old.ex",
63+
to: nil,
64+
chunks: [chunk([line(type: :remove, from: 1, to: "", text: "-old line")])]
65+
}
66+
67+
html = render_component(diff, "diff-2")
68+
69+
assert html =~ "removed"
70+
assert html =~ "lib/old.ex"
71+
assert html =~ "diff-2-body"
72+
end
73+
74+
test "renamed file" do
75+
diff = %GitDiff.Patch{
76+
from: "lib/old_name.ex",
77+
to: "lib/new_name.ex",
78+
chunks: [chunk([line()])]
79+
}
80+
81+
html = render_component(diff, "diff-3")
82+
83+
assert html =~ "renamed"
84+
assert html =~ "lib/old_name.ex -&gt; lib/new_name.ex"
85+
assert html =~ "diff-3-body"
86+
end
87+
88+
test "includes toggle icon and phx-click" do
89+
diff = %GitDiff.Patch{
90+
from: "lib/app.ex",
91+
to: "lib/app.ex",
92+
chunks: [chunk([line()])]
93+
}
94+
95+
html = render_component(diff, "diff-0")
96+
97+
assert html =~ "<svg"
98+
assert html =~ "phx-click"
99+
end
100+
101+
test "renders multiple chunks and lines" do
102+
lines = [
103+
line(type: :context, from: 1, to: 1, text: " unchanged"),
104+
line(type: :remove, from: 2, to: "", text: "-removed"),
105+
line(type: :add, from: "", to: 2, text: "+added")
106+
]
107+
108+
diff = %GitDiff.Patch{
109+
from: "lib/app.ex",
110+
to: "lib/app.ex",
111+
chunks: [chunk(lines), chunk([line(from: 10, to: 10)])]
112+
}
113+
114+
html = render_component(diff, "diff-0")
115+
116+
assert html =~ "unchanged"
117+
assert html =~ "removed"
118+
assert html =~ "added"
119+
end
120+
end
121+
end

0 commit comments

Comments
 (0)