Skip to content

Commit 9564219

Browse files
authored
Merge pull request #8325 from gitbutlerapp/test-stackless-commit-and-your-commit-goes-here
Test the stackless commit & 'your commit goes here' badge
2 parents cef5c1b + 34878d1 commit 9564219

File tree

8 files changed

+240
-14
lines changed

8 files changed

+240
-14
lines changed

apps/desktop/cypress/e2e/commitActions.cy.ts

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ import { MOCK_STACK_A_ID } from './support/mock/stacks';
55

66
describe('Commit Actions', () => {
77
let mockBackend: MockBackend;
8+
89
beforeEach(() => {
910
mockBackend = new MockBackend();
1011
mockCommand('stack_details', (params) => mockBackend.getStackDetails(params));
1112
mockCommand('update_commit_message', (params) => mockBackend.updateCommitMessage(params));
1213
mockCommand('changes_in_worktree', (params) => mockBackend.getWorktreeChanges(params));
1314
mockCommand('tree_change_diffs', (params) => mockBackend.getDiff(params));
15+
mockCommand('changes_in_commit', (params) => mockBackend.getCommitChanges(params));
1416
mockCommand('create_commit_from_worktree_changes', (params) =>
1517
mockBackend.createCommit(params)
1618
);
@@ -31,6 +33,7 @@ describe('Commit Actions', () => {
3133
const newCommitMessageBody = 'New commit message body';
3234

3335
cy.spy(mockBackend, 'updateCommitMessage').as('updateCommitMessageSpy');
36+
cy.spy(mockBackend, 'getDiff').as('getDiffSpy');
3437

3538
// Click on the first commit
3639
cy.getByTestId('commit-row').first().should('contain', originalCommitMessage).click();
@@ -78,6 +81,9 @@ describe('Commit Actions', () => {
7881
commitOid: mockBackend.commitOid,
7982
message: `${newCommitMessageTitle}\n\n${newCommitMessageBody}`
8083
});
84+
85+
// Should never get the diff information, because there are no partial changes being committed.
86+
expect(mockBackend.getDiff).to.have.callCount(0);
8187
});
8288

8389
it('Should be able to commit', () => {
@@ -107,6 +113,9 @@ describe('Commit Actions', () => {
107113
// Should open the new commit drawer
108114
cy.getByTestId('new-commit-drawer').should('be.visible');
109115

116+
// Should have the "Your commit goes here" text
117+
cy.getByTestId('your-commit-goes-here').should('be.visible').should('have.class', 'first');
118+
110119
// Should have selected the file
111120
cy.getByTestId('uncommitted-changes-file-list-item')
112121
.first()
@@ -135,7 +144,101 @@ describe('Commit Actions', () => {
135144
cy.getByTestId('commit-drawer-title').should('contain', newCommitMessage);
136145
cy.getByTestId('commit-drawer-description').should('contain', newCommitMessageBody);
137146

138-
// Should never get the diff information, becase there are no partial changes being commmitted.
139-
cy.get('@getDiffSpy').should('not.be.called');
147+
// Should never get the diff information, because there are no partial changes being committed.
148+
expect(mockBackend.getDiff).to.have.callCount(0);
149+
});
150+
});
151+
152+
describe('Commit Actions with no stacks', () => {
153+
let mockBackend: MockBackend;
154+
155+
beforeEach(() => {
156+
mockBackend = new MockBackend({ initalStacks: [] });
157+
mockCommand('stacks', () => mockBackend.getStacks());
158+
mockCommand('create_virtual_branch', () => mockBackend.createBranch());
159+
mockCommand('canned_branch_name', () => mockBackend.getCannedBranchName());
160+
mockCommand('stack_details', (params) => mockBackend.getStackDetails(params));
161+
mockCommand('update_commit_message', (params) => mockBackend.updateCommitMessage(params));
162+
mockCommand('changes_in_worktree', (params) => mockBackend.getWorktreeChanges(params));
163+
mockCommand('tree_change_diffs', (params) => mockBackend.getDiff(params));
164+
mockCommand('changes_in_commit', (params) => mockBackend.getCommitChanges(params));
165+
mockCommand('create_commit_from_worktree_changes', (params) =>
166+
mockBackend.createCommit(params)
167+
);
168+
169+
cy.visit('/');
170+
171+
cy.url().should('include', `/${PROJECT_ID}/workspace`);
172+
});
173+
174+
afterEach(() => {
175+
clearCommandMocks();
176+
});
177+
178+
it('Should be able to commit even without a stack present', () => {
179+
const newCommitMessage = 'New commit message';
180+
const newCommitMessageBody = 'New commit message body';
181+
182+
// spies
183+
cy.spy(mockBackend, 'getDiff').as('getDiffSpy');
184+
cy.spy(mockBackend, 'createBranch').as('createBranchSpy');
185+
186+
// There should be uncommitted changes
187+
cy.getByTestId('uncommitted-changes-file-list').should('be.visible');
188+
189+
const fileNames = mockBackend.getWorktreeChangesFileNames();
190+
191+
expect(fileNames).to.have.length(1);
192+
193+
const fileName = fileNames[0]!;
194+
195+
cy.getByTestId('uncommitted-changes-file-list-item')
196+
.first()
197+
.should('be.visible')
198+
.should('contain', fileName);
199+
200+
// Click on the commit button
201+
cy.getByTestId('start-commit-button').should('be.visible').should('be.enabled').click();
202+
203+
// Should open the new commit drawer
204+
cy.getByTestId('new-commit-drawer').should('be.visible');
205+
206+
// Should display the draft stack
207+
cy.getByTestId('stack-draft').should('be.visible');
208+
cy.getByTestId('stack-draft').should('contain', mockBackend.cannedBranchName);
209+
210+
// Should have the "Your commit goes here" text
211+
cy.getByTestId('your-commit-goes-here').should('be.visible').should('have.class', 'draft');
212+
213+
// Should have selected the file
214+
cy.getByTestId('uncommitted-changes-file-list-item')
215+
.first()
216+
.get('input[type="checkbox"]')
217+
.should('be.checked');
218+
219+
// Type in a commit message
220+
cy.getByTestId('commit-drawer-title-input')
221+
.should('be.visible')
222+
.should('be.enabled')
223+
.type(newCommitMessage); // Type the new commit message
224+
225+
// Type in a description
226+
cy.getByTestId('commit-drawer-description-input')
227+
.should('be.visible')
228+
.click()
229+
.type(newCommitMessageBody); // Type the new commit message body
230+
231+
// Click on the commit button
232+
cy.getByTestId('commit-drawer-action-button').should('be.visible').should('be.enabled').click();
233+
234+
// Should display the commit rows
235+
cy.getByTestId('commit-row').should('have.length', 1);
236+
237+
// Should commit and select the new commit
238+
cy.getByTestId('commit-drawer-title').should('contain', newCommitMessage);
239+
cy.getByTestId('commit-drawer-description').should('contain', newCommitMessageBody);
240+
241+
// Should never get the diff information, because there are no partial changes being committed.
242+
expect(mockBackend.getDiff).to.have.callCount(0);
140243
});
141244
});

apps/desktop/cypress/e2e/support/mock/backend.ts

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
bytesToStr,
3+
isGetCommitChangesParams,
34
isGetDiffParams,
45
isGetWorktreeChangesParams,
56
MOCK_TREE_CHANGE_A,
@@ -9,29 +10,60 @@ import {
910
isCreateCommitParams,
1011
isStackDetailsParams,
1112
isUpdateCommitMessageParams,
13+
MOCK_BRAND_NEW_BRANCH_NAME,
1214
MOCK_COMMIT,
1315
MOCK_STACK_A_ID,
14-
MOCK_STACK_DETAILS
16+
MOCK_STACK_BRAND_NEW,
17+
MOCK_STACK_BRAND_NEW_ID,
18+
MOCK_STACK_DETAILS,
19+
MOCK_STACK_DETAILS_BRAND_NEW,
20+
MOCK_STACKS
1521
} from './stacks';
16-
import type { WorktreeChanges } from '$lib/hunks/change';
22+
import type { TreeChange, TreeChanges, WorktreeChanges } from '$lib/hunks/change';
1723
import type { UnifiedDiff } from '$lib/hunks/diff';
18-
import type { StackDetails } from '$lib/stacks/stack';
24+
import type { Stack, StackDetails } from '$lib/stacks/stack';
1925
import type { InvokeArgs } from '@tauri-apps/api/core';
2026

27+
export type MockBackendOptions = {
28+
initalStacks?: Stack[];
29+
};
30+
2131
/**
2232
* *Ooooh look at me, I'm a mock backend!*
2333
*/
2434
export default class MockBackend {
35+
private stacks: Stack[];
2536
private stackDetails: Map<string, StackDetails>;
37+
private commitChanges: Map<string, TreeChange[]>;
2638
private worktreeChanges: WorktreeChanges;
2739
stackId: string = MOCK_STACK_A_ID;
40+
renamedCommitId: string = '424242424242';
2841
commitOid: string = MOCK_COMMIT.id;
42+
cannedBranchName = MOCK_BRAND_NEW_BRANCH_NAME;
2943

30-
constructor() {
44+
constructor(private options: MockBackendOptions = {}) {
45+
this.stacks = options.initalStacks ?? MOCK_STACKS;
3146
this.stackDetails = new Map<string, StackDetails>();
47+
this.commitChanges = new Map<string, TreeChange[]>();
3248
this.worktreeChanges = { changes: [MOCK_TREE_CHANGE_A], ignoredChanges: [] };
3349

3450
this.stackDetails.set(MOCK_STACK_A_ID, structuredClone(MOCK_STACK_DETAILS));
51+
this.stackDetails.set(MOCK_STACK_BRAND_NEW_ID, structuredClone(MOCK_STACK_DETAILS_BRAND_NEW));
52+
this.commitChanges.set(MOCK_COMMIT.id, []);
53+
this.commitChanges.set(this.renamedCommitId, []);
54+
}
55+
56+
public getStacks(): Stack[] {
57+
return this.stacks;
58+
}
59+
60+
public getCannedBranchName(): string {
61+
return this.cannedBranchName ?? 'super-cool-branch-name';
62+
}
63+
64+
public createBranch(): Stack {
65+
this.stacks.push(MOCK_STACK_BRAND_NEW);
66+
return MOCK_STACK_BRAND_NEW;
3567
}
3668

3769
public getStackDetails(args: InvokeArgs | undefined): StackDetails {
@@ -63,7 +95,7 @@ export default class MockBackend {
6395
const commitIndex = branch.commits.findIndex((commit) => commit.id === commitOid);
6496
if (commitIndex === -1) continue;
6597
const commit = branch.commits[commitIndex]!;
66-
const newId = '424242424242';
98+
const newId = this.renamedCommitId;
6799
branch.commits[commitIndex] = {
68100
...commit,
69101
message,
@@ -108,9 +140,17 @@ export default class MockBackend {
108140
const editableDetails = structuredClone(stackDetails);
109141

110142
// Assume only full file changes are passed.
111-
const remainingChanges = this.worktreeChanges.changes.filter((change) => {
112-
return !worktreeChanges.some((c) => bytesToStr(c.pathBytes) === change.path);
113-
});
143+
const remainingChanges: TreeChange[] = [];
144+
const committedChanges: TreeChange[] = [];
145+
146+
for (const change of this.worktreeChanges.changes) {
147+
const isCommitted = worktreeChanges.some((c) => bytesToStr(c.pathBytes) === change.path);
148+
if (isCommitted) {
149+
committedChanges.push(change);
150+
} else {
151+
remainingChanges.push(change);
152+
}
153+
}
114154

115155
this.worktreeChanges = {
116156
...this.worktreeChanges,
@@ -140,6 +180,7 @@ export default class MockBackend {
140180
];
141181

142182
this.stackDetails.set(stackId, editableDetails);
183+
this.commitChanges.set(newCommitId, committedChanges);
143184

144185
const pathsToRejectedChanges: string[] = [];
145186

@@ -153,4 +194,26 @@ export default class MockBackend {
153194

154195
return MOCK_UNIFIED_DIFF;
155196
}
197+
198+
public getCommitChanges(args: InvokeArgs | undefined): TreeChanges {
199+
if (!args || !isGetCommitChangesParams(args)) {
200+
throw new Error('Invalid arguments for getCommitChanges');
201+
}
202+
203+
const { commitId } = args;
204+
const changes = this.commitChanges.get(commitId);
205+
206+
if (!changes) {
207+
throw new Error(`No changes found for commit with ID ${commitId}`);
208+
}
209+
210+
return {
211+
changes,
212+
stats: {
213+
linesAdded: 0,
214+
linesRemoved: 0,
215+
filesChanged: changes.length
216+
}
217+
};
218+
}
156219
}

apps/desktop/cypress/e2e/support/mock/changes.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,19 @@ export function isGetDiffParams(args: unknown): args is GetDiffParams {
102102
isTreeChange(args['change'])
103103
);
104104
}
105+
106+
export type GetCommitChangesParams = {
107+
projectId: string;
108+
commitId: string;
109+
};
110+
111+
export function isGetCommitChangesParams(args: unknown): args is GetCommitChangesParams {
112+
return (
113+
typeof args === 'object' &&
114+
args !== null &&
115+
'projectId' in args &&
116+
typeof args['projectId'] === 'string' &&
117+
'commitId' in args &&
118+
typeof args['commitId'] === 'string'
119+
);
120+
}

apps/desktop/cypress/e2e/support/mock/stacks.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,21 @@ export const MOCK_STACK_A: Stack = {
1515
tip: '1234123'
1616
};
1717

18+
export const MOCK_BRAND_NEW_BRANCH_NAME = 'super-cool-branch-name';
19+
20+
export const MOCK_STACK_BRAND_NEW_ID = 'empty-stack';
21+
22+
export const MOCK_STACK_BRAND_NEW: Stack = {
23+
id: MOCK_STACK_BRAND_NEW_ID,
24+
heads: [
25+
{
26+
name: MOCK_BRAND_NEW_BRANCH_NAME,
27+
tip: '1234123'
28+
}
29+
],
30+
tip: '1234123'
31+
};
32+
1833
export const MOCK_STACKS: Stack[] = [MOCK_STACK_A];
1934

2035
export const MOCK_AUTHOR: Author = {
@@ -59,8 +74,32 @@ export const MOCK_BRANCH_DETAILS: BranchDetails = {
5974
isRemoteHead: false
6075
};
6176

77+
export const MOCK_BRANCH_DETAILS_BRAND_NEW: BranchDetails = {
78+
name: MOCK_BRAND_NEW_BRANCH_NAME,
79+
remoteTrackingBranch: null,
80+
description: 'A mock branch for testing',
81+
prNumber: null,
82+
reviewId: null,
83+
tip: '1234123',
84+
baseCommit: 'base-sha',
85+
pushStatus: 'completelyUnpushed',
86+
lastUpdatedAt: Date.now(),
87+
authors: [],
88+
isConflicted: false,
89+
commits: [],
90+
upstreamCommits: [],
91+
isRemoteHead: false
92+
};
93+
94+
export const MOCK_STACK_DETAILS_BRAND_NEW: StackDetails = {
95+
derivedName: MOCK_BRAND_NEW_BRANCH_NAME,
96+
pushStatus: 'completelyUnpushed',
97+
branchDetails: [MOCK_BRANCH_DETAILS_BRAND_NEW],
98+
isConflicted: false
99+
};
100+
62101
export const MOCK_STACK_DETAILS: StackDetails = {
63-
derivedName: 'mock-branch',
102+
derivedName: 'branch-a',
64103
pushStatus: 'completelyUnpushed',
65104
branchDetails: [MOCK_BRANCH_DETAILS],
66105
isConflicted: false

apps/desktop/src/components/v3/CommitGoesHere.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script lang="ts">
2+
import { TestId } from '$lib/testing/testIds';
23
import Badge from '@gitbutler/ui/Badge.svelte';
34
45
type Props = {
@@ -14,6 +15,7 @@
1415

1516
{#snippet indicator(args?: { last?: boolean; first?: boolean; draft?: boolean })}
1617
<div
18+
data-testid={TestId.YourCommitGoesHere}
1719
class="indicator"
1820
class:first={args?.first}
1921
class:last={args?.last}

apps/desktop/src/components/v3/StackDraft.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import CommitGoesHere from '$components/v3/CommitGoesHere.svelte';
55
import { StackService } from '$lib/stacks/stackService.svelte';
66
import { UiState } from '$lib/state/uiState.svelte';
7+
import { TestId } from '$lib/testing/testIds';
78
import { inject } from '@gitbutler/shared/context';
89
910
type Props = {
@@ -32,7 +33,7 @@
3233
const branchName = $derived(draftBranchName.current || newName);
3334
</script>
3435

35-
<div class="stack-draft">
36+
<div data-testid={TestId.StackDraft} class="stack-draft">
3637
<BranchCard type="draft-branch" {projectId} {branchName}>
3738
{#snippet header()}
3839
<BranchHeader

apps/desktop/src/components/v3/WorktreeChanges.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@
159159
type="button"
160160
size="cta"
161161
wide
162-
disabled={isCommitting || !defaultBranchName}
162+
disabled={isCommitting || defaultBranchResult?.current.isLoading}
163163
onclick={startCommit}
164164
>
165165
Start a commit…

0 commit comments

Comments
 (0)