You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							246 lines
						
					
					
						
							5.8 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							246 lines
						
					
					
						
							5.8 KiB
						
					
					
				| # Time Safari Architecture — Examples and Testing | |
| 
 | |
| > **Agent role**: Reference this file for architectural examples and | |
|   testing patterns when working with TimeSafari architecture. | |
| 
 | |
| ## Error Handling Patterns | |
| 
 | |
| ### Global Error Handler | |
| 
 | |
| ```typescript | |
| 
 | |
| // main.ts | |
| app.config.errorHandler = (err, instance, info) => { | |
|   const componentName = instance?.$options?.name || 'Unknown'; | |
|   logger.error(`[${componentName}] Vue error`, err, info); | |
| }; | |
| 
 | |
| window.addEventListener('unhandledrejection', (event) => { | |
|   logger.error('[Global] Unhandled promise rejection', event.reason); | |
| }); | |
| 
 | |
| ``` | |
| 
 | |
| ### Platform-Specific Error Wrapping | |
| 
 | |
| ```typescript | |
| 
 | |
| // services/platforms/CapacitorPlatformService.ts | |
| export class CapacitorPlatformService { | |
|   async getFileContents(path: string): Promise<string> { | |
|     try { | |
|       const result = await Filesystem.readFile({ | |
|         path: path, | |
|         encoding: 'utf8' | |
|       }); | |
|       return result.data; | |
|     } catch (error) { | |
|       logger.error('[Capacitor API Error] Failed to read file', error, path); | |
|       throw new Error(`Failed to read file: ${path}`); | |
|     } | |
|   } | |
| } | |
| 
 | |
| ``` | |
| 
 | |
| ## Testing Patterns | |
| 
 | |
| ### Platform-Specific Test Skipping | |
| 
 | |
| ```typescript | |
| 
 | |
| // tests/QRScanner.test.ts | |
| describe('QRScanner Service', () => { | |
|   test('should start scanning on web', async () => { | |
|     test.skip(process.env.VITE_PLATFORM !== 'web', 'Web-only test'); | |
| 
 | |
|     const scanner = new WebInlineQRScanner(); | |
|     await scanner.startScanning(); | |
|     // Assert scanning started | |
|   }); | |
| 
 | |
|   test('should start scanning on mobile', async () => { | |
|     test.skip(process.env.VITE_PLATFORM !== 'capacitor', 'Mobile-only test'); | |
| 
 | |
|     const scanner = new CapacitorQRScanner(); | |
|     await scanner.startScanning(); | |
|     // Assert scanning started | |
|   }); | |
| }); | |
| 
 | |
| ``` | |
| 
 | |
| ### Mock Service Testing | |
| 
 | |
| ```typescript | |
| 
 | |
| // tests/mocks/QRScannerMock.ts | |
| export class QRScannerMock implements QRScannerService { | |
|   private isScanning = false; | |
|   private listeners: Map<string, Function[]> = new Map(); | |
| 
 | |
|   async startScanning(): Promise<void> { | |
|     this.isScanning = true; | |
|     this.emit('scanningStarted'); | |
|   } | |
| 
 | |
|   async stopScanning(): Promise<void> { | |
|     this.isScanning = false; | |
|     this.emit('scanningStopped'); | |
|   } | |
| 
 | |
|   addListener(event: string, callback: Function): void { | |
|     if (!this.listeners.has(event)) { | |
|       this.listeners.set(event, []); | |
|     } | |
|     this.listeners.get(event)!.push(callback); | |
|   } | |
| 
 | |
|   removeListener(event: string, callback: Function): void { | |
|     const callbacks = this.listeners.get(event); | |
|     if (callbacks) { | |
|       const index = callbacks.indexOf(callback); | |
|       if (index > -1) { | |
|         callbacks.splice(index, 1); | |
|       } | |
|     } | |
|   } | |
| 
 | |
|   private emit(event: string, ...args: any[]): void { | |
|     const callbacks = this.listeners.get(event); | |
|     if (callbacks) { | |
|       callbacks.forEach(callback => callback(...args)); | |
|     } | |
|   } | |
| 
 | |
|   getScanningState(): boolean { | |
|     return this.isScanning; | |
|   } | |
| } | |
| 
 | |
| ``` | |
| 
 | |
