| // mock-git-data-service.ts |
| // Mock implementation of GitDataService for the demo environment |
| |
| import { GitDataService, GitDiffFile } from '../git-data-service'; |
| import { GitLogEntry } from '../../types'; |
| |
| /** |
| * Demo implementation of GitDataService with canned responses |
| */ |
| export class MockGitDataService implements GitDataService { |
| constructor() { |
| console.log('MockGitDataService instance created'); |
| } |
| |
| // Mock commit history |
| private mockCommits: GitLogEntry[] = [ |
| { |
| hash: 'abc123456789', |
| subject: 'Implement new file picker UI', |
| refs: ['HEAD', 'main'] |
| }, |
| { |
| hash: 'def987654321', |
| subject: 'Add range picker component', |
| refs: [] |
| }, |
| { |
| hash: 'ghi456789123', |
| subject: 'Fix styling issues in navigation', |
| refs: [] |
| }, |
| { |
| hash: 'jkl789123456', |
| subject: 'Initial commit', |
| refs: ['sketch-base'] |
| } |
| ]; |
| |
| // Mock diff files for various scenarios |
| private mockDiffFiles: GitDiffFile[] = [ |
| { |
| path: 'src/components/FilePicker.js', |
| status: 'A', |
| new_mode: '100644', |
| old_mode: '000000', |
| old_hash: '0000000000000000000000000000000000000000', |
| new_hash: 'def0123456789abcdef0123456789abcdef0123' |
| }, |
| { |
| path: 'src/components/RangePicker.js', |
| status: 'A', |
| new_mode: '100644', |
| old_mode: '000000', |
| old_hash: '0000000000000000000000000000000000000000', |
| new_hash: 'cde0123456789abcdef0123456789abcdef0123' |
| }, |
| { |
| path: 'src/components/App.js', |
| status: 'M', |
| new_mode: '100644', |
| old_mode: '100644', |
| old_hash: 'abc0123456789abcdef0123456789abcdef0123', |
| new_hash: 'bcd0123456789abcdef0123456789abcdef0123' |
| }, |
| { |
| path: 'src/styles/main.css', |
| status: 'M', |
| new_mode: '100644', |
| old_mode: '100644', |
| old_hash: 'fgh0123456789abcdef0123456789abcdef0123', |
| new_hash: 'ghi0123456789abcdef0123456789abcdef0123' |
| } |
| ]; |
| |
| // Mock file content for different files and commits |
| private appJSOriginal = `function App() { |
| return ( |
| <div className="app"> |
| <header> |
| <h1>Git Diff Viewer</h1> |
| </header> |
| <main> |
| <p>Select a file to view differences</p> |
| </main> |
| </div> |
| ); |
| }`; |
| |
| private appJSModified = `function App() { |
| const [files, setFiles] = useState([]); |
| const [selectedFile, setSelectedFile] = useState(null); |
| |
| // Load commits and files |
| useEffect(() => { |
| // Code to load commits would go here |
| // setCommits(...); |
| }, []); |
| |
| return ( |
| <div className="app"> |
| <header> |
| <h1>Git Diff Viewer</h1> |
| </header> |
| <main> |
| <FilePicker files={files} onFileSelect={setSelectedFile} /> |
| <div className="diff-view"> |
| {selectedFile ? ( |
| <div>Diff view for {selectedFile.path}</div> |
| ) : ( |
| <p>Select a file to view differences</p> |
| )} |
| </div> |
| </main> |
| </div> |
| ); |
| }`; |
| |
| private filePickerJS = `function FilePicker({ files, onFileSelect }) { |
| const [selectedIndex, setSelectedIndex] = useState(0); |
| |
| useEffect(() => { |
| // Reset selection when files change |
| setSelectedIndex(0); |
| if (files.length > 0) { |
| onFileSelect(files[0]); |
| } |
| }, [files, onFileSelect]); |
| |
| const handleNext = () => { |
| if (selectedIndex < files.length - 1) { |
| const newIndex = selectedIndex + 1; |
| setSelectedIndex(newIndex); |
| onFileSelect(files[newIndex]); |
| } |
| }; |
| |
| const handlePrevious = () => { |
| if (selectedIndex > 0) { |
| const newIndex = selectedIndex - 1; |
| setSelectedIndex(newIndex); |
| onFileSelect(files[newIndex]); |
| } |
| }; |
| |
| return ( |
| <div className="file-picker"> |
| <select value={selectedIndex} onChange={(e) => { |
| const index = parseInt(e.target.value, 10); |
| setSelectedIndex(index); |
| onFileSelect(files[index]); |
| }}> |
| {files.map((file, index) => ( |
| <option key={file.path} value={index}> |
| {file.status} {file.path} |
| </option> |
| ))} |
| </select> |
| |
| <div className="navigation-buttons"> |
| <button |
| onClick={handlePrevious} |
| disabled={selectedIndex === 0} |
| > |
| Previous |
| </button> |
| <button |
| onClick={handleNext} |
| disabled={selectedIndex === files.length - 1} |
| > |
| Next |
| </button> |
| </div> |
| </div> |
| ); |
| }`; |
| |
| private rangePickerJS = `function RangePicker({ commits, onRangeChange }) { |
| const [rangeType, setRangeType] = useState('range'); |
| const [startCommit, setStartCommit] = useState(commits[0]); |
| const [endCommit, setEndCommit] = useState(commits[commits.length - 1]); |
| |
| const handleTypeChange = (e) => { |
| setRangeType(e.target.value); |
| if (e.target.value === 'single') { |
| onRangeChange({ type: 'single', commit: startCommit }); |
| } else { |
| onRangeChange({ type: 'range', from: startCommit, to: endCommit }); |
| } |
| }; |
| |
| return ( |
| <div className="range-picker"> |
| <div className="range-type-selector"> |
| <label> |
| <input |
| type="radio" |
| value="range" |
| checked={rangeType === 'range'} |
| onChange={handleTypeChange} |
| /> |
| Commit Range |
| </label> |
| <label> |
| <input |
| type="radio" |
| value="single" |
| checked={rangeType === 'single'} |
| onChange={handleTypeChange} |
| /> |
| Single Commit |
| </label> |
| </div> |
| </div> |
| ); |
| }`; |
| |
| private mainCSSOriginal = `body { |
| font-family: sans-serif; |
| margin: 0; |
| padding: 0; |
| } |
| |
| .app { |
| max-width: 1200px; |
| margin: 0 auto; |
| padding: 20px; |
| } |
| |
| header { |
| margin-bottom: 20px; |
| } |
| |
| h1 { |
| color: #333; |
| }`; |
| |
| private mainCSSModified = `body { |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
| margin: 0; |
| padding: 0; |
| background-color: #f5f5f5; |
| } |
| |
| .app { |
| max-width: 1200px; |
| margin: 0 auto; |
| padding: 20px; |
| } |
| |
| header { |
| margin-bottom: 20px; |
| border-bottom: 1px solid #ddd; |
| padding-bottom: 10px; |
| } |
| |
| h1 { |
| color: #333; |
| } |
| |
| .file-picker { |
| display: flex; |
| gap: 8px; |
| align-items: center; |
| margin-bottom: 20px; |
| } |
| |
| .file-picker select { |
| flex: 1; |
| padding: 8px; |
| border-radius: 4px; |
| border: 1px solid #ddd; |
| } |
| |
| .navigation-buttons { |
| display: flex; |
| gap: 8px; |
| } |
| |
| button { |
| padding: 8px 12px; |
| background-color: #4a7dfc; |
| color: white; |
| border: none; |
| border-radius: 4px; |
| cursor: pointer; |
| }`; |
| |
| async getCommitHistory(initialCommit?: string): Promise<GitLogEntry[]> { |
| console.log(`[MockGitDataService] Getting commit history from ${initialCommit || 'beginning'}`); |
| |
| // If initialCommit is provided, return commits from that commit to HEAD |
| if (initialCommit) { |
| const startIndex = this.mockCommits.findIndex(commit => commit.hash === initialCommit); |
| if (startIndex >= 0) { |
| return this.mockCommits.slice(0, startIndex + 1); |
| } |
| } |
| |
| return [...this.mockCommits]; |
| } |
| |
| async getDiff(from: string, to: string): Promise<GitDiffFile[]> { |
| console.log(`[MockGitDataService] Getting diff from ${from} to ${to}`); |
| |
| return [...this.mockDiffFiles]; |
| } |
| |
| async getCommitDiff(commit: string): Promise<GitDiffFile[]> { |
| console.log(`[MockGitDataService] Getting diff for commit ${commit}`); |
| |
| // Return a subset of files for specific commits |
| if (commit === 'abc123456789') { |
| return this.mockDiffFiles.slice(0, 2); |
| } else if (commit === 'def987654321') { |
| return this.mockDiffFiles.slice(1, 3); |
| } |
| |
| // For other commits, return all files |
| return [...this.mockDiffFiles]; |
| } |
| |
| async getFileContent(fileHash: string): Promise<string> { |
| console.log(`[MockGitDataService] Getting file content for hash: ${fileHash}`); |
| |
| // Return different content based on the file hash |
| if (fileHash === 'bcd0123456789abcdef0123456789abcdef0123') { |
| return this.appJSModified; |
| } else if (fileHash === 'abc0123456789abcdef0123456789abcdef0123') { |
| return this.appJSOriginal; |
| } else if (fileHash === 'def0123456789abcdef0123456789abcdef0123') { |
| return this.filePickerJS; |
| } else if (fileHash === 'cde0123456789abcdef0123456789abcdef0123') { |
| return this.rangePickerJS; |
| } else if (fileHash === 'ghi0123456789abcdef0123456789abcdef0123') { |
| return this.mainCSSModified; |
| } else if (fileHash === 'fgh0123456789abcdef0123456789abcdef0123') { |
| return this.mainCSSOriginal; |
| } |
| |
| // Return empty string for unknown file hashes |
| return ''; |
| } |
| |
| async getBaseCommitRef(): Promise<string> { |
| console.log('[MockGitDataService] Getting base commit ref'); |
| |
| // Find the commit with the sketch-base ref |
| const baseCommit = this.mockCommits.find(commit => |
| commit.refs && commit.refs.includes('sketch-base') |
| ); |
| |
| if (baseCommit) { |
| return baseCommit.hash; |
| } |
| |
| // Fallback to the last commit in our list |
| return this.mockCommits[this.mockCommits.length - 1].hash; |
| } |
| |
| // Helper to simulate network delay |
| private delay(ms: number): Promise<void> { |
| return new Promise(resolve => setTimeout(resolve, ms)); |
| } |
| |
| async getWorkingCopyContent(filePath: string): Promise<string> { |
| console.log(`[MockGitDataService] Getting working copy content for path: ${filePath}`); |
| |
| // Return different content based on the file path |
| if (filePath === 'src/components/App.js') { |
| return this.appJSModified; |
| } else if (filePath === 'src/components/FilePicker.js') { |
| return this.filePickerJS; |
| } else if (filePath === 'src/components/RangePicker.js') { |
| return this.rangePickerJS; |
| } else if (filePath === 'src/styles/main.css') { |
| return this.mainCSSModified; |
| } |
| |
| // Return empty string for unknown file paths |
| return ''; |
| } |
| |
| async getUnstagedChanges(from: string = 'HEAD'): Promise<GitDiffFile[]> { |
| console.log(`[MockGitDataService] Getting unstaged changes from ${from}`); |
| |
| // Create a new array of files with 0000000... as the new hashes |
| // to simulate unstaged changes |
| return this.mockDiffFiles.map(file => ({ |
| ...file, |
| newHash: '0000000000000000000000000000000000000000' |
| })); |
| } |
| |
| async saveFileContent(filePath: string, content: string): Promise<void> { |
| console.log(`[MockGitDataService] Saving file content for path: ${filePath}`); |
| // Simulate a network delay |
| await this.delay(500); |
| // In a mock implementation, we just log the save attempt |
| console.log(`File would be saved: ${filePath} (${content.length} characters)`); |
| // Return void as per interface |
| } |
| |
| } |