Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
38 changes: 38 additions & 0 deletions src/simulation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,44 @@ describe('git state simulation scenarios', () => {
expect(committed.changes[0]?.current).toBeUndefined();
});

it('unstages modified file on mixed reset', async () => {
const sim = await trackSimulationWithTrackedFile('reset-mixed.ts', 'v1\n');

await sim.write('reset-mixed.ts', 'v2\n', { label: 'edit before reset' });
const staged = await sim.gitAdd('reset-mixed.ts', { label: 'stage before reset' });
expect(staged.tree).toContain('[M staged]');

const reset = await sim.gitReset('mixed', { label: 'mixed reset' });
expect(reset.changes).toHaveLength(1);
expect(reset.tree).toContain('[M unstaged]');
expect(reset.changes[0]).toMatchObject({
path: 'reset-mixed.ts',
previous: { staged: true, unstaged: false },
current: {
path: 'reset-mixed.ts',
status: 'modified',
staged: false,
unstaged: true,
},
});
});

it('clears dirty tracked state on hard reset', async () => {
const sim = await trackSimulationWithTrackedFile('reset-hard.ts', 'v1\n');

await sim.write('reset-hard.ts', 'v2\n', { label: 'edit before hard reset' });
await sim.gitAdd('reset-hard.ts', { label: 'stage before hard reset' });

const reset = await sim.gitReset('hard', { label: 'hard reset' });
expect(reset.changes).toHaveLength(1);
expect(reset.tree).not.toContain('[M');
expect(reset.changes[0]).toMatchObject({
path: 'reset-hard.ts',
current: undefined,
});
expect(sim.getState()).toEqual([]);
});

it('does not emit extra events for unchanged filesystem writes', async () => {
const sim = await trackSimulationWithTrackedFile('stable.ts', 'same\n');

Expand Down
47 changes: 47 additions & 0 deletions src/watcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ async function simpleGitAdd(repoPath: string, filePath: string): Promise<void> {
await simpleGit(repoPath).add(filePath);
}

async function simpleGitReset(
repoPath: string,
mode: 'soft' | 'mixed' | 'hard',
): Promise<void> {
const { simpleGit } = await import('simple-git');
await simpleGit(repoPath).reset([`--${mode}`]);
}

afterEach(async () => {
await Promise.all(
tempRepos.splice(0).map((repo) => fs.rm(repo, { recursive: true, force: true })),
Expand Down Expand Up @@ -144,6 +152,45 @@ describe('createGitStateWatcher', () => {
await watcher.close();
});

it('emits when git metadata changes on mixed reset', async () => {
const repo = await trackRepo();
await writeAndAdd(repo, 'tracked.ts', 'v1\n');
await simpleGitCommit(repo, 'initial');
await fs.writeFile(path.join(repo, 'tracked.ts'), 'v2\n', 'utf8');

const watcher = await createGitStateWatcher(repo, { debounceMs: 75 });
expect(watcher.getFileState('tracked.ts')).toMatchObject({
path: 'tracked.ts',
status: 'modified',
staged: false,
unstaged: true,
});

const stagedChangesPromise = waitForChanges(watcher);
await simpleGitAdd(repo, 'tracked.ts');
const stagedChanges = await stagedChangesPromise;

expect(stagedChanges).toHaveLength(1);
expect(stagedChanges[0]).toMatchObject({
path: 'tracked.ts',
previous: { staged: false, unstaged: true },
current: { status: 'modified', staged: true, unstaged: false },
});

const resetChangesPromise = waitForChanges(watcher);
await simpleGitReset(repo, 'mixed');
const resetChanges = await resetChangesPromise;

expect(resetChanges).toHaveLength(1);
expect(resetChanges[0]).toMatchObject({
path: 'tracked.ts',
previous: { staged: true, unstaged: false },
current: { status: 'modified', staged: false, unstaged: true },
});

await watcher.close();
});

it('does not emit when filesystem noise leaves git state unchanged', async () => {
const repo = await trackRepo();
await writeAndAdd(repo, 'stable.ts', 'same\n');
Expand Down