feat(engagement): localize limits and streaks

Use reported user timezones for daily response limits, expose couple streak state and reminders, and improve swipe and live-drawing synchronization with focused tests and documentation.
This commit is contained in:
jjbrunton committed 2026-08-28 20:28:31 +01:00
1 parent 61ec3692b7
commit b60c788362
42 files changed
+1308 -217

No files matched your search

@@ -1,5 +1,5 @@
import { act, renderHook, waitFor } from '@testing-library/react-native';
import { useDrawingSync } from '@/features/live-draw/hooks/useDrawingSync';
import { useDrawingSync, dedupeById, mergeDrawingChanges } from '@/features/live-draw/hooks/useDrawingSync';
import { appDataApi } from '@/lib/appDataApi';
import { ApiError } from '@/lib/apiClient';
@@ -47,3 +47,37 @@ describe('useDrawingSync API polling', () => {
expect(onLoadStrokes).toHaveBeenLastCalledWith([stroke, partnerStroke, mine]);
});
});
describe('useDrawingSync stroke deduplication', () => {
const at = (id: string, points: number, userId = 'u1') => ({
id, userId, points: Array.from({ length: points }, (_, i) => ({ x: i, y: i })),
color: '#fff', width: 2, timestamp: 1, isEraser: false,
});
it('keeps the most complete copy of a stroke at its original z-position', () => {
// Mid-draw writes persist a truncated copy; touch-end appends the full one.
const out = dedupeById([at('a', 3), at('mine', 5), at('b', 3), at('mine', 40)]);
expect(out.map(s => s.id)).toEqual(['a', 'mine', 'b']);
expect(out[1].points).toHaveLength(40);
});
it('leaves a set of unique strokes untouched', () => {
const input = [at('a', 1), at('b', 1)];
expect(dedupeById(input)).toEqual(input);
});
it('never emits duplicate ids after a conflict merge', () => {
const merged = mergeDrawingChanges(
[at('a', 2)],
[at('a', 2), at('mine', 3), at('mine', 30)],
[at('a', 2), at('partner', 4)],
);
expect(merged.map(s => s.id)).toEqual(['a', 'partner', 'mine']);
expect(merged.find(s => s.id === 'mine')!.points).toHaveLength(30);
});
it('still drops strokes removed locally', () => {
const merged = mergeDrawingChanges([at('a', 2), at('gone', 2)], [at('a', 2)], [at('a', 2), at('gone', 2)]);
expect(merged.map(s => s.id)).toEqual(['a']);
});
});