gitmaps 1.1.0 → 1.1.2
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/README.md +267 -118
- package/app/[...slug]/page.client.tsx +1 -0
- package/app/[...slug]/page.tsx +6 -0
- package/app/analytics.db +0 -0
- package/app/api/analytics/route.ts +64 -0
- package/app/api/auth/positions/route.ts +95 -33
- package/app/api/build-info/route.ts +19 -0
- package/app/api/chat/route.ts +13 -2
- package/app/api/og-image/route.ts +14 -0
- package/app/api/repo/file-content/route.ts +73 -20
- package/app/api/repo/load/route.test.ts +62 -0
- package/app/api/repo/load/route.ts +41 -1
- package/app/api/repo/pdf-thumb/route.ts +127 -0
- package/app/api/repo/resolve-slug/route.ts +51 -0
- package/app/api/repo/tree/route.ts +188 -104
- package/app/api/version/route.ts +26 -0
- package/app/globals.css +5706 -4938
- package/app/layout.tsx +1279 -490
- package/app/lib/auto-arrange.test.ts +158 -0
- package/app/lib/auto-arrange.ts +147 -0
- package/app/lib/canvas-export.ts +358 -358
- package/app/lib/canvas.ts +625 -564
- package/app/lib/cards.tsx +1361 -916
- package/app/lib/chat.tsx +65 -9
- package/app/lib/code-editor.ts +86 -2
- package/app/lib/context.test.ts +32 -0
- package/app/lib/context.ts +19 -3
- package/app/lib/cursor-sharing.ts +34 -0
- package/app/lib/events.tsx +71 -93
- package/app/lib/export-canvas.ts +287 -0
- package/app/lib/file-card-plugin.ts +148 -148
- package/app/lib/file-modal.tsx +49 -0
- package/app/lib/file-preview.ts +486 -427
- package/app/lib/github-import.test.ts +424 -0
- package/app/lib/initial-route-hydration.test.ts +283 -0
- package/app/lib/initial-route-hydration.ts +202 -0
- package/app/lib/landing-reset.test.ts +99 -0
- package/app/lib/landing-reset.ts +106 -0
- package/app/lib/landing-shell.test.ts +75 -0
- package/app/lib/large-repo-optimization.ts +37 -0
- package/app/lib/layout-snapshots.ts +320 -0
- package/app/lib/loading.test.ts +69 -0
- package/app/lib/loading.tsx +160 -45
- package/app/lib/mount-cleanup.test.ts +52 -0
- package/app/lib/mount-cleanup.ts +34 -0
- package/app/lib/mount-init.test.ts +123 -0
- package/app/lib/mount-init.ts +107 -0
- package/app/lib/mount-lifecycle.test.ts +39 -0
- package/app/lib/mount-lifecycle.ts +12 -0
- package/app/lib/mount-route-wiring.test.ts +87 -0
- package/app/lib/mount-route-wiring.ts +84 -0
- package/app/lib/multi-repo.ts +14 -0
- package/app/lib/onboarding-tutorial.ts +278 -0
- package/app/lib/positions.ts +190 -121
- package/app/lib/recent-commits.test.ts +947 -0
- package/app/lib/recent-commits.ts +227 -0
- package/app/lib/repo-handoff.test.ts +23 -0
- package/app/lib/repo-handoff.ts +16 -0
- package/app/lib/repo-progressive.ts +119 -0
- package/app/lib/repo-select.test.ts +61 -0
- package/app/lib/repo-select.ts +74 -0
- package/app/lib/repo.tsx +1383 -987
- package/app/lib/role.ts +228 -0
- package/app/lib/route-catchall.test.ts +27 -0
- package/app/lib/route-repo-entry.test.ts +95 -0
- package/app/lib/route-repo-entry.ts +36 -0
- package/app/lib/router-contract.test.ts +22 -0
- package/app/lib/router-contract.ts +19 -0
- package/app/lib/shared-layout.test.ts +86 -0
- package/app/lib/shared-layout.ts +82 -0
- package/app/lib/status-bar.test.ts +118 -0
- package/app/lib/status-bar.ts +365 -128
- package/app/lib/sync-controls.test.ts +43 -0
- package/app/lib/sync-controls.tsx +303 -0
- package/app/lib/test-dom.ts +145 -0
- package/app/lib/test-fixtures/router-contract/[...slug]/page.tsx +3 -0
- package/app/lib/test-fixtures/router-contract/api/health/route.ts +3 -0
- package/app/lib/test-fixtures/router-contract/api/version/route.ts +3 -0
- package/app/lib/test-fixtures/router-contract/galaxy-canvas/page.tsx +3 -0
- package/app/lib/test-fixtures/router-contract/page.tsx +3 -0
- package/app/lib/transclusion-smoke.test.ts +163 -0
- package/app/lib/tutorial.ts +301 -0
- package/app/lib/version.ts +93 -0
- package/app/lib/viewport-culling.ts +740 -735
- package/app/lib/virtual-files.ts +456 -0
- package/app/lib/webgl-text.ts +189 -0
- package/app/lib/{galaxydraw-bridge.ts → xydraw-bridge.ts} +485 -482
- package/app/lib/{galaxydraw.test.ts → xydraw.test.ts} +228 -229
- package/app/og-image.png +0 -0
- package/app/page.client.tsx +70 -269
- package/app/page.tsx +15 -16
- package/app/state/machine.js +13 -0
- package/package.json +84 -75
- package/server.ts +10 -0
- package/app/[owner]/[repo]/page.tsx +0 -6
- package/app/[slug]/page.tsx +0 -6
- package/packages/galaxydraw/README.md +0 -296
- package/packages/galaxydraw/banner.png +0 -0
- package/packages/galaxydraw/demo/build-static.ts +0 -100
- package/packages/galaxydraw/demo/client.ts +0 -154
- package/packages/galaxydraw/demo/dist/client.js +0 -8
- package/packages/galaxydraw/demo/index.html +0 -256
- package/packages/galaxydraw/demo/server.ts +0 -96
- package/packages/galaxydraw/dist/index.js +0 -984
- package/packages/galaxydraw/dist/index.js.map +0 -16
- package/packages/galaxydraw/node_modules/.bin/tsc.bunx +0 -0
- package/packages/galaxydraw/node_modules/.bin/tsc.exe +0 -0
- package/packages/galaxydraw/node_modules/.bin/tsserver.bunx +0 -0
- package/packages/galaxydraw/node_modules/.bin/tsserver.exe +0 -0
- package/packages/galaxydraw/package.json +0 -49
- package/packages/galaxydraw/perf.test.ts +0 -284
- package/packages/galaxydraw/src/core/cards.ts +0 -435
- package/packages/galaxydraw/src/core/engine.ts +0 -339
- package/packages/galaxydraw/src/core/events.ts +0 -81
- package/packages/galaxydraw/src/core/layout.ts +0 -136
- package/packages/galaxydraw/src/core/minimap.ts +0 -216
- package/packages/galaxydraw/src/core/state.ts +0 -177
- package/packages/galaxydraw/src/core/viewport.ts +0 -106
- package/packages/galaxydraw/src/galaxydraw.css +0 -166
- package/packages/galaxydraw/src/index.ts +0 -40
- package/packages/galaxydraw/tsconfig.json +0 -30
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test';
|
|
2
|
+
import { setupGithubImport } from './events';
|
|
3
|
+
import { installFetchMock, installWindowOpenMock, setupDomTest } from './test-dom';
|
|
4
|
+
|
|
5
|
+
function makeSseResponse(chunks: string[]): Response {
|
|
6
|
+
const stream = new ReadableStream({
|
|
7
|
+
start(controller) {
|
|
8
|
+
const enc = new TextEncoder();
|
|
9
|
+
for (const chunk of chunks) controller.enqueue(enc.encode(chunk));
|
|
10
|
+
controller.close();
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
return new Response(stream, {
|
|
14
|
+
headers: { 'Content-Type': 'text/event-stream' },
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe('GitHub import modal smoke', () => {
|
|
19
|
+
let cleanup: (() => void) | undefined;
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
const handle = setupDomTest({
|
|
23
|
+
url: 'http://localhost:3335/',
|
|
24
|
+
html: `
|
|
25
|
+
<button id="githubImportBtn">Import GitHub</button>
|
|
26
|
+
<div class="github-modal" id="githubModal">
|
|
27
|
+
<div class="github-modal-backdrop"></div>
|
|
28
|
+
<div class="github-modal-content">
|
|
29
|
+
<button id="githubModalClose">×</button>
|
|
30
|
+
<input id="githubUserInput" />
|
|
31
|
+
<select id="githubSortSelect">
|
|
32
|
+
<option value="updated">Recently Updated</option>
|
|
33
|
+
<option value="stars">Most Stars</option>
|
|
34
|
+
<option value="name">Name A→Z</option>
|
|
35
|
+
</select>
|
|
36
|
+
<button id="githubSearchBtn">Search</button>
|
|
37
|
+
<div id="githubUrlCloneRow" style="display:none">
|
|
38
|
+
<span id="githubDetectedUrl"></span>
|
|
39
|
+
<button id="githubUrlCloneBtn">Clone & Open</button>
|
|
40
|
+
</div>
|
|
41
|
+
<div id="githubFilterRow" style="display:none">
|
|
42
|
+
<input id="githubRepoFilter" />
|
|
43
|
+
</div>
|
|
44
|
+
<div id="githubProfile" style="display:none"></div>
|
|
45
|
+
<div id="githubReposGrid"></div>
|
|
46
|
+
<div id="githubPagination" style="display:none">
|
|
47
|
+
<button id="githubPrevPage"></button>
|
|
48
|
+
<span id="githubPageInfo"></span>
|
|
49
|
+
<button id="githubNextPage"></button>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
<div id="cloneStatus"></div>
|
|
54
|
+
<select id="repoSelect"></select>
|
|
55
|
+
`,
|
|
56
|
+
});
|
|
57
|
+
cleanup = handle.cleanup;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
afterEach(() => {
|
|
61
|
+
cleanup?.();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('opens, searches, renders repos, and filters results', async () => {
|
|
65
|
+
const fetchMock = mock(async (input: string) => {
|
|
66
|
+
expect(input).toBe('/api/github/repos?user=7flash&page=1&sort=updated');
|
|
67
|
+
return new Response(JSON.stringify({
|
|
68
|
+
profile: {
|
|
69
|
+
login: '7flash',
|
|
70
|
+
name: '7flash',
|
|
71
|
+
public_repos: 2,
|
|
72
|
+
type: 'User',
|
|
73
|
+
avatar_url: 'https://example.com/avatar.png',
|
|
74
|
+
bio: 'builder',
|
|
75
|
+
},
|
|
76
|
+
repos: [
|
|
77
|
+
{
|
|
78
|
+
name: 'gitmaps',
|
|
79
|
+
clone_url: 'https://github.com/7flash/gitmaps.git',
|
|
80
|
+
description: 'Spatial code explorer',
|
|
81
|
+
language: 'TypeScript',
|
|
82
|
+
size: 2048,
|
|
83
|
+
updated_at: new Date().toISOString(),
|
|
84
|
+
stars: 10,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'jsx-ai',
|
|
88
|
+
clone_url: 'https://github.com/7flash/jsx-ai.git',
|
|
89
|
+
description: 'JSX interface for structured LLM calls',
|
|
90
|
+
language: 'TypeScript',
|
|
91
|
+
size: 512,
|
|
92
|
+
updated_at: new Date().toISOString(),
|
|
93
|
+
stars: 5,
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
page: 1,
|
|
97
|
+
hasNext: true,
|
|
98
|
+
hasPrev: false,
|
|
99
|
+
}), {
|
|
100
|
+
headers: { 'Content-Type': 'application/json' },
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const openMock = mock(() => null as any);
|
|
105
|
+
const fetchHandle = installFetchMock(fetchMock as any);
|
|
106
|
+
const openHandle = installWindowOpenMock(openMock as any);
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
setupGithubImport({} as any);
|
|
110
|
+
|
|
111
|
+
const openBtn = document.getElementById('githubImportBtn') as HTMLButtonElement;
|
|
112
|
+
const modal = document.getElementById('githubModal') as HTMLElement;
|
|
113
|
+
const userInput = document.getElementById('githubUserInput') as HTMLInputElement;
|
|
114
|
+
const searchBtn = document.getElementById('githubSearchBtn') as HTMLButtonElement;
|
|
115
|
+
const profile = document.getElementById('githubProfile') as HTMLElement;
|
|
116
|
+
const grid = document.getElementById('githubReposGrid') as HTMLElement;
|
|
117
|
+
const filterRow = document.getElementById('githubFilterRow') as HTMLElement;
|
|
118
|
+
const filterInput = document.getElementById('githubRepoFilter') as HTMLInputElement;
|
|
119
|
+
const pageInfo = document.getElementById('githubPageInfo') as HTMLElement;
|
|
120
|
+
const nextBtn = document.getElementById('githubNextPage') as HTMLButtonElement;
|
|
121
|
+
|
|
122
|
+
openBtn.click();
|
|
123
|
+
expect(modal.classList.contains('active')).toBe(true);
|
|
124
|
+
|
|
125
|
+
userInput.value = '7flash';
|
|
126
|
+
searchBtn.click();
|
|
127
|
+
await Promise.resolve();
|
|
128
|
+
await Promise.resolve();
|
|
129
|
+
|
|
130
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
131
|
+
expect(localStorage.getItem('gitcanvas:lastGithubUser')).toBe('7flash');
|
|
132
|
+
expect(profile.style.display).toBe('flex');
|
|
133
|
+
expect(profile.textContent).toContain('@7flash');
|
|
134
|
+
expect(grid.textContent).toContain('gitmaps');
|
|
135
|
+
expect(grid.textContent).toContain('jsx-ai');
|
|
136
|
+
expect(filterRow.style.display).toBe('flex');
|
|
137
|
+
expect(pageInfo.textContent).toBe('Page 1');
|
|
138
|
+
expect(nextBtn.disabled).toBe(false);
|
|
139
|
+
|
|
140
|
+
filterInput.value = 'jsx';
|
|
141
|
+
filterInput.dispatchEvent(new window.Event('input', { bubbles: true }));
|
|
142
|
+
|
|
143
|
+
const cards = Array.from(document.querySelectorAll('.github-repo-card')) as HTMLElement[];
|
|
144
|
+
const visibleNames = cards.filter(card => card.style.display !== 'none').map(card => card.dataset.name);
|
|
145
|
+
expect(visibleNames).toEqual(['jsx-ai']);
|
|
146
|
+
|
|
147
|
+
cards[0].click();
|
|
148
|
+
expect(openMock).toHaveBeenCalledWith('https://github.com/7flash/gitmaps', '_blank');
|
|
149
|
+
} finally {
|
|
150
|
+
fetchHandle.restore();
|
|
151
|
+
openHandle.restore();
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('detects direct GitHub URLs and shows clone affordance', () => {
|
|
156
|
+
setupGithubImport({} as any);
|
|
157
|
+
|
|
158
|
+
const userInput = document.getElementById('githubUserInput') as HTMLInputElement;
|
|
159
|
+
const urlRow = document.getElementById('githubUrlCloneRow') as HTMLElement;
|
|
160
|
+
const detected = document.getElementById('githubDetectedUrl') as HTMLElement;
|
|
161
|
+
|
|
162
|
+
userInput.value = 'https://github.com/7flash/jsx-ai';
|
|
163
|
+
userInput.dispatchEvent(new window.Event('input', { bubbles: true }));
|
|
164
|
+
|
|
165
|
+
expect(urlRow.style.display).toBe('flex');
|
|
166
|
+
expect(detected.textContent).toBe('7flash/jsx-ai');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('pressing Enter on a direct GitHub URL starts clone-stream and closes the modal', async () => {
|
|
170
|
+
const fetchMock = mock(() => new Promise<Response>(() => {}));
|
|
171
|
+
const fetchHandle = installFetchMock(fetchMock as any);
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
setupGithubImport({} as any);
|
|
175
|
+
|
|
176
|
+
const openBtn = document.getElementById('githubImportBtn') as HTMLButtonElement;
|
|
177
|
+
const modal = document.getElementById('githubModal') as HTMLElement;
|
|
178
|
+
const userInput = document.getElementById('githubUserInput') as HTMLInputElement;
|
|
179
|
+
const cloneStatus = document.getElementById('cloneStatus') as HTMLElement;
|
|
180
|
+
|
|
181
|
+
openBtn.click();
|
|
182
|
+
expect(modal.classList.contains('active')).toBe(true);
|
|
183
|
+
|
|
184
|
+
userInput.value = 'https://github.com/7flash/jsx-ai';
|
|
185
|
+
userInput.dispatchEvent(new window.Event('input', { bubbles: true }));
|
|
186
|
+
userInput.dispatchEvent(new window.KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
|
|
187
|
+
await Promise.resolve();
|
|
188
|
+
|
|
189
|
+
expect(modal.classList.contains('active')).toBe(false);
|
|
190
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
191
|
+
expect(fetchMock.mock.calls[0]?.[0]).toBe('/api/repo/clone-stream');
|
|
192
|
+
expect(String(fetchMock.mock.calls[0]?.[1]?.body)).toContain('https://github.com/7flash/jsx-ai.git');
|
|
193
|
+
expect(cloneStatus.style.display).toBe('block');
|
|
194
|
+
expect(cloneStatus.textContent).toContain('Cloning');
|
|
195
|
+
} finally {
|
|
196
|
+
fetchHandle.restore();
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test('repo card clone button starts clone-stream without opening the GitHub page', async () => {
|
|
201
|
+
const fetchMock = mock(async (input: string) => {
|
|
202
|
+
if (input.startsWith('/api/github/repos')) {
|
|
203
|
+
return new Response(JSON.stringify({
|
|
204
|
+
profile: {
|
|
205
|
+
login: '7flash',
|
|
206
|
+
name: '7flash',
|
|
207
|
+
public_repos: 1,
|
|
208
|
+
type: 'User',
|
|
209
|
+
avatar_url: 'https://example.com/avatar.png',
|
|
210
|
+
},
|
|
211
|
+
repos: [
|
|
212
|
+
{
|
|
213
|
+
name: 'gitmaps',
|
|
214
|
+
clone_url: 'https://github.com/7flash/gitmaps.git',
|
|
215
|
+
description: 'Spatial code explorer',
|
|
216
|
+
language: 'TypeScript',
|
|
217
|
+
size: 2048,
|
|
218
|
+
updated_at: new Date().toISOString(),
|
|
219
|
+
stars: 10,
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
page: 1,
|
|
223
|
+
hasNext: false,
|
|
224
|
+
hasPrev: false,
|
|
225
|
+
}), { headers: { 'Content-Type': 'application/json' } });
|
|
226
|
+
}
|
|
227
|
+
return new Promise<Response>(() => {});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const openMock = mock(() => null as any);
|
|
231
|
+
const fetchHandle = installFetchMock(fetchMock as any);
|
|
232
|
+
const openHandle = installWindowOpenMock(openMock as any);
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
setupGithubImport({} as any);
|
|
236
|
+
|
|
237
|
+
const openBtn = document.getElementById('githubImportBtn') as HTMLButtonElement;
|
|
238
|
+
const userInput = document.getElementById('githubUserInput') as HTMLInputElement;
|
|
239
|
+
const searchBtn = document.getElementById('githubSearchBtn') as HTMLButtonElement;
|
|
240
|
+
const modal = document.getElementById('githubModal') as HTMLElement;
|
|
241
|
+
const cloneStatus = document.getElementById('cloneStatus') as HTMLElement;
|
|
242
|
+
|
|
243
|
+
openBtn.click();
|
|
244
|
+
userInput.value = '7flash';
|
|
245
|
+
searchBtn.click();
|
|
246
|
+
await Promise.resolve();
|
|
247
|
+
await Promise.resolve();
|
|
248
|
+
|
|
249
|
+
const cloneBtn = document.querySelector('.github-clone-btn[data-url="https://github.com/7flash/gitmaps.git"]') as HTMLButtonElement;
|
|
250
|
+
expect(cloneBtn).toBeTruthy();
|
|
251
|
+
|
|
252
|
+
cloneBtn.click();
|
|
253
|
+
await Promise.resolve();
|
|
254
|
+
|
|
255
|
+
expect(fetchMock).toHaveBeenCalledTimes(2);
|
|
256
|
+
expect(fetchMock.mock.calls[1]?.[0]).toBe('/api/repo/clone-stream');
|
|
257
|
+
expect(String(fetchMock.mock.calls[1]?.[1]?.body)).toContain('https://github.com/7flash/gitmaps.git');
|
|
258
|
+
expect(modal.classList.contains('active')).toBe(false);
|
|
259
|
+
expect(cloneStatus.style.display).toBe('block');
|
|
260
|
+
expect(cloneStatus.textContent).toContain('Cloning');
|
|
261
|
+
expect(openMock).not.toHaveBeenCalled();
|
|
262
|
+
} finally {
|
|
263
|
+
fetchHandle.restore();
|
|
264
|
+
openHandle.restore();
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test('cached clone response shows success and selects the cached repo path', async () => {
|
|
269
|
+
const onRepoReady = mock(() => undefined);
|
|
270
|
+
const ctx = {
|
|
271
|
+
actor: { send: mock(() => undefined) },
|
|
272
|
+
onRepoReady,
|
|
273
|
+
snap: () => ({ context: { repoPath: '', zoom: 1, offsetX: 0, offsetY: 0, commits: [] }, value: { view: 'allfiles' } }),
|
|
274
|
+
fileCards: new Map(),
|
|
275
|
+
deferredCards: new Map(),
|
|
276
|
+
changedFilePaths: new Set(),
|
|
277
|
+
positions: new Map(),
|
|
278
|
+
hiddenFiles: new Set(),
|
|
279
|
+
allFilesData: [],
|
|
280
|
+
commitFilesData: [],
|
|
281
|
+
canvas: document.createElement('div'),
|
|
282
|
+
canvasViewport: document.createElement('div'),
|
|
283
|
+
svgOverlay: null,
|
|
284
|
+
loadingOverlay: null,
|
|
285
|
+
} as any;
|
|
286
|
+
|
|
287
|
+
const fetchMock = mock(async (input: string) => {
|
|
288
|
+
if (input === '/api/repo/clone-stream') {
|
|
289
|
+
return new Response(JSON.stringify({ path: 'C:/Code/gitmaps' }), {
|
|
290
|
+
headers: { 'Content-Type': 'application/json' },
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
if (input === '/api/repo/load') {
|
|
294
|
+
return new Promise<Response>(() => {});
|
|
295
|
+
}
|
|
296
|
+
throw new Error(`Unexpected fetch: ${input}`);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const fetchHandle = installFetchMock(fetchMock as any);
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
setupGithubImport(ctx);
|
|
303
|
+
|
|
304
|
+
const openBtn = document.getElementById('githubImportBtn') as HTMLButtonElement;
|
|
305
|
+
const userInput = document.getElementById('githubUserInput') as HTMLInputElement;
|
|
306
|
+
const cloneBtn = document.getElementById('githubUrlCloneBtn') as HTMLButtonElement;
|
|
307
|
+
const cloneStatus = document.getElementById('cloneStatus') as HTMLElement;
|
|
308
|
+
const repoSelect = document.getElementById('repoSelect') as HTMLSelectElement;
|
|
309
|
+
|
|
310
|
+
openBtn.click();
|
|
311
|
+
userInput.value = 'https://github.com/7flash/gitmaps';
|
|
312
|
+
userInput.dispatchEvent(new window.Event('input', { bubbles: true }));
|
|
313
|
+
cloneBtn.click();
|
|
314
|
+
await Promise.resolve();
|
|
315
|
+
await Promise.resolve();
|
|
316
|
+
|
|
317
|
+
expect(cloneStatus.className).toContain('success');
|
|
318
|
+
expect(cloneStatus.textContent).toContain('Updated — loading');
|
|
319
|
+
expect(repoSelect.value).toBe('C:/Code/gitmaps');
|
|
320
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
321
|
+
expect(onRepoReady).toHaveBeenCalledWith('C:/Code/gitmaps');
|
|
322
|
+
} finally {
|
|
323
|
+
fetchHandle.restore();
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test('SSE clone done updates success state and selects the cloned repo path', async () => {
|
|
328
|
+
const onRepoReady = mock(() => undefined);
|
|
329
|
+
const ctx = {
|
|
330
|
+
actor: { send: mock(() => undefined) },
|
|
331
|
+
onRepoReady,
|
|
332
|
+
snap: () => ({ context: { repoPath: '', zoom: 1, offsetX: 0, offsetY: 0, commits: [] }, value: { view: 'allfiles' } }),
|
|
333
|
+
fileCards: new Map(),
|
|
334
|
+
deferredCards: new Map(),
|
|
335
|
+
changedFilePaths: new Set(),
|
|
336
|
+
positions: new Map(),
|
|
337
|
+
hiddenFiles: new Set(),
|
|
338
|
+
allFilesData: [],
|
|
339
|
+
commitFilesData: [],
|
|
340
|
+
canvas: document.createElement('div'),
|
|
341
|
+
canvasViewport: document.createElement('div'),
|
|
342
|
+
svgOverlay: null,
|
|
343
|
+
loadingOverlay: null,
|
|
344
|
+
} as any;
|
|
345
|
+
|
|
346
|
+
const fetchMock = mock(async (input: string) => {
|
|
347
|
+
if (input === '/api/repo/clone-stream') {
|
|
348
|
+
return makeSseResponse([
|
|
349
|
+
'event: progress\ndata: {"message":"Resolving repository","percent":25}\n\n',
|
|
350
|
+
'event: done\ndata: {"path":"C:/Code/jsx-ai"}\n\n',
|
|
351
|
+
]);
|
|
352
|
+
}
|
|
353
|
+
if (input === '/api/repo/load') {
|
|
354
|
+
return new Promise<Response>(() => {});
|
|
355
|
+
}
|
|
356
|
+
throw new Error(`Unexpected fetch: ${input}`);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
const fetchHandle = installFetchMock(fetchMock as any);
|
|
360
|
+
|
|
361
|
+
try {
|
|
362
|
+
setupGithubImport(ctx);
|
|
363
|
+
|
|
364
|
+
const openBtn = document.getElementById('githubImportBtn') as HTMLButtonElement;
|
|
365
|
+
const userInput = document.getElementById('githubUserInput') as HTMLInputElement;
|
|
366
|
+
const cloneBtn = document.getElementById('githubUrlCloneBtn') as HTMLButtonElement;
|
|
367
|
+
const cloneStatus = document.getElementById('cloneStatus') as HTMLElement;
|
|
368
|
+
const repoSelect = document.getElementById('repoSelect') as HTMLSelectElement;
|
|
369
|
+
|
|
370
|
+
openBtn.click();
|
|
371
|
+
userInput.value = 'https://github.com/7flash/jsx-ai';
|
|
372
|
+
userInput.dispatchEvent(new window.Event('input', { bubbles: true }));
|
|
373
|
+
cloneBtn.click();
|
|
374
|
+
await Promise.resolve();
|
|
375
|
+
await Promise.resolve();
|
|
376
|
+
await Promise.resolve();
|
|
377
|
+
|
|
378
|
+
expect(cloneStatus.className).toContain('success');
|
|
379
|
+
expect(cloneStatus.textContent).toContain('Cloned — loading');
|
|
380
|
+
expect(repoSelect.value).toBe('C:/Code/jsx-ai');
|
|
381
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
382
|
+
expect(onRepoReady).toHaveBeenCalledWith('C:/Code/jsx-ai');
|
|
383
|
+
} finally {
|
|
384
|
+
fetchHandle.restore();
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
test('SSE clone error updates the clone status to an error state', async () => {
|
|
389
|
+
const fetchMock = mock(async (input: string) => {
|
|
390
|
+
if (input === '/api/repo/clone-stream') {
|
|
391
|
+
return makeSseResponse([
|
|
392
|
+
'event: progress\ndata: {"message":"Cloning","percent":40}\n\n',
|
|
393
|
+
'event: error\ndata: {"error":"Repository not found"}\n\n',
|
|
394
|
+
]);
|
|
395
|
+
}
|
|
396
|
+
throw new Error(`Unexpected fetch: ${input}`);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
const fetchHandle = installFetchMock(fetchMock as any);
|
|
400
|
+
|
|
401
|
+
try {
|
|
402
|
+
setupGithubImport({} as any);
|
|
403
|
+
|
|
404
|
+
const openBtn = document.getElementById('githubImportBtn') as HTMLButtonElement;
|
|
405
|
+
const userInput = document.getElementById('githubUserInput') as HTMLInputElement;
|
|
406
|
+
const cloneBtn = document.getElementById('githubUrlCloneBtn') as HTMLButtonElement;
|
|
407
|
+
const cloneStatus = document.getElementById('cloneStatus') as HTMLElement;
|
|
408
|
+
|
|
409
|
+
openBtn.click();
|
|
410
|
+
userInput.value = 'https://github.com/7flash/missing-repo';
|
|
411
|
+
userInput.dispatchEvent(new window.Event('input', { bubbles: true }));
|
|
412
|
+
cloneBtn.click();
|
|
413
|
+
await Promise.resolve();
|
|
414
|
+
await Promise.resolve();
|
|
415
|
+
await Promise.resolve();
|
|
416
|
+
|
|
417
|
+
expect(cloneStatus.className).toContain('error');
|
|
418
|
+
expect(cloneStatus.textContent).toContain('Repository not found');
|
|
419
|
+
} finally {
|
|
420
|
+
fetchHandle.restore();
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
|