blob: 6b5df654b137a81f1e00a7df230754047e548a89 [file] [log] [blame]
Philip Zeyliger272a90e2025-05-16 14:49:51 -07001// mock-git-data-service.ts
2// Mock implementation of GitDataService for the demo environment
3
Autoformatter8c463622025-05-16 21:54:17 +00004import { GitDataService, GitDiffFile } from "../git-data-service";
5import { GitLogEntry } from "../../types";
Philip Zeyliger272a90e2025-05-16 14:49:51 -07006
7/**
8 * Demo implementation of GitDataService with canned responses
9 */
10export class MockGitDataService implements GitDataService {
11 constructor() {
Autoformatter8c463622025-05-16 21:54:17 +000012 console.log("MockGitDataService instance created");
Philip Zeyliger272a90e2025-05-16 14:49:51 -070013 }
14
15 // Mock commit history
16 private mockCommits: GitLogEntry[] = [
17 {
Autoformatter8c463622025-05-16 21:54:17 +000018 hash: "abc123456789",
19 subject: "Implement new file picker UI",
Philip Zeyliger364b35a2025-06-21 16:39:04 -070020 refs: ["HEAD", "origin/main", "refs/heads/feature/file-picker"],
Philip Zeyliger272a90e2025-05-16 14:49:51 -070021 },
22 {
Autoformatter8c463622025-05-16 21:54:17 +000023 hash: "def987654321",
Philip Zeyliger364b35a2025-06-21 16:39:04 -070024 subject: "Add range picker component and improve styling",
25 refs: [
26 "origin/feature/range-picker",
27 "refs/heads/feature/ui-improvements",
28 "refs/remotes/origin/dev",
29 ],
Philip Zeyliger272a90e2025-05-16 14:49:51 -070030 },
31 {
Autoformatter8c463622025-05-16 21:54:17 +000032 hash: "ghi456789123",
Philip Zeyliger364b35a2025-06-21 16:39:04 -070033 subject: "Fix styling issues in navigation and add responsive design",
34 refs: ["refs/heads/hotfix/styling", "refs/tags/v1.2.0"],
Philip Zeyliger272a90e2025-05-16 14:49:51 -070035 },
36 {
Autoformatter8c463622025-05-16 21:54:17 +000037 hash: "jkl789123456",
38 subject: "Initial commit",
39 refs: ["sketch-base"],
40 },
Philip Zeyliger272a90e2025-05-16 14:49:51 -070041 ];
42
43 // Mock diff files for various scenarios
44 private mockDiffFiles: GitDiffFile[] = [
45 {
Autoformatter8c463622025-05-16 21:54:17 +000046 path: "src/components/FilePicker.js",
Josh Bleecher Snyderbcc1c412025-05-29 00:36:49 +000047 old_path: "",
Autoformatter8c463622025-05-16 21:54:17 +000048 status: "A",
49 new_mode: "100644",
50 old_mode: "000000",
51 old_hash: "0000000000000000000000000000000000000000",
52 new_hash: "def0123456789abcdef0123456789abcdef0123",
Philip Zeyligere89b3082025-05-29 03:16:06 +000053 additions: 54,
54 deletions: 0,
Philip Zeyliger272a90e2025-05-16 14:49:51 -070055 },
56 {
Autoformatter8c463622025-05-16 21:54:17 +000057 path: "src/components/RangePicker.js",
Josh Bleecher Snyderbcc1c412025-05-29 00:36:49 +000058 old_path: "",
Autoformatter8c463622025-05-16 21:54:17 +000059 status: "A",
60 new_mode: "100644",
61 old_mode: "000000",
62 old_hash: "0000000000000000000000000000000000000000",
63 new_hash: "cde0123456789abcdef0123456789abcdef0123",
Philip Zeyligere89b3082025-05-29 03:16:06 +000064 additions: 32,
65 deletions: 0,
Philip Zeyliger272a90e2025-05-16 14:49:51 -070066 },
67 {
Autoformatter8c463622025-05-16 21:54:17 +000068 path: "src/components/App.js",
Josh Bleecher Snyderbcc1c412025-05-29 00:36:49 +000069 old_path: "",
Autoformatter8c463622025-05-16 21:54:17 +000070 status: "M",
71 new_mode: "100644",
72 old_mode: "100644",
73 old_hash: "abc0123456789abcdef0123456789abcdef0123",
74 new_hash: "bcd0123456789abcdef0123456789abcdef0123",
Philip Zeyligere89b3082025-05-29 03:16:06 +000075 additions: 15,
76 deletions: 3,
Philip Zeyliger272a90e2025-05-16 14:49:51 -070077 },
78 {
Josh Bleecher Snyderb3aff882025-07-01 02:17:27 +000079 path: "src/components/DialogPicker.js",
80 old_path: "src/components/FilePicker.js",
81 status: "C85",
82 new_mode: "100644",
83 old_mode: "100644",
84 old_hash: "def0123456789abcdef0123456789abcdef0123",
85 new_hash: "hij0123456789abcdef0123456789abcdef0123",
86 additions: 8,
87 deletions: 2,
88 },
89 {
Autoformatter9556fcf2025-07-02 22:48:51 +000090 path: "src/components/RangeSelector.js",
Josh Bleecher Snyderb3aff882025-07-01 02:17:27 +000091 old_path: "src/components/RangePicker.js",
92 status: "R95",
93 new_mode: "100644",
94 old_mode: "100644",
95 old_hash: "cde0123456789abcdef0123456789abcdef0123",
96 new_hash: "klm0123456789abcdef0123456789abcdef0123",
97 additions: 5,
98 deletions: 3,
99 },
100 {
Autoformatter8c463622025-05-16 21:54:17 +0000101 path: "src/styles/main.css",
Josh Bleecher Snyderbcc1c412025-05-29 00:36:49 +0000102 old_path: "",
Autoformatter8c463622025-05-16 21:54:17 +0000103 status: "M",
104 new_mode: "100644",
105 old_mode: "100644",
106 old_hash: "fgh0123456789abcdef0123456789abcdef0123",
107 new_hash: "ghi0123456789abcdef0123456789abcdef0123",
Philip Zeyligere89b3082025-05-29 03:16:06 +0000108 additions: 25,
109 deletions: 8,
Autoformatter8c463622025-05-16 21:54:17 +0000110 },
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700111 ];
112
113 // Mock file content for different files and commits
114 private appJSOriginal = `function App() {
115 return (
116 <div className="app">
117 <header>
118 <h1>Git Diff Viewer</h1>
119 </header>
120 <main>
121 <p>Select a file to view differences</p>
122 </main>
123 </div>
124 );
125}`;
126
127 private appJSModified = `function App() {
128 const [files, setFiles] = useState([]);
129 const [selectedFile, setSelectedFile] = useState(null);
130
131 // Load commits and files
132 useEffect(() => {
133 // Code to load commits would go here
134 // setCommits(...);
135 }, []);
136
137 return (
138 <div className="app">
139 <header>
140 <h1>Git Diff Viewer</h1>
141 </header>
142 <main>
143 <FilePicker files={files} onFileSelect={setSelectedFile} />
144 <div className="diff-view">
145 {selectedFile ? (
146 <div>Diff view for {selectedFile.path}</div>
147 ) : (
148 <p>Select a file to view differences</p>
149 )}
150 </div>
151 </main>
152 </div>
153 );
154}`;
155
156 private filePickerJS = `function FilePicker({ files, onFileSelect }) {
157 const [selectedIndex, setSelectedIndex] = useState(0);
158
159 useEffect(() => {
160 // Reset selection when files change
161 setSelectedIndex(0);
162 if (files.length > 0) {
163 onFileSelect(files[0]);
164 }
165 }, [files, onFileSelect]);
166
167 const handleNext = () => {
168 if (selectedIndex < files.length - 1) {
169 const newIndex = selectedIndex + 1;
170 setSelectedIndex(newIndex);
171 onFileSelect(files[newIndex]);
172 }
173 };
174
175 const handlePrevious = () => {
176 if (selectedIndex > 0) {
177 const newIndex = selectedIndex - 1;
178 setSelectedIndex(newIndex);
179 onFileSelect(files[newIndex]);
180 }
181 };
182
183 return (
184 <div className="file-picker">
185 <select value={selectedIndex} onChange={(e) => {
186 const index = parseInt(e.target.value, 10);
187 setSelectedIndex(index);
188 onFileSelect(files[index]);
189 }}>
190 {files.map((file, index) => (
191 <option key={file.path} value={index}>
192 {file.status} {file.path}
193 </option>
194 ))}
195 </select>
196
197 <div className="navigation-buttons">
198 <button
199 onClick={handlePrevious}
200 disabled={selectedIndex === 0}
201 >
202 Previous
203 </button>
204 <button
205 onClick={handleNext}
206 disabled={selectedIndex === files.length - 1}
207 >
208 Next
209 </button>
210 </div>
211 </div>
212 );
213}`;
214
215 private rangePickerJS = `function RangePicker({ commits, onRangeChange }) {
216 const [rangeType, setRangeType] = useState('range');
217 const [startCommit, setStartCommit] = useState(commits[0]);
218 const [endCommit, setEndCommit] = useState(commits[commits.length - 1]);
219
220 const handleTypeChange = (e) => {
221 setRangeType(e.target.value);
222 if (e.target.value === 'single') {
223 onRangeChange({ type: 'single', commit: startCommit });
224 } else {
225 onRangeChange({ type: 'range', from: startCommit, to: endCommit });
226 }
227 };
228
229 return (
230 <div className="range-picker">
231 <div className="range-type-selector">
232 <label>
233 <input
234 type="radio"
235 value="range"
236 checked={rangeType === 'range'}
237 onChange={handleTypeChange}
238 />
239 Commit Range
240 </label>
241 <label>
242 <input
243 type="radio"
244 value="single"
245 checked={rangeType === 'single'}
246 onChange={handleTypeChange}
247 />
248 Single Commit
249 </label>
250 </div>
251 </div>
252 );
253}`;
254
Josh Bleecher Snyderb3aff882025-07-01 02:17:27 +0000255 private dialogPickerJS = `function DialogPicker({ files, onFileSelect, onClose }) {
256 const [selectedIndex, setSelectedIndex] = useState(0);
257 const [showDialog, setShowDialog] = useState(false);
258
259 // Similar to FilePicker but with modal dialog functionality
260 useEffect(() => {
261 setSelectedIndex(0);
262 if (files.length > 0) {
263 onFileSelect(files[0]);
264 }
265 }, [files, onFileSelect]);
266
267 return (
268 <div className="dialog-picker">
269 <button onClick={() => setShowDialog(true)}>
270 Open File Dialog
271 </button>
272 {showDialog && (
273 <div className="modal-overlay" onClick={() => setShowDialog(false)}>
274 <div className="modal-content" onClick={(e) => e.stopPropagation()}>
275 <h3>Select File</h3>
276 <select value={selectedIndex} onChange={(e) => {
277 const index = parseInt(e.target.value, 10);
278 setSelectedIndex(index);
279 onFileSelect(files[index]);
280 }}>
281 {files.map((file, index) => (
282 <option key={file.path} value={index}>
283 {file.status} {file.path}
284 </option>
285 ))}
286 </select>
287 <div className="modal-buttons">
288 <button onClick={() => setShowDialog(false)}>Close</button>
289 </div>
290 </div>
291 </div>
292 )}
293 </div>
294 );
295}`;
296
297 private rangeSelectorJS = `function RangeSelector({ commits, onRangeChange, allowCustomRange = true }) {
298 const [rangeType, setRangeType] = useState('range');
299 const [startCommit, setStartCommit] = useState(commits[0]);
300 const [endCommit, setEndCommit] = useState(commits[commits.length - 1]);
301 const [customRange, setCustomRange] = useState('');
302
303 const handleTypeChange = (e) => {
304 setRangeType(e.target.value);
305 if (e.target.value === 'single') {
306 onRangeChange({ type: 'single', commit: startCommit });
307 } else if (e.target.value === 'custom' && customRange) {
308 onRangeChange({ type: 'custom', range: customRange });
309 } else {
310 onRangeChange({ type: 'range', from: startCommit, to: endCommit });
311 }
312 };
313
314 return (
315 <div className="range-selector">
316 <div className="range-type-selector">
317 <label>
318 <input
319 type="radio"
320 value="range"
321 checked={rangeType === 'range'}
322 onChange={handleTypeChange}
323 />
324 Commit Range
325 </label>
326 <label>
327 <input
328 type="radio"
329 value="single"
330 checked={rangeType === 'single'}
331 onChange={handleTypeChange}
332 />
333 Single Commit
334 </label>
335 {allowCustomRange && (
336 <label>
337 <input
338 type="radio"
339 value="custom"
340 checked={rangeType === 'custom'}
341 onChange={handleTypeChange}
342 />
343 Custom Range
344 </label>
345 )}
346 </div>
347 {rangeType === 'custom' && (
348 <input
349 type="text"
350 placeholder="Enter custom range (e.g., HEAD~5..HEAD)"
351 value={customRange}
352 onChange={(e) => setCustomRange(e.target.value)}
353 />
354 )}
355 </div>
356 );
357}`;
358
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700359 private mainCSSOriginal = `body {
360 font-family: sans-serif;
361 margin: 0;
362 padding: 0;
363}
364
365.app {
366 max-width: 1200px;
367 margin: 0 auto;
368 padding: 20px;
369}
370
371header {
372 margin-bottom: 20px;
373}
374
375h1 {
376 color: #333;
377}`;
378
379 private mainCSSModified = `body {
380 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
381 margin: 0;
382 padding: 0;
383 background-color: #f5f5f5;
384}
385
386.app {
387 max-width: 1200px;
388 margin: 0 auto;
389 padding: 20px;
390}
391
392header {
393 margin-bottom: 20px;
394 border-bottom: 1px solid #ddd;
395 padding-bottom: 10px;
396}
397
398h1 {
399 color: #333;
400}
401
402.file-picker {
403 display: flex;
404 gap: 8px;
405 align-items: center;
406 margin-bottom: 20px;
407}
408
409.file-picker select {
410 flex: 1;
411 padding: 8px;
412 border-radius: 4px;
413 border: 1px solid #ddd;
414}
415
416.navigation-buttons {
417 display: flex;
418 gap: 8px;
419}
420
421button {
422 padding: 8px 12px;
423 background-color: #4a7dfc;
424 color: white;
425 border: none;
426 border-radius: 4px;
427 cursor: pointer;
428}`;
429
430 async getCommitHistory(initialCommit?: string): Promise<GitLogEntry[]> {
Autoformatter8c463622025-05-16 21:54:17 +0000431 console.log(
432 `[MockGitDataService] Getting commit history from ${initialCommit || "beginning"}`,
433 );
434
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700435 // If initialCommit is provided, return commits from that commit to HEAD
436 if (initialCommit) {
Autoformatter8c463622025-05-16 21:54:17 +0000437 const startIndex = this.mockCommits.findIndex(
438 (commit) => commit.hash === initialCommit,
439 );
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700440 if (startIndex >= 0) {
441 return this.mockCommits.slice(0, startIndex + 1);
442 }
443 }
Autoformatter8c463622025-05-16 21:54:17 +0000444
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700445 return [...this.mockCommits];
446 }
447
448 async getDiff(from: string, to: string): Promise<GitDiffFile[]> {
449 console.log(`[MockGitDataService] Getting diff from ${from} to ${to}`);
Autoformatter8c463622025-05-16 21:54:17 +0000450
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700451 return [...this.mockDiffFiles];
452 }
453
454 async getCommitDiff(commit: string): Promise<GitDiffFile[]> {
455 console.log(`[MockGitDataService] Getting diff for commit ${commit}`);
Autoformatter8c463622025-05-16 21:54:17 +0000456
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700457 // Return a subset of files for specific commits
Autoformatter8c463622025-05-16 21:54:17 +0000458 if (commit === "abc123456789") {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700459 return this.mockDiffFiles.slice(0, 2);
Autoformatter8c463622025-05-16 21:54:17 +0000460 } else if (commit === "def987654321") {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700461 return this.mockDiffFiles.slice(1, 3);
462 }
Autoformatter8c463622025-05-16 21:54:17 +0000463
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700464 // For other commits, return all files
465 return [...this.mockDiffFiles];
466 }
467
468 async getFileContent(fileHash: string): Promise<string> {
Autoformatter8c463622025-05-16 21:54:17 +0000469 console.log(
470 `[MockGitDataService] Getting file content for hash: ${fileHash}`,
471 );
472
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700473 // Return different content based on the file hash
Autoformatter8c463622025-05-16 21:54:17 +0000474 if (fileHash === "bcd0123456789abcdef0123456789abcdef0123") {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700475 return this.appJSModified;
Autoformatter8c463622025-05-16 21:54:17 +0000476 } else if (fileHash === "abc0123456789abcdef0123456789abcdef0123") {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700477 return this.appJSOriginal;
Autoformatter8c463622025-05-16 21:54:17 +0000478 } else if (fileHash === "def0123456789abcdef0123456789abcdef0123") {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700479 return this.filePickerJS;
Autoformatter8c463622025-05-16 21:54:17 +0000480 } else if (fileHash === "cde0123456789abcdef0123456789abcdef0123") {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700481 return this.rangePickerJS;
Josh Bleecher Snyderb3aff882025-07-01 02:17:27 +0000482 } else if (fileHash === "hij0123456789abcdef0123456789abcdef0123") {
483 return this.dialogPickerJS;
484 } else if (fileHash === "klm0123456789abcdef0123456789abcdef0123") {
485 return this.rangeSelectorJS;
Autoformatter8c463622025-05-16 21:54:17 +0000486 } else if (fileHash === "ghi0123456789abcdef0123456789abcdef0123") {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700487 return this.mainCSSModified;
Autoformatter8c463622025-05-16 21:54:17 +0000488 } else if (fileHash === "fgh0123456789abcdef0123456789abcdef0123") {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700489 return this.mainCSSOriginal;
490 }
Autoformatter8c463622025-05-16 21:54:17 +0000491
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700492 // Return empty string for unknown file hashes
Autoformatter8c463622025-05-16 21:54:17 +0000493 return "";
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700494 }
495
496 async getBaseCommitRef(): Promise<string> {
Autoformatter8c463622025-05-16 21:54:17 +0000497 console.log("[MockGitDataService] Getting base commit ref");
498
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700499 // Find the commit with the sketch-base ref
Autoformatter8c463622025-05-16 21:54:17 +0000500 const baseCommit = this.mockCommits.find(
501 (commit) => commit.refs && commit.refs.includes("sketch-base"),
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700502 );
Autoformatter8c463622025-05-16 21:54:17 +0000503
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700504 if (baseCommit) {
505 return baseCommit.hash;
506 }
Autoformatter8c463622025-05-16 21:54:17 +0000507
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700508 // Fallback to the last commit in our list
509 return this.mockCommits[this.mockCommits.length - 1].hash;
510 }
511
512 // Helper to simulate network delay
513 private delay(ms: number): Promise<void> {
Autoformatter8c463622025-05-16 21:54:17 +0000514 return new Promise((resolve) => setTimeout(resolve, ms));
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700515 }
Autoformatter8c463622025-05-16 21:54:17 +0000516
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700517 async getWorkingCopyContent(filePath: string): Promise<string> {
Autoformatter8c463622025-05-16 21:54:17 +0000518 console.log(
519 `[MockGitDataService] Getting working copy content for path: ${filePath}`,
520 );
521
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700522 // Return different content based on the file path
Autoformatter8c463622025-05-16 21:54:17 +0000523 if (filePath === "src/components/App.js") {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700524 return this.appJSModified;
Autoformatter8c463622025-05-16 21:54:17 +0000525 } else if (filePath === "src/components/FilePicker.js") {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700526 return this.filePickerJS;
Autoformatter8c463622025-05-16 21:54:17 +0000527 } else if (filePath === "src/components/RangePicker.js") {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700528 return this.rangePickerJS;
Josh Bleecher Snyderb3aff882025-07-01 02:17:27 +0000529 } else if (filePath === "src/components/DialogPicker.js") {
530 return this.dialogPickerJS;
531 } else if (filePath === "src/components/RangeSelector.js") {
532 return this.rangeSelectorJS;
Autoformatter8c463622025-05-16 21:54:17 +0000533 } else if (filePath === "src/styles/main.css") {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700534 return this.mainCSSModified;
535 }
Autoformatter8c463622025-05-16 21:54:17 +0000536
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700537 // Return empty string for unknown file paths
Autoformatter8c463622025-05-16 21:54:17 +0000538 return "";
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700539 }
Autoformatter8c463622025-05-16 21:54:17 +0000540
541 async getUnstagedChanges(from: string = "HEAD"): Promise<GitDiffFile[]> {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700542 console.log(`[MockGitDataService] Getting unstaged changes from ${from}`);
Autoformatter8c463622025-05-16 21:54:17 +0000543
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700544 // Create a new array of files with 0000000... as the new hashes
545 // to simulate unstaged changes
Autoformatter8c463622025-05-16 21:54:17 +0000546 return this.mockDiffFiles.map((file) => ({
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700547 ...file,
Philip Zeyligere89b3082025-05-29 03:16:06 +0000548 new_hash: "0000000000000000000000000000000000000000",
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700549 }));
550 }
Autoformatter8c463622025-05-16 21:54:17 +0000551
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700552 async saveFileContent(filePath: string, content: string): Promise<void> {
Autoformatter8c463622025-05-16 21:54:17 +0000553 console.log(
554 `[MockGitDataService] Saving file content for path: ${filePath}`,
555 );
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700556 // Simulate a network delay
557 await this.delay(500);
558 // In a mock implementation, we just log the save attempt
Autoformatter8c463622025-05-16 21:54:17 +0000559 console.log(
560 `File would be saved: ${filePath} (${content.length} characters)`,
561 );
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700562 // Return void as per interface
563 }
Autoformatter8c463622025-05-16 21:54:17 +0000564}