blob: 74937f02928b98c64a2d51d37db8a3d59c5b0eb3 [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
4import { GitDataService, GitDiffFile } from '../git-data-service';
5import { GitLogEntry } from '../../types';
6
7/**
8 * Demo implementation of GitDataService with canned responses
9 */
10export class MockGitDataService implements GitDataService {
11 constructor() {
12 console.log('MockGitDataService instance created');
13 }
14
15 // Mock commit history
16 private mockCommits: GitLogEntry[] = [
17 {
18 hash: 'abc123456789',
19 subject: 'Implement new file picker UI',
20 refs: ['HEAD', 'main']
21 },
22 {
23 hash: 'def987654321',
24 subject: 'Add range picker component',
25 refs: []
26 },
27 {
28 hash: 'ghi456789123',
29 subject: 'Fix styling issues in navigation',
30 refs: []
31 },
32 {
33 hash: 'jkl789123456',
34 subject: 'Initial commit',
35 refs: ['sketch-base']
36 }
37 ];
38
39 // Mock diff files for various scenarios
40 private mockDiffFiles: GitDiffFile[] = [
41 {
42 path: 'src/components/FilePicker.js',
43 status: 'A',
44 new_mode: '100644',
45 old_mode: '000000',
46 old_hash: '0000000000000000000000000000000000000000',
47 new_hash: 'def0123456789abcdef0123456789abcdef0123'
48 },
49 {
50 path: 'src/components/RangePicker.js',
51 status: 'A',
52 new_mode: '100644',
53 old_mode: '000000',
54 old_hash: '0000000000000000000000000000000000000000',
55 new_hash: 'cde0123456789abcdef0123456789abcdef0123'
56 },
57 {
58 path: 'src/components/App.js',
59 status: 'M',
60 new_mode: '100644',
61 old_mode: '100644',
62 old_hash: 'abc0123456789abcdef0123456789abcdef0123',
63 new_hash: 'bcd0123456789abcdef0123456789abcdef0123'
64 },
65 {
66 path: 'src/styles/main.css',
67 status: 'M',
68 new_mode: '100644',
69 old_mode: '100644',
70 old_hash: 'fgh0123456789abcdef0123456789abcdef0123',
71 new_hash: 'ghi0123456789abcdef0123456789abcdef0123'
72 }
73 ];
74
75 // Mock file content for different files and commits
76 private appJSOriginal = `function App() {
77 return (
78 <div className="app">
79 <header>
80 <h1>Git Diff Viewer</h1>
81 </header>
82 <main>
83 <p>Select a file to view differences</p>
84 </main>
85 </div>
86 );
87}`;
88
89 private appJSModified = `function App() {
90 const [files, setFiles] = useState([]);
91 const [selectedFile, setSelectedFile] = useState(null);
92
93 // Load commits and files
94 useEffect(() => {
95 // Code to load commits would go here
96 // setCommits(...);
97 }, []);
98
99 return (
100 <div className="app">
101 <header>
102 <h1>Git Diff Viewer</h1>
103 </header>
104 <main>
105 <FilePicker files={files} onFileSelect={setSelectedFile} />
106 <div className="diff-view">
107 {selectedFile ? (
108 <div>Diff view for {selectedFile.path}</div>
109 ) : (
110 <p>Select a file to view differences</p>
111 )}
112 </div>
113 </main>
114 </div>
115 );
116}`;
117
118 private filePickerJS = `function FilePicker({ files, onFileSelect }) {
119 const [selectedIndex, setSelectedIndex] = useState(0);
120
121 useEffect(() => {
122 // Reset selection when files change
123 setSelectedIndex(0);
124 if (files.length > 0) {
125 onFileSelect(files[0]);
126 }
127 }, [files, onFileSelect]);
128
129 const handleNext = () => {
130 if (selectedIndex < files.length - 1) {
131 const newIndex = selectedIndex + 1;
132 setSelectedIndex(newIndex);
133 onFileSelect(files[newIndex]);
134 }
135 };
136
137 const handlePrevious = () => {
138 if (selectedIndex > 0) {
139 const newIndex = selectedIndex - 1;
140 setSelectedIndex(newIndex);
141 onFileSelect(files[newIndex]);
142 }
143 };
144
145 return (
146 <div className="file-picker">
147 <select value={selectedIndex} onChange={(e) => {
148 const index = parseInt(e.target.value, 10);
149 setSelectedIndex(index);
150 onFileSelect(files[index]);
151 }}>
152 {files.map((file, index) => (
153 <option key={file.path} value={index}>
154 {file.status} {file.path}
155 </option>
156 ))}
157 </select>
158
159 <div className="navigation-buttons">
160 <button
161 onClick={handlePrevious}
162 disabled={selectedIndex === 0}
163 >
164 Previous
165 </button>
166 <button
167 onClick={handleNext}
168 disabled={selectedIndex === files.length - 1}
169 >
170 Next
171 </button>
172 </div>
173 </div>
174 );
175}`;
176
177 private rangePickerJS = `function RangePicker({ commits, onRangeChange }) {
178 const [rangeType, setRangeType] = useState('range');
179 const [startCommit, setStartCommit] = useState(commits[0]);
180 const [endCommit, setEndCommit] = useState(commits[commits.length - 1]);
181
182 const handleTypeChange = (e) => {
183 setRangeType(e.target.value);
184 if (e.target.value === 'single') {
185 onRangeChange({ type: 'single', commit: startCommit });
186 } else {
187 onRangeChange({ type: 'range', from: startCommit, to: endCommit });
188 }
189 };
190
191 return (
192 <div className="range-picker">
193 <div className="range-type-selector">
194 <label>
195 <input
196 type="radio"
197 value="range"
198 checked={rangeType === 'range'}
199 onChange={handleTypeChange}
200 />
201 Commit Range
202 </label>
203 <label>
204 <input
205 type="radio"
206 value="single"
207 checked={rangeType === 'single'}
208 onChange={handleTypeChange}
209 />
210 Single Commit
211 </label>
212 </div>
213 </div>
214 );
215}`;
216
217 private mainCSSOriginal = `body {
218 font-family: sans-serif;
219 margin: 0;
220 padding: 0;
221}
222
223.app {
224 max-width: 1200px;
225 margin: 0 auto;
226 padding: 20px;
227}
228
229header {
230 margin-bottom: 20px;
231}
232
233h1 {
234 color: #333;
235}`;
236
237 private mainCSSModified = `body {
238 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
239 margin: 0;
240 padding: 0;
241 background-color: #f5f5f5;
242}
243
244.app {
245 max-width: 1200px;
246 margin: 0 auto;
247 padding: 20px;
248}
249
250header {
251 margin-bottom: 20px;
252 border-bottom: 1px solid #ddd;
253 padding-bottom: 10px;
254}
255
256h1 {
257 color: #333;
258}
259
260.file-picker {
261 display: flex;
262 gap: 8px;
263 align-items: center;
264 margin-bottom: 20px;
265}
266
267.file-picker select {
268 flex: 1;
269 padding: 8px;
270 border-radius: 4px;
271 border: 1px solid #ddd;
272}
273
274.navigation-buttons {
275 display: flex;
276 gap: 8px;
277}
278
279button {
280 padding: 8px 12px;
281 background-color: #4a7dfc;
282 color: white;
283 border: none;
284 border-radius: 4px;
285 cursor: pointer;
286}`;
287
288 async getCommitHistory(initialCommit?: string): Promise<GitLogEntry[]> {
289 console.log(`[MockGitDataService] Getting commit history from ${initialCommit || 'beginning'}`);
290
291 // If initialCommit is provided, return commits from that commit to HEAD
292 if (initialCommit) {
293 const startIndex = this.mockCommits.findIndex(commit => commit.hash === initialCommit);
294 if (startIndex >= 0) {
295 return this.mockCommits.slice(0, startIndex + 1);
296 }
297 }
298
299 return [...this.mockCommits];
300 }
301
302 async getDiff(from: string, to: string): Promise<GitDiffFile[]> {
303 console.log(`[MockGitDataService] Getting diff from ${from} to ${to}`);
304
305 return [...this.mockDiffFiles];
306 }
307
308 async getCommitDiff(commit: string): Promise<GitDiffFile[]> {
309 console.log(`[MockGitDataService] Getting diff for commit ${commit}`);
310
311 // Return a subset of files for specific commits
312 if (commit === 'abc123456789') {
313 return this.mockDiffFiles.slice(0, 2);
314 } else if (commit === 'def987654321') {
315 return this.mockDiffFiles.slice(1, 3);
316 }
317
318 // For other commits, return all files
319 return [...this.mockDiffFiles];
320 }
321
322 async getFileContent(fileHash: string): Promise<string> {
323 console.log(`[MockGitDataService] Getting file content for hash: ${fileHash}`);
324
325 // Return different content based on the file hash
326 if (fileHash === 'bcd0123456789abcdef0123456789abcdef0123') {
327 return this.appJSModified;
328 } else if (fileHash === 'abc0123456789abcdef0123456789abcdef0123') {
329 return this.appJSOriginal;
330 } else if (fileHash === 'def0123456789abcdef0123456789abcdef0123') {
331 return this.filePickerJS;
332 } else if (fileHash === 'cde0123456789abcdef0123456789abcdef0123') {
333 return this.rangePickerJS;
334 } else if (fileHash === 'ghi0123456789abcdef0123456789abcdef0123') {
335 return this.mainCSSModified;
336 } else if (fileHash === 'fgh0123456789abcdef0123456789abcdef0123') {
337 return this.mainCSSOriginal;
338 }
339
340 // Return empty string for unknown file hashes
341 return '';
342 }
343
344 async getBaseCommitRef(): Promise<string> {
345 console.log('[MockGitDataService] Getting base commit ref');
346
347 // Find the commit with the sketch-base ref
348 const baseCommit = this.mockCommits.find(commit =>
349 commit.refs && commit.refs.includes('sketch-base')
350 );
351
352 if (baseCommit) {
353 return baseCommit.hash;
354 }
355
356 // Fallback to the last commit in our list
357 return this.mockCommits[this.mockCommits.length - 1].hash;
358 }
359
360 // Helper to simulate network delay
361 private delay(ms: number): Promise<void> {
362 return new Promise(resolve => setTimeout(resolve, ms));
363 }
364
365 async getWorkingCopyContent(filePath: string): Promise<string> {
366 console.log(`[MockGitDataService] Getting working copy content for path: ${filePath}`);
367
368 // Return different content based on the file path
369 if (filePath === 'src/components/App.js') {
370 return this.appJSModified;
371 } else if (filePath === 'src/components/FilePicker.js') {
372 return this.filePickerJS;
373 } else if (filePath === 'src/components/RangePicker.js') {
374 return this.rangePickerJS;
375 } else if (filePath === 'src/styles/main.css') {
376 return this.mainCSSModified;
377 }
378
379 // Return empty string for unknown file paths
380 return '';
381 }
382
383 async getUnstagedChanges(from: string = 'HEAD'): Promise<GitDiffFile[]> {
384 console.log(`[MockGitDataService] Getting unstaged changes from ${from}`);
385
386 // Create a new array of files with 0000000... as the new hashes
387 // to simulate unstaged changes
388 return this.mockDiffFiles.map(file => ({
389 ...file,
390 newHash: '0000000000000000000000000000000000000000'
391 }));
392 }
393
394 async saveFileContent(filePath: string, content: string): Promise<void> {
395 console.log(`[MockGitDataService] Saving file content for path: ${filePath}`);
396 // Simulate a network delay
397 await this.delay(500);
398 // In a mock implementation, we just log the save attempt
399 console.log(`File would be saved: ${filePath} (${content.length} characters)`);
400 // Return void as per interface
401 }
402
403}