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('.'); + }, }; };