@celestia-island/hikari 0.55.147 → 0.55.149
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/HkDiffViewer.scss +204 -0
- package/src/components/HkDiffViewer.test.tsx +240 -0
- package/src/components/HkDiffViewer.tsx +276 -0
- package/src/components/HkSliderField.scss +29 -0
- package/src/components/HkSliderField.test.tsx +411 -0
- package/src/components/HkSliderField.tsx +71 -0
- package/src/components/{HkTokenField.scss → HkTokenInput.scss} +12 -29
- package/src/components/{HkTokenField.tsx → HkTokenInput.tsx} +168 -237
- package/src/components/diffParse.test.ts +164 -0
- package/src/components/diffParse.ts +180 -0
- package/src/i18n/locales/ar/components.json +3 -1
- package/src/i18n/locales/de/components.json +3 -1
- package/src/i18n/locales/en/components.json +3 -1
- package/src/i18n/locales/es/components.json +3 -1
- package/src/i18n/locales/fr/components.json +3 -1
- package/src/i18n/locales/ja/components.json +3 -1
- package/src/i18n/locales/ko/components.json +3 -1
- package/src/i18n/locales/pt/components.json +3 -1
- package/src/i18n/locales/ru/components.json +3 -1
- package/src/i18n/locales/zh-Hans/components.json +3 -1
- package/src/i18n/locales/zh-Hant/components.json +3 -1
- package/src/index.ts +6 -2
- package/src/components/HkTokenField.test.tsx +0 -403
package/package.json
CHANGED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// HkDiffViewer — grouped, collapsible unified-diff rendering.
|
|
2
|
+
// Sibling of the markdown code-block chrome: same subtle surface, same
|
|
3
|
+
// mono stack, same hidden native scrollbars (overlay rails are attached
|
|
4
|
+
// by the consumer's scroll container).
|
|
5
|
+
|
|
6
|
+
@use "./hikari-vars" as vars;
|
|
7
|
+
|
|
8
|
+
.hk-diff-viewer {
|
|
9
|
+
border: 1px solid var(--hi-color-border, #646464);
|
|
10
|
+
border-radius: var(--radius-sm);
|
|
11
|
+
background: var(--hi-color-bg-subtle, #21262d);
|
|
12
|
+
overflow: hidden; // clip hunk/gap bands to the rounded corners
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.hk-diff-viewer__body {
|
|
16
|
+
overflow-x: auto;
|
|
17
|
+
font-size: var(--text-xs);
|
|
18
|
+
line-height: 1.55;
|
|
19
|
+
scrollbar-width: none;
|
|
20
|
+
|
|
21
|
+
&::-webkit-scrollbar {
|
|
22
|
+
display: none;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// One diff row: old number | new number | sign | content. The row is at
|
|
27
|
+
// least the container width (so tints span edge to edge) and grows with
|
|
28
|
+
// the longest content (so the container can scroll horizontally).
|
|
29
|
+
.hk-diff-viewer__line {
|
|
30
|
+
display: grid;
|
|
31
|
+
grid-template-columns: 4.2ch 4.2ch 2ch 1fr;
|
|
32
|
+
width: 100%;
|
|
33
|
+
min-width: max-content;
|
|
34
|
+
font-family: var(--font-mono, vars.$hikari-font-family-mono);
|
|
35
|
+
white-space: pre;
|
|
36
|
+
color: var(--hi-color-text-primary, #1e1e1e);
|
|
37
|
+
|
|
38
|
+
&[data-kind="add"] {
|
|
39
|
+
background: rgb(var(--color-success) / 12%);
|
|
40
|
+
|
|
41
|
+
.hk-diff-viewer__sign {
|
|
42
|
+
color: rgb(var(--color-success));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
&[data-kind="del"] {
|
|
47
|
+
background: rgb(var(--color-error) / 12%);
|
|
48
|
+
|
|
49
|
+
.hk-diff-viewer__sign {
|
|
50
|
+
color: rgb(var(--color-error));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.hk-diff-viewer__no {
|
|
56
|
+
padding: 0 0.8ch;
|
|
57
|
+
text-align: end;
|
|
58
|
+
color: var(--hi-color-text-tertiary, #8b949e);
|
|
59
|
+
opacity: 0.75;
|
|
60
|
+
user-select: none;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.hk-diff-viewer__sign {
|
|
64
|
+
user-select: none;
|
|
65
|
+
text-align: center;
|
|
66
|
+
font-weight: 600;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.hk-diff-viewer__code {
|
|
70
|
+
padding-inline-end: 1ch;
|
|
71
|
+
min-width: 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── hunk groups ────────────────────────────────────────────────────────────
|
|
75
|
+
.hk-diff-viewer__hunk + .hk-diff-viewer__hunk,
|
|
76
|
+
.hk-diff-viewer__hunk-body {
|
|
77
|
+
border-top: 1px solid var(--hi-color-border, #646464);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.hk-diff-viewer__hunk-head {
|
|
81
|
+
display: flex;
|
|
82
|
+
align-items: center;
|
|
83
|
+
gap: 0.5em;
|
|
84
|
+
width: 100%;
|
|
85
|
+
padding: 0.3em 0.8em;
|
|
86
|
+
border: 0;
|
|
87
|
+
background: var(--hi-color-bg-subtle, #21262d);
|
|
88
|
+
color: var(--hi-color-text-secondary, #666666);
|
|
89
|
+
font: inherit;
|
|
90
|
+
text-align: start;
|
|
91
|
+
cursor: pointer;
|
|
92
|
+
user-select: none;
|
|
93
|
+
|
|
94
|
+
&:hover {
|
|
95
|
+
color: var(--hi-color-text-primary, #1e1e1e);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
&:focus-visible {
|
|
99
|
+
outline: 2px solid var(--hi-color-primary, #7aa2f7);
|
|
100
|
+
outline-offset: -2px;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.hk-diff-viewer__hunk-range {
|
|
105
|
+
font-weight: 600;
|
|
106
|
+
letter-spacing: 0.01em;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.hk-diff-viewer__hunk-section {
|
|
110
|
+
color: var(--hi-color-text-tertiary, #8b949e);
|
|
111
|
+
overflow: hidden;
|
|
112
|
+
text-overflow: ellipsis;
|
|
113
|
+
white-space: nowrap;
|
|
114
|
+
min-width: 0;
|
|
115
|
+
flex: 1;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.hk-diff-viewer__hunk-stats {
|
|
119
|
+
display: inline-flex;
|
|
120
|
+
gap: 0.6em;
|
|
121
|
+
margin-inline-start: auto;
|
|
122
|
+
font-style: normal;
|
|
123
|
+
|
|
124
|
+
em {
|
|
125
|
+
font-style: normal;
|
|
126
|
+
font-weight: 600;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
em[data-kind="add"] {
|
|
130
|
+
color: rgb(var(--color-success));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
em[data-kind="del"] {
|
|
134
|
+
color: rgb(var(--color-error));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ── collapsed unchanged regions ────────────────────────────────────────────
|
|
139
|
+
.hk-diff-viewer__gap {
|
|
140
|
+
display: flex;
|
|
141
|
+
align-items: center;
|
|
142
|
+
justify-content: center;
|
|
143
|
+
gap: 0.5em;
|
|
144
|
+
width: 100%;
|
|
145
|
+
padding: 0.35em 0.8em;
|
|
146
|
+
border: 0;
|
|
147
|
+
border-top: 1px dashed var(--hi-color-border, #646464);
|
|
148
|
+
background: rgb(var(--color-primary) / 5%);
|
|
149
|
+
color: var(--hi-color-text-tertiary, #8b949e);
|
|
150
|
+
font: inherit;
|
|
151
|
+
cursor: pointer;
|
|
152
|
+
user-select: none;
|
|
153
|
+
|
|
154
|
+
&:hover:not(:disabled) {
|
|
155
|
+
background: rgb(var(--color-primary) / 10%);
|
|
156
|
+
color: var(--hi-color-text-primary, #1e1e1e);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
&:focus-visible {
|
|
160
|
+
outline: 2px solid var(--hi-color-primary, #7aa2f7);
|
|
161
|
+
outline-offset: -2px;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
&:disabled {
|
|
165
|
+
cursor: progress;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
&[data-state="failed"] {
|
|
169
|
+
background: rgb(var(--color-error) / 8%);
|
|
170
|
+
color: rgb(var(--color-error));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Without a loadContext fetcher the row is a passive divider.
|
|
174
|
+
&[data-state="static"] {
|
|
175
|
+
cursor: default;
|
|
176
|
+
|
|
177
|
+
&:hover {
|
|
178
|
+
background: rgb(var(--color-primary) / 5%);
|
|
179
|
+
color: var(--hi-color-text-tertiary, #8b949e);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.hk-diff-viewer__gap-label {
|
|
185
|
+
font-size: 0.95em;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// A gap directly after the last hunk still gets its dashed separator.
|
|
189
|
+
.hk-diff-viewer__hunk + .hk-diff-viewer__gap,
|
|
190
|
+
.hk-diff-viewer__gap + .hk-diff-viewer__hunk .hk-diff-viewer__hunk-head {
|
|
191
|
+
border-top: 1px dashed var(--hi-color-border, #646464);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ── raw fallback (unparseable input) ───────────────────────────────────────
|
|
195
|
+
.hk-diff-viewer__raw {
|
|
196
|
+
margin: 0;
|
|
197
|
+
padding: 0.6em 0.8em;
|
|
198
|
+
font-family: var(--font-mono, vars.$hikari-font-family-mono);
|
|
199
|
+
font-size: var(--text-xs);
|
|
200
|
+
line-height: 1.55;
|
|
201
|
+
white-space: pre-wrap;
|
|
202
|
+
word-break: break-word;
|
|
203
|
+
color: var(--hi-color-text-primary, #1e1e1e);
|
|
204
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { createApp, defineComponent, h, nextTick, ref, type App } from "vue";
|
|
3
|
+
|
|
4
|
+
import HkDiffViewer from "./HkDiffViewer";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* HkDiffViewer — the rendered half: hunk grouping, collapsed unchanged
|
|
8
|
+
* regions and the async expander contract (loadContext round-trips,
|
|
9
|
+
* anchor policy, failure/retry, reset on diff change).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const apps: App[] = [];
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
for (const app of apps.splice(0)) app.unmount();
|
|
15
|
+
document.body.innerHTML = "";
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
function mount(props: Record<string, unknown>) {
|
|
19
|
+
const container = document.createElement("div");
|
|
20
|
+
document.body.appendChild(container);
|
|
21
|
+
const Root = defineComponent({
|
|
22
|
+
setup() {
|
|
23
|
+
return () => h(HkDiffViewer, props as never);
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
const app = createApp(Root);
|
|
27
|
+
app.mount(container);
|
|
28
|
+
apps.push(app);
|
|
29
|
+
return container;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Diff with a 9-line leading gap, one mixed hunk, and a 5-line trailing gap. */
|
|
33
|
+
const DIFF = [
|
|
34
|
+
"--- a/a.toml",
|
|
35
|
+
"+++ b/a.toml",
|
|
36
|
+
"@@ -10,4 +10,5 @@",
|
|
37
|
+
" ctx-a",
|
|
38
|
+
"-old",
|
|
39
|
+
"+new-1",
|
|
40
|
+
"+new-2",
|
|
41
|
+
" ctx-b",
|
|
42
|
+
" ctx-c",
|
|
43
|
+
"",
|
|
44
|
+
].join("\n");
|
|
45
|
+
|
|
46
|
+
const tick = () => new Promise((r) => setTimeout(r, 0));
|
|
47
|
+
|
|
48
|
+
describe("HkDiffViewer", () => {
|
|
49
|
+
it("groups the diff into hunk cards with per-hunk stats and both-side numbers", async () => {
|
|
50
|
+
const root = mount({ diff: DIFF, totalLines: 19 });
|
|
51
|
+
await nextTick();
|
|
52
|
+
const head = root.querySelector(".hk-diff-viewer__hunk-head")!;
|
|
53
|
+
expect(head.textContent).toContain("@@ -10,4 +10,5 @@");
|
|
54
|
+
expect(head.querySelector('[data-kind="add"]')!.textContent).toBe("+2");
|
|
55
|
+
expect(head.querySelector('[data-kind="del"]')!.textContent).toBe("−1");
|
|
56
|
+
const lines = [...root.querySelectorAll(".hk-diff-viewer__line")];
|
|
57
|
+
// 6 hunk lines (3 context + 1 del + 2 add); gap rows are not .line
|
|
58
|
+
expect(lines).toHaveLength(6);
|
|
59
|
+
const kinds = lines.map((l) => l.getAttribute("data-kind"));
|
|
60
|
+
expect(kinds).toEqual(["context", "del", "add", "add", "context", "context"]);
|
|
61
|
+
const delLine = lines[1];
|
|
62
|
+
expect(delLine.querySelectorAll(".hk-diff-viewer__no")[0].textContent).toBe("11");
|
|
63
|
+
expect(delLine.querySelectorAll(".hk-diff-viewer__no")[1].textContent).toBe("");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("renders unchanged regions as static dividers without a loadContext fetcher", async () => {
|
|
67
|
+
const root = mount({ diff: DIFF, totalLines: 19 });
|
|
68
|
+
await nextTick();
|
|
69
|
+
const gaps = [...root.querySelectorAll(".hk-diff-viewer__gap")];
|
|
70
|
+
expect(gaps).toHaveLength(2); // leading 9 + trailing 5
|
|
71
|
+
expect(gaps[0].tagName).toBe("DIV"); // not a button — nothing to fetch with
|
|
72
|
+
expect(gaps[0].textContent).toContain("9");
|
|
73
|
+
expect(gaps[1].textContent).toContain("5");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("expands an unchanged region asynchronously via loadContext", async () => {
|
|
77
|
+
const seen: Array<[number, number]> = [];
|
|
78
|
+
const loadContext = vi.fn(async (start: number, count: number) => {
|
|
79
|
+
seen.push([start, count]);
|
|
80
|
+
return Array.from({ length: count }, (_, i) => `line-${start + i}`);
|
|
81
|
+
});
|
|
82
|
+
const root = mount({ diff: DIFF, totalLines: 19, loadContext, contextChunk: 4 });
|
|
83
|
+
await nextTick();
|
|
84
|
+
|
|
85
|
+
// Trailing gap [15..19] (5 lines, anchor start): first click fetches 15..18.
|
|
86
|
+
const trailing = root.querySelectorAll(".hk-diff-viewer__gap")[1] as HTMLButtonElement;
|
|
87
|
+
expect(trailing.tagName).toBe("BUTTON");
|
|
88
|
+
trailing.click();
|
|
89
|
+
await tick();
|
|
90
|
+
await nextTick();
|
|
91
|
+
expect(seen).toEqual([[15, 4]]);
|
|
92
|
+
let text = root.textContent!;
|
|
93
|
+
expect(text).toContain("line-15");
|
|
94
|
+
expect(text).toContain("line-18");
|
|
95
|
+
// One line remains hidden → the expander stays, now counting 1.
|
|
96
|
+
const after = root.querySelectorAll(".hk-diff-viewer__gap")[1] as HTMLButtonElement;
|
|
97
|
+
expect(after.textContent).toContain("1");
|
|
98
|
+
|
|
99
|
+
// Second click completes the region; the divider disappears entirely.
|
|
100
|
+
after.click();
|
|
101
|
+
await tick();
|
|
102
|
+
await nextTick();
|
|
103
|
+
expect(seen[1]).toEqual([19, 1]);
|
|
104
|
+
expect(root.querySelectorAll(".hk-diff-viewer__gap")).toHaveLength(1); // only the leading gap
|
|
105
|
+
text = root.textContent!;
|
|
106
|
+
expect(text).toContain("line-19");
|
|
107
|
+
// Revealed context lines carry both-side numbers (shift = 0 here).
|
|
108
|
+
const revealed = [...root.querySelectorAll(".hk-diff-viewer__line[data-kind='context']")];
|
|
109
|
+
expect(revealed.length).toBeGreaterThan(4);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("expands the LEADING gap bottom-up (lines nearest the hunk first)", async () => {
|
|
113
|
+
const seen: Array<[number, number]> = [];
|
|
114
|
+
const loadContext = vi.fn(async (start: number, count: number) => {
|
|
115
|
+
seen.push([start, count]);
|
|
116
|
+
return Array.from({ length: count }, (_, i) => `L${start + i}`);
|
|
117
|
+
});
|
|
118
|
+
const root = mount({ diff: DIFF, totalLines: 19, loadContext, contextChunk: 4 });
|
|
119
|
+
await nextTick();
|
|
120
|
+
const leading = root.querySelectorAll(".hk-diff-viewer__gap")[0] as HTMLButtonElement;
|
|
121
|
+
leading.click();
|
|
122
|
+
await tick();
|
|
123
|
+
await nextTick();
|
|
124
|
+
expect(seen).toEqual([[6, 4]]); // bottom 4 of the 9-line leading gap
|
|
125
|
+
const text = root.textContent!;
|
|
126
|
+
expect(text).toContain("L6");
|
|
127
|
+
expect(text).toContain("L9");
|
|
128
|
+
expect(text).not.toContain("L5");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("marks a failed fetch inline and retries on the next click", async () => {
|
|
132
|
+
let calls = 0;
|
|
133
|
+
const loadContext = vi.fn(async () => {
|
|
134
|
+
calls += 1;
|
|
135
|
+
if (calls === 1) return null; // transport failure
|
|
136
|
+
return ["recovered"];
|
|
137
|
+
});
|
|
138
|
+
const root = mount({
|
|
139
|
+
diff: DIFF,
|
|
140
|
+
totalLines: 19,
|
|
141
|
+
loadContext,
|
|
142
|
+
contextChunk: 1, // trailing gap is 5 lines; irrelevant — leading used
|
|
143
|
+
});
|
|
144
|
+
await nextTick();
|
|
145
|
+
const gap = root.querySelectorAll(".hk-diff-viewer__gap")[1] as HTMLButtonElement;
|
|
146
|
+
gap.click();
|
|
147
|
+
await tick();
|
|
148
|
+
await nextTick();
|
|
149
|
+
expect(root.querySelector(".hk-diff-viewer__gap[data-state='failed']")).toBeTruthy();
|
|
150
|
+
expect(loadContext).toHaveBeenCalledTimes(1);
|
|
151
|
+
|
|
152
|
+
(root.querySelector(".hk-diff-viewer__gap[data-state='failed']") as HTMLButtonElement).click();
|
|
153
|
+
await tick();
|
|
154
|
+
await nextTick();
|
|
155
|
+
expect(loadContext).toHaveBeenCalledTimes(2);
|
|
156
|
+
expect(root.querySelector(".hk-diff-viewer__gap[data-state='failed']")).toBeNull();
|
|
157
|
+
expect(root.textContent).toContain("recovered");
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("bottom-aligns a SHORT response in the leading gap (R1)", async () => {
|
|
161
|
+
// A clipping host answers fewer lines than requested: for the
|
|
162
|
+
// bottom-anchored leading gap the arrived lines are the ones NEAREST
|
|
163
|
+
// the hunk — storing them top-aligned would permanently hide those.
|
|
164
|
+
const seen: Array<[number, number]> = [];
|
|
165
|
+
const loadContext = vi.fn(async (start: number, count: number) => {
|
|
166
|
+
seen.push([start, count]);
|
|
167
|
+
// Clips to 2 lines regardless of the requested window.
|
|
168
|
+
return ["L8", "L9"].slice(0, Math.min(2, count));
|
|
169
|
+
});
|
|
170
|
+
const root = mount({ diff: DIFF, totalLines: 19, loadContext, contextChunk: 4 });
|
|
171
|
+
await nextTick();
|
|
172
|
+
const leading = root.querySelectorAll(".hk-diff-viewer__gap")[0] as HTMLButtonElement;
|
|
173
|
+
leading.click();
|
|
174
|
+
await tick();
|
|
175
|
+
await nextTick();
|
|
176
|
+
expect(seen).toEqual([[6, 4]]); // bottom 4 of the 9-line gap requested…
|
|
177
|
+
const text = root.textContent!;
|
|
178
|
+
expect(text).toContain("L8"); // …but the 2 that arrived render…
|
|
179
|
+
expect(text).toContain("L9"); // …nearest the hunk (bottom-aligned)
|
|
180
|
+
expect(text).not.toContain("L6");
|
|
181
|
+
// 7 lines remain hidden; the expander persists with the new count.
|
|
182
|
+
const after = root.querySelectorAll(".hk-diff-viewer__gap")[0] as HTMLButtonElement;
|
|
183
|
+
expect(after.textContent).toContain("7");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("collapses and re-expands a hunk via its header", async () => {
|
|
187
|
+
const root = mount({ diff: DIFF, totalLines: 19 });
|
|
188
|
+
await nextTick();
|
|
189
|
+
const head = root.querySelector(".hk-diff-viewer__hunk-head") as HTMLButtonElement;
|
|
190
|
+
expect(root.querySelectorAll(".hk-diff-viewer__line")).toHaveLength(6);
|
|
191
|
+
head.click();
|
|
192
|
+
await nextTick();
|
|
193
|
+
expect(root.querySelectorAll(".hk-diff-viewer__line")).toHaveLength(0);
|
|
194
|
+
head.click();
|
|
195
|
+
await nextTick();
|
|
196
|
+
expect(root.querySelectorAll(".hk-diff-viewer__line")).toHaveLength(6);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("resets gap state when the diff prop changes", async () => {
|
|
200
|
+
const loadContext = vi.fn(async (start: number, count: number) =>
|
|
201
|
+
Array.from({ length: count }, (_, i) => `line-${start + i}`),
|
|
202
|
+
);
|
|
203
|
+
const container = document.createElement("div");
|
|
204
|
+
document.body.appendChild(container);
|
|
205
|
+
const diffRef = ref(DIFF);
|
|
206
|
+
const Root = defineComponent({
|
|
207
|
+
setup() {
|
|
208
|
+
return () =>
|
|
209
|
+
h(HkDiffViewer, {
|
|
210
|
+
diff: diffRef.value,
|
|
211
|
+
totalLines: 19,
|
|
212
|
+
loadContext,
|
|
213
|
+
contextChunk: 100,
|
|
214
|
+
} as never);
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
const app = createApp(Root);
|
|
218
|
+
app.mount(container);
|
|
219
|
+
apps.push(app);
|
|
220
|
+
await nextTick();
|
|
221
|
+
|
|
222
|
+
(container.querySelector(".hk-diff-viewer__gap") as HTMLButtonElement).click();
|
|
223
|
+
await tick();
|
|
224
|
+
await nextTick();
|
|
225
|
+
expect(container.textContent).toContain("line-1");
|
|
226
|
+
|
|
227
|
+
// A different diff must drop the fetched lines and recompute gaps.
|
|
228
|
+
diffRef.value = "@@ -3,2 +3,2 @@\n-a\n+b\n";
|
|
229
|
+
await nextTick();
|
|
230
|
+
expect(container.textContent).not.toContain("line-1");
|
|
231
|
+
expect(container.textContent).toContain("15"); // trailing gap of the new diff
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("degrades unparseable input to a raw <pre>", async () => {
|
|
235
|
+
const root = mount({ diff: "not a diff\nat all" });
|
|
236
|
+
await nextTick();
|
|
237
|
+
expect(root.querySelector(".hk-diff-viewer__raw")!.textContent).toContain("not a diff");
|
|
238
|
+
expect(root.querySelector(".hk-diff-viewer__hunk")).toBeNull();
|
|
239
|
+
});
|
|
240
|
+
});
|