| ## Integration Examples | |
| 
 | |
| ### Service Composition | |
| 
 | |
| ```typescript | |
| 
 | |
| // services/QRScannerService.ts | |
| export class QRScannerService { | |
|   constructor( | |
|     private platformService: PlatformService, | |
|     private notificationService: NotificationService | |
|   ) {} | |
| 
 | |
|   async startScanning(): Promise<void> { | |
|     try { | |
|       await this.platformService.startCamera(); | |
|       this.notificationService.show('Camera started'); | |
|     } catch (error) { | |
|       this.notificationService.showError('Failed to start camera'); | |
|       throw error; | |
|     } | |
|   } | |
| } | |
| 
 | |
| ``` | |
| 
 | |
| ### Component Integration | |
| 
 | |
| ```typescript | |
| 
 | |
| // components/QRScannerDialog.vue | |
| export default class QRScannerDialog extends Vue { | |
|   @Inject() private qrScannerService!: QRScannerService; | |
| 
 | |
|   async mounted() { | |
|     try { | |
|       await this.qrScannerService.startScanning(); | |
|     } catch (error) { | |
|       this.$notify.error('Failed to start scanner'); | |
|     } | |
|   } | |
| 
 | |
|   beforeDestroy() { | |
|     this.qrScannerService.stopScanning(); | |
|   } | |
| } | |
| 
 | |
| ``` | |
| 
 | |
| ## Best Practices | |
| 
 | |
| ### Service Design | |
| 
 | |
| - Keep services focused and single-purpose | |
| 
 | |
| - Use dependency injection for service composition | |
| 
 | |
| - Implement proper error handling and logging | |
| 
 | |
| - Provide clear interfaces and contracts | |
| 
 | |
| ### Testing Strategy | |
| 
 | |
| - Test platform-specific behavior separately | |
| 
 | |
| - Use mocks for external dependencies | |
| 
 | |
| - Test error conditions and edge cases | |
| 
 | |
| - Validate service contracts and interfaces | |
| 
 | |
| ### Error Handling | |
| 
 | |
| - Log errors with appropriate context | |
| 
 | |
| - Provide user-friendly error messages | |
| 
 | |
| - Implement graceful degradation | |
| 
 | |
| - Handle platform-specific error scenarios | |
| 
 | |
| --- | |
| 
 | |
| **See also**: | |
| 
 | |
| - `.cursor/rules/app/architectural_decision_record.mdc` for | |
| 
 | |
|   core architecture principles | |
| 
 | |
| - `.cursor/rules/app/architectural_implementation.mdc` for | |
| 
 | |
|   implementation details | |
| 
 | |
| - `.cursor/rules/app/architectural_patterns.mdc` for core patterns | |
| 
 | |
| **Status**: Active examples and testing guide | |
| **Priority**: Medium | |
| **Estimated Effort**: Ongoing reference | |
| **Dependencies**: architectural_patterns.mdc | |
| **Stakeholders**: Development team, Testing team | |
| 
 | |
| ## Model Implementation Checklist | |
| 
 | |
| ### Before Architectural Examples | |
| 
 | |
| - [ ] **Pattern Selection**: Choose appropriate architectural pattern for the use | |
|   case | |
| - [ ] **Service Design**: Plan service structure and dependencies | |
| - [ ] **Testing Strategy**: Plan testing approach for the example | |
| - [ ] **Error Handling**: Plan error handling and logging strategy | |
| 
 | |
| ### During Architectural Examples | |
| 
 | |
| - [ ] **Service Implementation**: Implement focused, single-purpose services | |
| - [ ] **Dependency Injection**: Use proper dependency injection patterns | |
| - [ ] **Error Handling**: Implement proper error handling and logging | |
| - [ ] **Interface Design**: Provide clear interfaces and contracts | |
| 
 | |
| ### After Architectural Examples | |
| 
 | |
| - [ ] **Testing Execution**: Test platform-specific behavior separately | |
| - [ ] **Service Validation**: Validate service contracts and interfaces | |
| - [ ] **Error Testing**: Test error conditions and edge cases | |
| - [ ] **Documentation**: Update architectural examples documentation
 | |
| 
 |