diff --git a/src/command-registry.ts b/src/command-registry.ts
index b258a2d..cbc5535 100644
--- a/src/command-registry.ts
+++ b/src/command-registry.ts
@@ -1,3 +1,4 @@
+import ChangesAdd, { changesAddConfig } from './commands/changes/add.js';
import Help, { helpConfig } from './commands/help.js';
import Hop, { hopConfig } from './commands/hop.js';
import Sync, { syncConfig } from './commands/sync.js';
@@ -20,4 +21,15 @@ export const REGISTERED_COMMANDS: CommandGroup = {
component: Sync,
config: syncConfig,
},
+ changes: {
+ _group: {
+ alias: 'c',
+ name: 'changes',
+ description: 'Commands related to staged changes',
+ },
+ add: {
+ component: ChangesAdd,
+ config: changesAddConfig,
+ },
+ } as CommandGroup,
} as const;
diff --git a/src/commands/changes/add.test.tsx b/src/commands/changes/add.test.tsx
new file mode 100644
index 0000000..5250f42
--- /dev/null
+++ b/src/commands/changes/add.test.tsx
@@ -0,0 +1,100 @@
+import ChangesAdd from './add.js';
+import React from 'react';
+import { Text } from 'ink';
+import { delay } from '../../utils/time.js';
+import { describe, expect, it, vi } from 'vitest';
+import { render } from '@levelbreaded/ink-testing-library';
+
+const ARBITRARY_DELAY = 250; // ms
+
+const mocks = vi.hoisted(() => {
+ return {
+ createGitService: vi.fn(({}) => {
+ return {
+ addAllFiles: async () => {
+ return new Promise((resolve) =>
+ setTimeout(resolve, ARBITRARY_DELAY)
+ );
+ },
+ };
+ }),
+ };
+});
+
+vi.mock('../../services/git.js', () => {
+ return {
+ DEFAULT_OPTIONS: {},
+ createGitService: mocks.createGitService,
+ };
+});
+
+const LOADING_MESSAGE = 'Loading...';
+const SUCCESS_MESSAGE = 'Staged all changes';
+
+describe('correctly renders changes add UI', () => {
+ it('runs as intended', async () => {
+ const actual1 = render(
+
+ );
+
+ const actual2 = render(
+
+ );
+
+ const ExpectedComp = () => {
+ return (
+
+ {SUCCESS_MESSAGE}
+
+ );
+ };
+ const expected = render();
+
+ await delay(ARBITRARY_DELAY + 250);
+ expect(actual1.lastFrame()).to.equal(expected.lastFrame());
+ expect(actual2.lastFrame()).to.equal(expected.lastFrame());
+ });
+
+ it('displays a loading state while processing', async () => {
+ const actual1 = render(
+
+ );
+
+ const actual2 = render(
+
+ );
+
+ const ExpectedComp = () => {
+ return {LOADING_MESSAGE};
+ };
+ const expected = render();
+
+ await delay(ARBITRARY_DELAY / 2);
+ expect(actual1.lastFrame()).to.equal(expected.lastFrame());
+ expect(actual2.lastFrame()).to.equal(expected.lastFrame());
+ });
+});
diff --git a/src/commands/changes/add.tsx b/src/commands/changes/add.tsx
new file mode 100644
index 0000000..70df36f
--- /dev/null
+++ b/src/commands/changes/add.tsx
@@ -0,0 +1,80 @@
+import ErrorDisplay from '../../components/error-display.js';
+import React, { useEffect, useState } from 'react';
+import { CommandConfig, CommandProps } from '../../types.js';
+import { Text } from 'ink';
+import { useGit } from '../../hooks/use-git.js';
+
+function ChangedAdd({}: CommandProps) {
+ const result = useChangesAdd();
+
+ if (result.isError) {
+ return ;
+ }
+
+ if (result.isLoading) {
+ return Loading...;
+ }
+
+ return (
+
+ Staged all changes
+
+ );
+}
+
+type Action = { isLoading: boolean } & (
+ | {
+ isError: false;
+ }
+ | {
+ isError: true;
+ error: Error;
+ }
+);
+
+type State =
+ | {
+ type: 'LOADING';
+ }
+ | {
+ type: 'COMPLETE';
+ }
+ | {
+ type: 'ERROR';
+ error: Error;
+ };
+
+const useChangesAdd = (): Action => {
+ const git = useGit();
+ const [state, setState] = useState({ type: 'LOADING' });
+
+ useEffect(() => {
+ git.addAllFiles()
+ .then(() => setState({ type: 'COMPLETE' }))
+ .catch((e: Error) => {
+ setState({ type: 'ERROR', error: e });
+ });
+ }, []);
+
+ if (state.type === 'ERROR') {
+ return {
+ isLoading: false,
+ isError: true,
+ error: state.error,
+ };
+ }
+
+ return {
+ isLoading: state.type === 'LOADING',
+ isError: false,
+ };
+};
+
+export const changesAddConfig: CommandConfig = {
+ description: 'Stage all changes.',
+ usage: 'changes add',
+ key: 'add',
+ aliases: ['a'],
+};
+
+export default ChangedAdd;
diff --git a/src/services/git.ts b/src/services/git.ts
index 8952f57..e913926 100644
--- a/src/services/git.ts
+++ b/src/services/git.ts
@@ -11,6 +11,7 @@ export interface GitService {
_git: SimpleGit;
branchLocal: () => Promise>;
checkout: (branch: string) => Promise>;
+ addAllFiles: () => Promise;
}
export const createGitService = ({
@@ -29,5 +30,8 @@ export const createGitService = ({
branchLocal: async () => {
return gitEngine.branchLocal();
},
+ addAllFiles: async () => {
+ await gitEngine.add('.');
+ },
};
};