mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 04:01:09 +02:00
- Add uload project with apps/web structure
- Reorganize from flat to monorepo structure
- Remove PocketBase binary and local data
- Update to pnpm and @uload/web namespace
- Add picture project to monorepo
- Remove embedded git repository
- Unify all package names to @{project}/{app} schema:
- @maerchenzauber/* (was @storyteller/*)
- @manacore/* (was manacore-*, manacore)
- @manadeck/* (was web, backend, manadeck)
- @memoro/* (was memoro-web, landing, memoro)
- @picture/* (already unified)
- @uload/web
- Add convenient dev scripts for all apps:
- pnpm dev:{project}:web
- pnpm dev:{project}:landing
- pnpm dev:{project}:mobile
- pnpm dev:{project}:backend
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
203 lines
No EOL
6.2 KiB
TypeScript
203 lines
No EOL
6.2 KiB
TypeScript
import { describe, test, expect, beforeEach, vi } from 'vitest';
|
|
import { isTouchDevice, isOptimalTouchTarget } from './touch';
|
|
|
|
// Mock DOM APIs für Tests
|
|
const mockEventListener = vi.fn();
|
|
const mockRemoveEventListener = vi.fn();
|
|
|
|
const createMockElement = (width = 44, height = 44) => ({
|
|
addEventListener: mockEventListener,
|
|
removeEventListener: mockRemoveEventListener,
|
|
getBoundingClientRect: () => ({ width, height, top: 0, left: 0, right: width, bottom: height }),
|
|
style: {},
|
|
appendChild: vi.fn(),
|
|
remove: vi.fn()
|
|
});
|
|
|
|
// Mock global objects
|
|
Object.defineProperty(window, 'navigator', {
|
|
value: {
|
|
maxTouchPoints: 0,
|
|
userAgent: 'Mozilla/5.0 (Test Browser)'
|
|
},
|
|
writable: true
|
|
});
|
|
|
|
describe('Touch Utilities', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
// Reset touch support
|
|
delete (window as any).ontouchstart;
|
|
(window.navigator as any).maxTouchPoints = 0;
|
|
});
|
|
|
|
describe('isTouchDevice', () => {
|
|
test('should detect touch support via ontouchstart', () => {
|
|
(window as any).ontouchstart = true;
|
|
expect(isTouchDevice()).toBe(true);
|
|
});
|
|
|
|
test('should detect touch support via maxTouchPoints', () => {
|
|
(window.navigator as any).maxTouchPoints = 1;
|
|
expect(isTouchDevice()).toBe(true);
|
|
});
|
|
|
|
test('should return false for non-touch devices', () => {
|
|
expect(isTouchDevice()).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('isOptimalTouchTarget', () => {
|
|
test('should return true for 44x44 elements', () => {
|
|
const element = createMockElement(44, 44);
|
|
expect(isOptimalTouchTarget(element as any)).toBe(true);
|
|
});
|
|
|
|
test('should return true for larger elements', () => {
|
|
const element = createMockElement(50, 60);
|
|
expect(isOptimalTouchTarget(element as any)).toBe(true);
|
|
});
|
|
|
|
test('should return false for small width', () => {
|
|
const element = createMockElement(30, 44);
|
|
expect(isOptimalTouchTarget(element as any)).toBe(false);
|
|
});
|
|
|
|
test('should return false for small height', () => {
|
|
const element = createMockElement(44, 30);
|
|
expect(isOptimalTouchTarget(element as any)).toBe(false);
|
|
});
|
|
|
|
test('should return false for small elements', () => {
|
|
const element = createMockElement(20, 20);
|
|
expect(isOptimalTouchTarget(element as any)).toBe(false);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Touch Actions (Integration)', () => {
|
|
let mockElement: any;
|
|
|
|
beforeEach(() => {
|
|
mockElement = createMockElement();
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe('Event Registration', () => {
|
|
test('should register touch and pointer events', () => {
|
|
// Diese Tests würden die tatsächlichen Touch-Actions testen
|
|
// Für jetzt testen wir nur die Utility-Funktionen
|
|
expect(mockEventListener).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('Gesture Recognition', () => {
|
|
test('should calculate touch distances correctly', () => {
|
|
const touch1 = { clientX: 0, clientY: 0 };
|
|
const touch2 = { clientX: 100, clientY: 100 };
|
|
|
|
// Math.sqrt(100^2 + 100^2) = Math.sqrt(20000) ≈ 141.42
|
|
const expectedDistance = Math.sqrt(20000);
|
|
const actualDistance = Math.sqrt(
|
|
Math.pow(touch2.clientX - touch1.clientX, 2) +
|
|
Math.pow(touch2.clientY - touch1.clientY, 2)
|
|
);
|
|
|
|
expect(actualDistance).toBeCloseTo(expectedDistance, 2);
|
|
});
|
|
|
|
test('should detect horizontal swipes', () => {
|
|
const startTouch = { clientX: 0, clientY: 100 };
|
|
const endTouch = { clientX: 100, clientY: 100 };
|
|
|
|
const deltaX = endTouch.clientX - startTouch.clientX;
|
|
const deltaY = endTouch.clientY - startTouch.clientY;
|
|
const absDeltaX = Math.abs(deltaX);
|
|
const absDeltaY = Math.abs(deltaY);
|
|
|
|
// Horizontal swipe: |deltaX| > |deltaY|
|
|
expect(absDeltaX).toBeGreaterThan(absDeltaY);
|
|
expect(deltaX).toBeGreaterThan(0); // Right swipe
|
|
});
|
|
|
|
test('should detect vertical swipes', () => {
|
|
const startTouch = { clientX: 100, clientY: 0 };
|
|
const endTouch = { clientX: 100, clientY: 100 };
|
|
|
|
const deltaX = endTouch.clientX - startTouch.clientX;
|
|
const deltaY = endTouch.clientY - startTouch.clientY;
|
|
const absDeltaX = Math.abs(deltaX);
|
|
const absDeltaY = Math.abs(deltaY);
|
|
|
|
// Vertical swipe: |deltaY| > |deltaX|
|
|
expect(absDeltaY).toBeGreaterThan(absDeltaX);
|
|
expect(deltaY).toBeGreaterThan(0); // Down swipe
|
|
});
|
|
});
|
|
|
|
describe('Touch Target Validation', () => {
|
|
test('should validate minimum touch target sizes', () => {
|
|
const sizes = [
|
|
{ width: 44, height: 44, expected: true },
|
|
{ width: 48, height: 48, expected: true },
|
|
{ width: 40, height: 40, expected: false },
|
|
{ width: 44, height: 40, expected: false },
|
|
{ width: 40, height: 44, expected: false }
|
|
];
|
|
|
|
sizes.forEach(({ width, height, expected }) => {
|
|
const element = createMockElement(width, height);
|
|
expect(isOptimalTouchTarget(element as any)).toBe(expected);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Performance Considerations', () => {
|
|
test('should handle rapid touch events', () => {
|
|
// Simuliere viele schnelle Touch-Events
|
|
const events = Array.from({ length: 100 }, (_, i) => ({
|
|
clientX: i,
|
|
clientY: i,
|
|
timestamp: Date.now() + i
|
|
}));
|
|
|
|
// In einer echten Implementation würden wir Throttling/Debouncing testen
|
|
expect(events).toHaveLength(100);
|
|
|
|
// Teste dass Events innerhalb vernünftiger Zeit verarbeitet werden können
|
|
const startTime = Date.now();
|
|
events.forEach(event => {
|
|
// Simuliere Event-Verarbeitung
|
|
const deltaX = event.clientX;
|
|
const deltaY = event.clientY;
|
|
Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
});
|
|
const endTime = Date.now();
|
|
|
|
expect(endTime - startTime).toBeLessThan(100); // Sollte sehr schnell sein
|
|
});
|
|
});
|
|
|
|
describe('Accessibility Considerations', () => {
|
|
test('should maintain focus accessibility', () => {
|
|
// Touch-Actions sollten Keyboard-Navigation nicht beeinträchtigen
|
|
const element = createMockElement();
|
|
|
|
// Simuliere dass Element fokussierbar bleibt
|
|
element.tabIndex = 0;
|
|
element.setAttribute = vi.fn();
|
|
|
|
expect(element.tabIndex).toBe(0);
|
|
});
|
|
|
|
test('should work with screen readers', () => {
|
|
// Touch-Targets sollten Screen-Reader-kompatibel bleiben
|
|
const element = createMockElement();
|
|
element.getAttribute = vi.fn().mockReturnValue('button');
|
|
element.textContent = 'Touch Button';
|
|
|
|
expect(element.getAttribute('role')).toBe('button');
|
|
expect(element.textContent).toBe('Touch Button');
|
|
});
|
|
});
|
|
}); |