Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
38a6fad
chore: add test-suite plan
damlayildiz Mar 6, 2026
b9816db
chore: add @testing-library/react-native for hook tests
damlayildiz Mar 6, 2026
4091751
test: add shared mocks for native modules
damlayildiz Mar 6, 2026
7f57f07
test: add pure function and createDatabase tests (46 tests)
damlayildiz Mar 6, 2026
0f8638b
test: add core logic tests (67 tests)
damlayildiz Mar 6, 2026
cfe6d90
test: add pushNotificationSyncTask, optionalDependencies, and context…
damlayildiz Mar 6, 2026
99eaf44
test: add hook tests for useSqliteExecute, useSqliteTransaction, useO…
damlayildiz Mar 6, 2026
524f7d7
test: add complex hook tests for sync manager, lifecycle, polling, pu…
damlayildiz Mar 6, 2026
f78808d
test: add useDatabaseInitialization and SQLiteSyncProvider integratio…
damlayildiz Mar 6, 2026
6e7cec3
test: add coverage for permission fallback, auth, and config in SQLit…
damlayildiz Mar 6, 2026
adcd0a3
chore: update gitignore
damlayildiz Mar 6, 2026
387c95d
fix: skip push token registration when siteId is unavailable
damlayildiz Mar 6, 2026
29ec55b
test: add Tier 1 coverage gap tests (262 total, 82.96% branches)
damlayildiz Mar 9, 2026
34f43b5
test: add Tier 2 coverage gap tests (272 total, 85.47% branches)
damlayildiz Mar 9, 2026
7c87fdb
test: suppress console.warn leak in backgroundSyncRegistry test
damlayildiz Mar 9, 2026
38491da
chore: update .claude/plans/test-suite-design.md
damlayildiz Mar 9, 2026
39f2073
fix: correct push token registration auth header and base URL
damlayildiz Mar 9, 2026
09e341f
fix: resolve TypeScript errors across test files
damlayildiz Mar 9, 2026
6847368
fix: imports
damlayildiz Mar 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
389 changes: 389 additions & 0 deletions .claude/plans/test-suite-design.md

Large diffs are not rendered by default.

2,499 changes: 2,499 additions & 0 deletions .claude/plans/test-suite-implementation.md

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,8 @@ ios/
# React Native Nitro Modules
nitrogen/

# Jest coverage
coverage/

# Example app environment files
examples/*/.env
105 changes: 105 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,26 @@ Every exported function, interface, and type gets JSDoc documentation:
export function myFunction(foo: string): number { ... }
```

### JSDoc for types and interfaces
All interfaces and types get a JSDoc comment, and every field gets an inline `/** comment */`:
```typescript
/** Props for {@link MyComponent} */
interface MyComponentProps {
/** Whether the dialog is visible */
open: boolean;
/** Called to close the dialog */
onClose: () => void;
}

/** Single navigation link in the sidebar */
type NavItem = {
/** Display text */
label: string;
/** Route path */
href: string;
};
```

### Section markers inside functions
Use `/** SECTION NAME */` to mark logical sections within complex functions:
```typescript
Expand Down Expand Up @@ -106,3 +126,88 @@ if (Platform.OS === 'android') { ... }
- Obvious code (`// increment counter` before `count++`)
- Code that's already clear from good naming
- Every single line - only where it adds value

## 6. Learn From Mistakes

**When corrected, codify the lesson. Don't repeat the same mistake twice.**

When the user corrects you or you discover a pattern/convention mid-session:
1. Acknowledge the mistake explicitly.
2. Propose a new rule or update to an existing section of this file that would have prevented it.
3. After user approval, add/update the rule in CLAUDE.md so future sessions benefit.

Examples of things worth capturing:
- File/folder conventions the user enforces (e.g. "utils go under `src/lib/utils/`")
- Naming patterns (e.g. "one function per file, kebab-case filename")
- Architectural preferences revealed through feedback
- Anti-patterns the user flags

Do NOT add rules speculatively. Only add rules that come from actual corrections or explicit user preferences expressed during a session.

## 7. Workflow Orchestration

### Plan Mode Default

- Enter plan mode for ANY non-trivial task (3+ steps or architectural decisions)
- If something goes sideways, STOP and re-plan immediately — don't keep pushing
- Use plan mode for verification steps, not just building
- Write detailed specs upfront to reduce ambiguity

### Subagent Strategy

- Use subagents liberally to keep main context window clean
- Offload research, exploration, and parallel analysis to subagents
- For complex problems, throw more compute at it via subagents
- One task per subagent for focused execution

### Self-Improvement Loop

- After ANY correction from the user: update relevant files in `.claude/rules/` with the pattern
- Write rules for yourself that prevent the same mistake
- Ruthlessly iterate on these lessons until mistake rate drops
- Review lessons at session start for relevant project

### Verification Before Done

- Never mark a task complete without proving it works
- Diff behavior between main and your changes when relevant
- Ask yourself: "Would a staff engineer approve this?"
- Run tests, check logs, demonstrate correctness

### Demand Elegance (Balanced)

- For non-trivial changes: pause and ask "is there a more elegant way?"
- If a fix feels hacky: "Knowing everything I know now, implement the elegant solution"
- Skip this for simple, obvious fixes — don't over-engineer
- Challenge your own work before presenting it

### Autonomous Bug Fixing

