blob: 046d9eccde0921f80031440933b6776a19c168c1 [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 {
Autoformatter8c463622025-05-16 21:54:17 +000079 path: "src/styles/main.css",
Josh Bleecher Snyderbcc1c412025-05-29 00:36:49 +000080 old_path: "",
Autoformatter8c463622025-05-16 21:54:17 +000081 status: "M",
82 new_mode: "100644",
83 old_mode: "100644",
84 old_hash: "fgh0123456789abcdef0123456789abcdef0123",
85 new_hash: "ghi0123456789abcdef0123456789abcdef0123",
Philip Zeyligere89b3082025-05-29 03:16:06 +000086 additions: 25,
87 deletions: 8,
Autoformatter8c463622025-05-16 21:54:17 +000088 },
Philip Zeyliger272a90e2025-05-16 14:49:51 -070089 ];
90
91 // Mock file content for different files and commits
92 private appJSOriginal = `function App() {
93 return (
94 <div className="app">
95 <header>
96 <h1>Git Diff Viewer</h1>
97 </header>
98 <main>
99 <p>Select a file to view differences</p>
100 </main>
101 </div>
102 );
103}`;
104
105 private appJSModified = `function App() {
106 const [files, setFiles] = useState([]);
107 const [selectedFile, setSelectedFile] = useState(null);
108
109 // Load commits and files
110 useEffect(() => {
111 // Code to load commits would go here
112 // setCommits(...);
113 }, []);
114
115 return (
116 <div className="app">
117 <header>
118 <h1>Git Diff Viewer</h1>
119 </header>
120 <main>
121 <FilePicker files={files} onFileSelect={setSelectedFile} />
122 <div className="diff-view">
123 {selectedFile ? (
124 <div>Diff view for {selectedFile.path}</div>
125 ) : (
126 <p>Select a file to view differences</p>
127 )}
128 </div>
129 </main>
130 </div>
131 );
132}`;
133
134 private filePickerJS = `function FilePicker({ files, onFileSelect }) {
135 const [selectedIndex, setSelectedIndex] = useState(0);
136
137 useEffect(() => {
138 // Reset selection when files change
139 setSelectedIndex(0);
140 if (files.length > 0) {
141 onFileSelect(files[0]);
142 }
143 }, [files, onFileSelect]);
144
145 const handleNext = () => {
146 if (selectedIndex < files.length - 1) {
147 const newIndex = selectedIndex + 1;
148 setSelectedIndex(newIndex);
149 onFileSelect(files[newIndex]);
150 }
151 };
152
153 const handlePrevious = () => {
154 if (selectedIndex > 0) {
155 const newIndex = selectedIndex - 1;
156 setSelectedIndex(newIndex);
157 onFileSelect(files[newIndex]);
158 }
159 };
160
161 return (
162 <div className="file-picker">
163 <select value={selectedIndex} onChange={(e) => {
164 const index = parseInt(e.target.value, 10);
165 setSelectedIndex(index);
166 onFileSelect(files[index]);
167 }}>
168 {files.map((file, index) => (
169 <option key={file.path} value={index}>
170 {file.status} {file.path}
171 </option>
172 ))}
173 </select>
174
175 <div className="navigation-buttons">
176 <button
177 onClick={handlePrevious}
178 disabled={selectedIndex === 0}
179 >
180 Previous
181 </button>
182 <button
183 onClick={handleNext}
184 disabled={selectedIndex === files.length - 1}
185 >
186 Next
187 </button>
188 </div>
189 </div>
190 );
191}`;
192
193 private rangePickerJS = `function RangePicker({ commits, onRangeChange }) {
194 const [rangeType, setRangeType] = useState('range');
195 const [startCommit, setStartCommit] = useState(commits[0]);
196 const [endCommit, setEndCommit] = useState(commits[commits.length - 1]);
197
198 const handleTypeChange = (e) => {
199 setRangeType(e.target.value);
200 if (e.target.value === 'single') {
201 onRangeChange({ type: 'single', commit: startCommit });
202 } else {
203 onRangeChange({ type: 'range', from: startCommit, to: endCommit });
204 }
205 };
206
207 return (
208 <div className="range-picker">
209 <div className="range-type-selector">
210 <label>
211 <input
212 type="radio"
213 value="range"
214 checked={rangeType === 'range'}
215 onChange={handleTypeChange}
216 />
217 Commit Range
218 </label>
219 <label>
220 <input
221 type="radio"
222 value="single"
223 checked={rangeType === 'single'}
224 onChange={handleTypeChange}
225 />
226 Single Commit
227 </label>
228 </div>
229 </div>
230 );
231}`;
232
233 private mainCSSOriginal = `body {
234 font-family: sans-serif;
235 margin: 0;
236 padding: 0;
237}
238
239.app {
240 max-width: 1200px;
241 margin: 0 auto;
242 padding: 20px;
243}
244
245header {
246 margin-bottom: 20px;
247}
248
249h1 {
250 color: #333;
251}`;
252
253 private mainCSSModified = `body {
254 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
255 margin: 0;
256 padding: 0;
257 background-color: #f5f5f5;
258}
259
260.app {
261 max-width: 1200px;
262 margin: 0 auto;
263 padding: 20px;
264}
265
266header {
267 margin-bottom: 20px;
268 border-bottom: 1px solid #ddd;
269 padding-bottom: 10px;
270}
271
272h1 {
273 color: #333;
274}
275
276.file-picker {
277 display: flex;
278 gap: 8px;
279 align-items: center;
280 margin-bottom: 20px;
281}
282
283.file-picker select {
284 flex: 1;
285 padding: 8px;
286 border-radius: 4px;
287 border: 1px solid #ddd;
288}
289
290.navigation-buttons {
291 display: flex;
292 gap: 8px;
293}
294
295button {
296 padding: 8px 12px;
297 background-color: #4a7dfc;
298 color: white;
299 border: none;
300 border-radius: 4px;
301 cursor: pointer;
302}`;
303
304 async getCommitHistory(initialCommit?: string): Promise<GitLogEntry[]> {
Autoformatter8c463622025-05-16 21:54:17 +0000305 console.log(
306 `[MockGitDataService] Getting commit history from ${initialCommit || "beginning"}`,
307 );
308
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700309 // If initialCommit is provided, return commits from that commit to HEAD
310 if (initialCommit) {
Autoformatter8c463622025-05-16 21:54:17 +0000311 const startIndex = this.mockCommits.findIndex(
312 (commit) => commit.hash === initialCommit,
313 );
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700314 if (startIndex >= 0) {
315 return this.mockCommits.slice(0, startIndex + 1);
316 }
317 }
Autoformatter8c463622025-05-16 21:54:17 +0000318
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700319 return [...this.mockCommits];
320 }
321
322 async getDiff(from: string, to: string): Promise<GitDiffFile[]> {
323 console.log(`[MockGitDataService] Getting diff from ${from} to ${to}`);
Autoformatter8c463622025-05-16 21:54:17 +0000324
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700325 return [...this.mockDiffFiles];
326 }
327
328 async getCommitDiff(commit: string): Promise<GitDiffFile[]> {
329 console.log(`[MockGitDataService] Getting diff for commit ${commit}`);
Autoformatter8c463622025-05-16 21:54:17 +0000330
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700331 // Return a subset of files for specific commits
Autoformatter8c463622025-05-16 21:54:17 +0000332 if (commit === "abc123456789") {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700333 return this.mockDiffFiles.slice(0, 2);
Autoformatter8c463622025-05-16 21:54:17 +0000334 } else if (commit === "def987654321") {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700335 return this.mockDiffFiles.slice(1, 3);
336 }
Autoformatter8c463622025-05-16 21:54:17 +0000337
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700338 // For other commits, return all files
339 return [...this.mockDiffFiles];
340 }
341
342 async getFileContent(fileHash: string): Promise<string> {
Autoformatter8c463622025-05-16 21:54:17 +0000343 console.log(
344 `[MockGitDataService] Getting file content for hash: ${fileHash}`,
345 );
346
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700347 // Return different content based on the file hash
Autoformatter8c463622025-05-16 21:54:17 +0000348 if (fileHash === "bcd0123456789abcdef0123456789abcdef0123") {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700349 return this.appJSModified;
Autoformatter8c463622025-05-16 21:54:17 +0000350 } else if (fileHash === "abc0123456789abcdef0123456789abcdef0123") {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700351 return this.appJSOriginal;
Autoformatter8c463622025-05-16 21:54:17 +0000352 } else if (fileHash === "def0123456789abcdef0123456789abcdef0123") {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700353 return this.filePickerJS;
Autoformatter8c463622025-05-16 21:54:17 +0000354 } else if (fileHash === "cde0123456789abcdef0123456789abcdef0123") {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700355 return this.rangePickerJS;
Autoformatter8c463622025-05-16 21:54:17 +0000356 } else if (fileHash === "ghi0123456789abcdef0123456789abcdef0123") {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700357 return this.mainCSSModified;
Autoformatter8c463622025-05-16 21:54:17 +0000358 } else if (fileHash === "fgh0123456789abcdef0123456789abcdef0123") {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700359 return this.mainCSSOriginal;
360 }
Autoformatter8c463622025-05-16 21:54:17 +0000361
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700362 // Return empty string for unknown file hashes
Autoformatter8c463622025-05-16 21:54:17 +0000363 return "";
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700364 }
365
366 async getBaseCommitRef(): Promise<string> {
Autoformatter8c463622025-05-16 21:54:17 +0000367 console.log("[MockGitDataService] Getting base commit ref");
368
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700369 // Find the commit with the sketch-base ref
Autoformatter8c463622025-05-16 21:54:17 +0000370 const baseCommit = this.mockCommits.find(
371 (commit) => commit.refs && commit.refs.includes("sketch-base"),
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700372 );
Autoformatter8c463622025-05-16 21:54:17 +0000373
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700374 if (baseCommit) {
375 return baseCommit.hash;
376 }
Autoformatter8c463622025-05-16 21:54:17 +0000377
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700378 // Fallback to the last commit in our list
379 return this.mockCommits[this.mockCommits.length - 1].hash;
380 }
381
382 // Helper to simulate network delay
383 private delay(ms: number): Promise<void> {
Autoformatter8c463622025-05-16 21:54:17 +0000384 return new Promise((resolve) => setTimeout(resolve, ms));
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700385 }
Autoformatter8c463622025-05-16 21:54:17 +0000386
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700387 async getWorkingCopyContent(filePath: string): Promise<string> {
Autoformatter8c463622025-05-16 21:54:17 +0000388 console.log(
389 `[MockGitDataService] Getting working copy content for path: ${filePath}`,
390 );
391
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700392 // Return different content based on the file path
Autoformatter8c463622025-05-16 21:54:17 +0000393 if (filePath === "src/components/App.js") {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700394 return this.appJSModified;
Autoformatter8c463622025-05-16 21:54:17 +0000395 } else if (filePath === "src/components/FilePicker.js") {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700396 return this.filePickerJS;
Autoformatter8c463622025-05-16 21:54:17 +0000397 } else if (filePath === "src/components/RangePicker.js") {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700398 return this.rangePickerJS;
Autoformatter8c463622025-05-16 21:54:17 +0000399 } else if (filePath === "src/styles/main.css") {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700400 return this.mainCSSModified;
401 }
Autoformatter8c463622025-05-16 21:54:17 +0000402
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700403 // Return empty string for unknown file paths
Autoformatter8c463622025-05-16 21:54:17 +0000404 return "";
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700405 }
Autoformatter8c463622025-05-16 21:54:17 +0000406
407 async getUnstagedChanges(from: string = "HEAD"): Promise<GitDiffFile[]> {
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700408 console.log(`[MockGitDataService] Getting unstaged changes from ${from}`);
Autoformatter8c463622025-05-16 21:54:17 +0000409
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700410 // Create a new array of files with 0000000... as the new hashes
411 // to simulate unstaged changes
Autoformatter8c463622025-05-16 21:54:17 +0000412 return this.mockDiffFiles.map((file) => ({
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700413 ...file,
Philip Zeyligere89b3082025-05-29 03:16:06 +0000414 new_hash: "0000000000000000000000000000000000000000",
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700415 }));
416 }
Autoformatter8c463622025-05-16 21:54:17 +0000417
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700418 async saveFileContent(filePath: string, content: string): Promise<void> {
Autoformatter8c463622025-05-16 21:54:17 +0000419 console.log(
420 `[MockGitDataService] Saving file content for path: ${filePath}`,
421 );
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700422 // Simulate a network delay
423 await this.delay(500);
424 // In a mock implementation, we just log the save attempt
Autoformatter8c463622025-05-16 21:54:17 +0000425 console.log(
426 `File would be saved: ${filePath} (${content.length} characters)`,
427 );
Philip Zeyliger272a90e2025-05-16 14:49:51 -0700428 // Return void as per interface
429 }
Autoformatter8c463622025-05-16 21:54:17 +0000430}