Skip to content
Package

git‑scenarios

Version v1.3.0
Output ESM + CJS
Runtime Node ≥22
Peer dep simple-git ^3
Author gfargo
composable git fixtures

Real repos. Mock objects. Any git state.

Deterministic fixtures for merge conflicts, submodules, multiple remotes, in‑progress operations, monorepos, and more — plus in‑memory mock factories for blazing‑fast unit tests. Real repos or typed mocks, one line each.

$ npm i -D @gfargo/git-scenarios simple-git
spinUpScenario(name) PUBLIC API 46 named scenarios SCENARIO REGISTRY addCommit chain startMerge cherryPick withAuthor addSubmodule 60+ COMPOSABLE ATOMS 3 2 1 3 LAYERS FIG. 01 Composition diagram — atoms compose into scenarios; the public API and CLI consume the registry. Same shape every test run.
46 scenarios
60+ composable atoms
7 test runner adapters
4 mock factories
22+ supported node
MIT license

§02 · Specification

What it is, in one drawing

Every git tool — TUI, IDE, CLI, linter, formatter, AI assistant — behaves differently against different repo states. Testing those behaviors well means starting from real repositories in carefully-constructed states. @gfargo/git-scenarios is the typed, composable kit for producing those states deterministically.

It ships in three layers, increasingly inline:

// 01. Named scenario — the one-liner
const repo = await spinUpScenario('mid-merge-conflict')
// 02. Scenario + custom atoms on top
const repo = await fromScenario('feature-pr-ready',
addCommit({ message: 'extra', files: { 'x.ts': 'x' } }),
)
// 03. Compose from primitives
const repo = await createTempGitRepo()
await chain(
addCommit({ message: 'base', files: { 'x.ts': 'base\n' } }),
switchToBranch('feat/theirs'),
addCommit({ message: 'theirs', files: { 'x.ts': 'theirs\n' } }),
switchToBranch('main'),
addCommit({ message: 'ours', files: { 'x.ts': 'ours\n' } }),
startMerge('feat/theirs'),
)(repo)
// repo is mid-merge with src/x.ts conflicted

For unit tests that don’t need a real repo on disk, the mock layer gives you the same typed state objects in under 1 ms:

// 04. Unit test mocks — no git, no disk, <1ms
import { mockStatus, mockSimpleGit } from '@gfargo/git-scenarios/mocks'
const status = mockStatus().staged('auth.ts').conflicted('merge.ts').build()
const git = mockSimpleGit({ createMockFn: jest.fn, overrides: { status } })

Every scenario produces byte-identical state every run. Tests built on top are deterministic too — no flake from clock-drift or hash variance.

FIG. 02 Two output modes: spinUpScenario for real repos on disk, mockFromScenario for typed in-memory mocks.
01 CALL SITE await spinUpScenario( 'mid-merge-conflict' ) 02 RESOLVE scenario registry findRegistered(name) [ 46 built‑in + N custom ] 03 EXECUTE scenario.setup(repo) chain( atom, atom, … ) addCommit · switchToBranch · startMerge 04 RESULT — ON DISK /var/folders/…/git-scenarios-xR2qwz ├── .git/ │ ├── HEAD │ └── MERGE_HEAD ├── README.md └── src/widget.ts ⟵ conflicted RETURNS type TempGitRepo = { path: string git: SimpleGit cleanup: () => Promise<void> } MOCK PATH M1 PARSE CONTRACTS mockFromScenario(name) parse contracts → typed objects M2 RESULT — IN MEMORY { status, log, branches } = mockFromScenario('mid-merge-conflict') status: StatusResult log: LogResult branches: BranchSummary no disk I/O · no simple-git runtime · <1ms

§03 · Failure modes

What it replaces

Hand-rolled setup

80+ lines of git initwriteFilecommit per test. Drift between tests is inevitable.

Checked-in fixtures

Real .git directories in your repo. Bloats the tree, breaks on updates, no programmatic access.

Mocking git commands

Shallow command mocks verify you called the right git CLI args — they say nothing about how your code handles the actual state. Mock the state, not the command. Mock Factories →

Docker containers

Pre-baked images with git state. Heavy, slow, CI-only. Not interactive-test-friendly.

Full comparison: why not just… →

§04 · Inventory

What’s in the kit

46 curated scenarios

Branch states, merge / rebase / cherry-pick / revert conflicts, bisect, stash, worktrees, submodules, shallow clones, monorepos, orphan branches, large histories. All deterministic. Browse them →

60+ composable atoms

Build any state from typed building blocks — chain(), addCommit, startMerge, withAuthor, insideSubmodule, more. Write your own; they’re just functions. Atom reference →

7 test-runner adapters

Jest, Vitest, node:test, Mocha, AVA, Playwright, Cypress — describeWithScenario('name', (getRepo) => { … }) with automatic cleanup. Pick your runner; the API stays the same. Setup guide →

Tool-agnostic CLI

git-scenarios create mid-merge-conflict --run "lazygit" — materialize any scenario and launch any tool against it. JSON output and tag/kind filters too. CLI docs →

Custom registration

registerScenario(myScenario) — your custom states work everywhere built-ins do. Define your own →

Mock Factories

Zero-dep in-memory mocks for StatusResult, LogResult, BranchSummary — typed, realistic, under 1 ms. Unit-test your git UI without touching disk. Mock Factories →

Dual CJS / ESM

Ships both formats. Use import or require — both work. TypeScript-first with full type declarations. Install →

FIG. 03 Four mid‑operation states — each leaves a distinct .git/ marker file. git‑scenarios produces all four deterministically.
M·01 merge
.git/MERGE_HEAD

Two branches edit the same line. Merge halts; conflict markers in worktree.

mid-merge-conflict
R·02 rebase
.git/rebase-merge/

Replay halts on a conflict. REBASE_HEAD set; resume with --continue.

mid-rebase-conflict
C·03 cherry‑pick
.git/CHERRY_PICK_HEAD

Pick from another branch conflicts. Worktree shows both sides.

mid-cherry-pick-conflict
V·04 revert
.git/REVERT_HEAD

Reverting a commit conflicts with later changes. Distinct UI state.

mid-revert-conflict

§05 · CLI

A small tour

git-scenarios inspect: a clean PR-ready feature branch, then an in-progress merge conflict — a real repo in any state from one command

git-scenarios inspect — see any scenario’s commit graph, branches, and status without keeping a thing on disk.

Terminal window
# Spin up a merge conflict, launch lazygit against it
npx git-scenarios create mid-merge-conflict --run "lazygit"
# Filter the catalogue
npx git-scenarios list --kind operation
npx git-scenarios list --tag conflict --json
# Describe a specific scenario's contracts
npx git-scenarios describe feature-pr-ready
# See a scenario's shape — graph, branches, status — without keeping it
npx git-scenarios inspect feature-pr-ready
# Snapshot a real repo's shape into a reusable scenario module
npx git-scenarios capture . --name my-bug-repro > scenarios/my-bug.ts
# Persist a scenario for hand-poking
npx git-scenarios create monorepo-multi-package --path ~/sandbox

§06 · Origins

Provenance

Originally extracted from coco, an AI-powered git CLI/TUI with sixteen specialised views. Every view needed deterministic git states to test against. After writing the same git initwriteFilecommit setup forty-seven times, the scenario library was born.

Now it stands alone — for anyone building git tools, IDE extensions, CLI utilities, linters, formatters, AI assistants, or any program that reads repo state.

§07 · Notes

Where to next