- When given a bug report: just fix it. Don't ask for hand-holding
- Point at logs, errors, failing tests — then resolve them
- Zero context switching required from the user
- Go fix failing CI tests without being told how

## 8. Plan Files

**Store implementation plans in `.claude/plans/<feature-name>.md`**

- Plans live in `.claude/plans/` so Claude can reference them across sessions
- Use descriptive kebab-case names: `test-suite-design.md`, `push-notification-refactor.md`
- Include date and approval status at the top
- These are working documents — update them as the plan evolves

## 9. Task Management

1. **Plan First**: Write plan to `.claude/todo/<feature-or-task-name>.md` with checkable items
2. **Verify Plan**: Check in before starting implementation
3. **Track Progress**: Mark items complete as you go
4. **Explain Changes**: High-level summary at each step
5. **Document Results**: Add review section to `.claude/todo/<feature-or-task-name>.md`
6. **Capture Lessons**: Update relevant files in `.claude/rules/` after corrections

## 10. Core Principles

- **Simplicity First**: Make every change as simple as possible. Impact minimal code.
- **No Laziness**: Find root causes. No temporary fixes. Senior developer standards.
- **Minimal Impact**: Changes should only touch what's necessary. Avoid introducing bugs.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,10 @@
"@react-native/babel-preset": "0.81.1",
"@react-native/eslint-config": "^0.83.0",
"@release-it/conventional-changelog": "^10.0.1",
"@testing-library/react-native": "^13.3.3",
"@types/jest": "^29.5.14",
"@types/react": "^19.1.12",
"@types/react-test-renderer": "^19",
"del-cli": "^6.0.0",
"eslint": "^9.35.0",
"eslint-config-prettier": "^10.1.8",
Expand All @@ -101,6 +103,7 @@
"react": "19.1.0",
"react-native": "0.81.5",
"react-native-builder-bob": "^0.40.16",
"react-test-renderer": "19.1.0",
"release-it": "^19.0.4",
"typescript": "^5.9.2"
},
Expand Down
29 changes: 29 additions & 0 deletions src/__mocks__/@op-engineering/op-sqlite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const createMockTx = () => ({
execute: jest.fn().mockResolvedValue({ rows: [] }),
});

export const createMockDB = () => ({
execute: jest.fn().mockResolvedValue({ rows: [] }),
transaction: jest.fn(async (fn: any) => {
const tx = createMockTx();
await fn(tx);
return tx;
}),
close: jest.fn(),
loadExtension: jest.fn(),
updateHook: jest.fn(),
reactiveExecute: jest.fn(() => jest.fn()),
});

export const open = jest.fn(() => createMockDB());
export const getDylibPath = jest.fn(
(_bundleId: string, _name: string) => '/mock/path/CloudSync'
);

export type DB = ReturnType<typeof createMockDB>;
export type QueryResult = {
rows?: Record<string, any>[];
insertId?: number;
rowsAffected?: number;
};
export type Transaction = ReturnType<typeof createMockTx>;
24 changes: 24 additions & 0 deletions src/__mocks__/@react-native-community/netinfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
type NetInfoCallback = (state: any) => void;
const listeners: NetInfoCallback[] = [];

const NetInfo = {
addEventListener: jest.fn((callback: NetInfoCallback) => {
listeners.push(callback);
return jest.fn(() => {
const idx = listeners.indexOf(callback);
if (idx >= 0) listeners.splice(idx, 1);
});
}),
fetch: jest.fn().mockResolvedValue({
isConnected: true,
isInternetReachable: true,
}),
__simulateChange: (state: any) => {
listeners.forEach((cb) => cb(state));
},
__clearListeners: () => {
listeners.length = 0;
},
};

export default NetInfo;
2 changes: 2 additions & 0 deletions src/__mocks__/expo-application.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const getIosIdForVendorAsync = jest.fn().mockResolvedValue('mock-ios-vendor-id');
export const getAndroidId = jest.fn(() => 'mock-android-id');
4 changes: 4 additions & 0 deletions src/__mocks__/expo-constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default {
expoConfig: { extra: { eas: { projectId: 'mock-project-id' } } },
easConfig: { projectId: 'mock-project-id' },
};
7 changes: 7 additions & 0 deletions src/__mocks__/expo-notifications.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const getPermissionsAsync = jest.fn().mockResolvedValue({ status: 'granted' });
export const requestPermissionsAsync = jest.fn().mockResolvedValue({ status: 'granted' });
export const getExpoPushTokenAsync = jest.fn().mockResolvedValue({ data: 'ExponentPushToken[mock]' });
export const getDevicePushTokenAsync = jest.fn().mockResolvedValue({ data: 'mock-device-token' });
export const addNotificationReceivedListener = jest.fn(() => ({ remove: jest.fn() }));
export const registerTaskAsync = jest.fn().mockResolvedValue(undefined);
export const unregisterTaskAsync = jest.fn().mockResolvedValue(undefined);
5 changes: 5 additions & 0 deletions src/__mocks__/expo-secure-store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const store: Record<string, string> = {};
export const getItemAsync = jest.fn(async (key: string) => store[key] ?? null);
export const setItemAsync = jest.fn(async (key: string, value: string) => { store[key] = value; });
export const deleteItemAsync = jest.fn(async (key: string) => { delete store[key]; });
export const __clearStore = () => { Object.keys(store).forEach((k) => delete store[k]); };
1 change: 1 addition & 0 deletions src/__mocks__/expo-task-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const defineTask = jest.fn();
Loading