# Test Coverage All tests are in `tests/` and run with `npm run test` (Vitest). --- ## utils/path — `tests/utils/path.test.ts` ### `isBinaryPath()` | Group | Cases | |---|---| | Image formats | png, jpg, gif, bmp, webp (legacy); heic, heif, avif, tiff, tif, jxl (modern) | | Audio formats | mp3, wav, ogg, flac, aac, m4a, opus, aiff | | Video formats | mp4, webm, mov, mkv, avi, wmv, flv, m4v | | Archive formats | zip, tar, gz, bz2, xz, zst, 7z, rar | | Font formats | ttf, otf, woff, woff2 | | Database formats | sqlite, db | | Text files | md, json, ts, yml, txt → returns false | | Hidden file paths | `.gitignore`, `.env` → false; `.claude/settings.json` → false; `.claude/photo.png` → true | | Case insensitivity | `.PNG`, `.JPG`, `.FLAC` | | BINARY_EXTENSIONS completeness | Set membership checks for major categories | ### `contentsEqual()` | Group | Cases | |---|---| | String | identical, different, empty, case-sensitive | | ArrayBuffer | identical, different bytes, different lengths, empty buffers | | Mixed types | string vs ArrayBuffer → false | --- ## logic/SyncManager — `tests/logic/sync-manager.test.ts` | Case | |---| | Push file content correctly | | Detect conflict when remote SHA differs from last synced SHA | | Resolve conflict by choosing local | | Resolve conflict by choosing remote | | Update metadata when file is already in sync (contentsEqual) | | Update metadata after successful push | | Pull and modify file content, update metadata | | Handle file not existing in vault | | Add new file to repo when exists locally but not on remote | | **Renames:** detect and handle file rename | | **Error handling:** push errors, rename errors | | **pullFile:** file not in remote, pull new file, pull errors | --- ## logic/SyncManager Batch — `tests/logic/sync-manager-batch.test.ts` | Case | |---| | Push multiple files (TFile and string paths) | | Handle failures during batch push | | Pull multiple files (TFile and string paths) | | Handle missing remote files during batch pull | | onProgress callback called for each file | | Detect and handle rename during batch push | --- ## logic/SyncManager Path Mapping — `tests/logic/sync-manager-mapping.test.ts` | Case | |---| | Strip vaultFolder prefix when pushing | | Re-add vaultFolder prefix when pulling | | Handle root-level files with no vaultFolder | --- ## logic/SyncManager Binary Files — `tests/logic/sync-manager-binary.test.ts` ### `pushFile` with binary path (string) | Case | |---| | Reads via `adapter.readBinary` for binary extensions | | Skips push when binary content is already in sync | | Updates metadata when binary is already in sync | ### `pullFile` with binary content | Case | |---| | Writes via `adapter.writeBinary` when remote content is ArrayBuffer | | Creates parent directory before writing binary | | Skips pull when binary content is already in sync | | Updates metadata after pulling binary file | --- ## logic/SyncManager Hidden Files — `tests/logic/sync-manager-hidden.test.ts` ### `pullFile` with hidden paths | Case | |---| | Creates single hidden parent directory (`.claude/`) on pull | | Creates all nested hidden parent directories (`.claude/memory/`) on pull | | Does not throw if hidden directory already exists (mkdir error is swallowed) | | Updates syncMetadata after pulling hidden file | ### `pushFile` with hidden paths | Case | |---| | Pushes hidden file content via string path | | Skips push when hidden file is already in sync (same content) | | Does not push when hidden file is missing from vault | --- ## logic/GitignoreManager — `tests/logic/gitignore-manager.test.ts` ### `loadGitignores()` | Case | |---| | Load root .gitignore from local if it exists | | Load gitignores from remote as fallback | | Fall back to `[".gitignore"]` when getRepoGitignores throws | | Fall back to remote when local .gitignore read throws | | Handle subdirectory .gitignores correctly | | Pick up local-only subdirectory .gitignore not yet on remote | ### `isIgnored()` with rootPath | Case | |---| | Correctly resolve paths relative to git root | ### Complex patterns | Case | |---| | Negative patterns (`!important.log`) | | Directory-only patterns (`build/`) | | Deep wildcards (`**/temp/*`) | ### Hidden file and directory filtering | Case | |---| | Ignore hidden directory when listed in .gitignore (`.obsidian/`, `.trash/`) | | Pass through hidden directory absent from .gitignore (`.claude/`) | | Ignore `.claude/` when explicitly added to .gitignore | | Pass normal files when only hidden dirs are ignored | | Find `.gitignore` inside a hidden directory via scanDir | --- ## services/GitServiceBase — `tests/services/git-service-base.test.ts` | Case | |---| | No double-slash when rootPath already ends with `/` | | Wrap non-Error throws in a new Error | | Correctly encode and decode UTF-8 content | --- ## services/GitHubService — `tests/services/github-service.test.ts` | Group | Cases | |---|---| | getFile | fetch and decode content, 404 handling, bypass rootPath with `/` prefix, no double-prefix, return sha | | pushFile | new file (no sha), update existing file (with sha) | | listFiles | list blob files, filter by rootPath, no sibling path collision, truncated result warning | | deleteFile | delete using file sha | --- ## services/GitLabService — `tests/services/gitlab-service.test.ts` | Group | Cases | |---|---| | getFile | fetch and decode content, 404 handling, return last_commit_id as sha | | pushFile | POST for new file, PUT for existing file | | listFiles | list blob files, filter by rootPath, no sibling path collision, pagination boundary (100 items), multi-page (200 items), stop on partial page | | deleteFile | delete with commit message | --- ## ui/ActionBar — `tests/ui/ActionBar.test.ts` | Group | Cases | |---|---| | No files | always renders refresh button; no push/pull/delete; no select-all | | Has files | renders push, pull, delete, select-all; disabled states when count is 0; enabled when count > 0 | | Interactions | onRefresh, onPush, onPull, onDelete, onSelectAll(true/false) | --- ## ui/DiffPanel — `tests/ui/DiffPanel.test.ts` | Case | |---| | Returns element with `ssv-diff` class | | Renders Remote and Local column headers | | Renders unchanged lines | | Renders added line | | Renders removed line | | Renders replacement (removed + added) | | Renders unchanged lines for identical content | --- ## ui/FileListItem — `tests/ui/FileListItem.test.ts` | Group | Cases | |---|---| | Status CSS | distinct class for each status | | Rendering | file path, status badge, checkbox (selected/unselected) | | Interactions | onSelect(path, true/false) | | Synced status | no action buttons | | Remote-only status | no action buttons | | Modified status | push + pull buttons; onPush/onPull callbacks; diff toggle; diff panel visibility toggle; diff button label toggle | | Local-only status | push + delete buttons; no pull; onDelete callback | | Missing status | pull button; no push | --- ## utils/diff — `tests/utils/diff.test.ts` | Group | Cases | |---|---| | Identical content | single-line, multi-line, empty strings | | CRLF normalisation | CRLF = LF, CR-only | | Additions only | single added line, multiple added lines, single-line change | | Removals only | single removed line, multiple removed lines | | Mixed changes | paired removed+added, unchanged lines between changes | | Line numbers | correct numbering |