Skip to content
Draft
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
24 changes: 14 additions & 10 deletions src/issues/stateManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,20 +345,24 @@ export class StateManager {
singleRepoState.maxIssueNumber = await folderManager.getMaxIssue();
singleRepoState.lastHead = folderManager.repository.state.HEAD?.commit;
singleRepoState.lastBranch = folderManager.repository.state.HEAD?.name;
this._onDidChangeIssueData.fire();
}

private setIssues(folderManager: FolderRepositoryManager, query: string): Promise<IssueItem[] | undefined> {
return new Promise(async resolve => {
private async setIssues(folderManager: FolderRepositoryManager, query: string): Promise<IssueItem[] | undefined> {
try {
const issues = await folderManager.getIssues(query, { fetchNextPage: false, fetchOnePagePerRepo: true });
return issues?.items.map(item => {
const issueItem: IssueItem = item as IssueItem;
issueItem.uri = folderManager.repository.rootUri;
return issueItem;
});
} catch {
// Errors from fetching issues are expected (e.g. network failures).
// Return undefined so the tree shows an empty state for this query.
return undefined;
} finally {
this._onDidChangeIssueData.fire();
resolve(
issues?.items.map(item => {
const issueItem: IssueItem = item as IssueItem;
issueItem.uri = folderManager.repository.rootUri;
return issueItem;
}),
);
});
}
}

private async setCurrentIssueFromBranch(singleRepoState: SingleRepoState, branchName: string, silent: boolean = false) {
Expand Down
136 changes: 136 additions & 0 deletions src/test/issues/stateManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,140 @@ describe('StateManager branch behavior with useBranchForIssues setting', functio
vscode.workspace.getConfiguration = originalGetConfiguration;
}
});

it('should fire onDidChangeIssueData even when getIssues throws', async function () {
const mockUri = vscode.Uri.parse('file:///test');
const mockFolderManager = {
repository: { rootUri: mockUri, state: { HEAD: { commit: 'abc123' }, remotes: [] } },
getIssues: async () => {
throw new Error('Network error');
},
getMaxIssue: async () => 0,
};

const originalGetConfiguration = vscode.workspace.getConfiguration;
vscode.workspace.getConfiguration = (section?: string) => {
if (section === ISSUES_SETTINGS_NAMESPACE) {
return {
get: (key: string, defaultValue?: any) => {
if (key === 'queries') {
return [{ label: 'Test', query: 'is:open assignee:@me repo:owner/repo', groupBy: [] }];
}
return defaultValue;
},
} as any;
}
return originalGetConfiguration(section);
};

try {
const sm = new StateManager(undefined as any, {
folderManagers: [mockFolderManager],
credentialStore: { isAnyAuthenticated: () => true, getCurrentUser: async () => ({ login: 'testuser' }) },
} as any, mockContext);

let changeEventCount = 0;
sm.onDidChangeIssueData(() => changeEventCount++);

await (sm as any).setIssueData(mockFolderManager);

// The event should have fired even though getIssues threw
assert.ok(changeEventCount > 0, 'onDidChangeIssueData should fire even when getIssues fails');

// The promise in the collection should resolve to undefined issues (not reject)
const collection = sm.getIssueCollection(mockUri);
const queryResult = await collection.get('Test');
assert.strictEqual(queryResult?.issues, undefined, 'Issues should be undefined when getIssues fails');
} finally {
vscode.workspace.getConfiguration = originalGetConfiguration;
}
});

it('should fire onDidChangeIssueData after setIssueData completes', async function () {
const mockUri = vscode.Uri.parse('file:///test');
const mockFolderManager = {
repository: { rootUri: mockUri, state: { HEAD: { commit: 'abc123' }, remotes: [] } },
getIssues: async () => {
return { items: [], hasMorePages: false, hasUnsearchedRepositories: false, totalCount: 0 };
},
getMaxIssue: async () => 0,
};

const originalGetConfiguration = vscode.workspace.getConfiguration;
vscode.workspace.getConfiguration = (section?: string) => {
if (section === ISSUES_SETTINGS_NAMESPACE) {
return {
get: (key: string, defaultValue?: any) => {
if (key === 'queries') {
return [{ label: 'Test', query: 'is:open assignee:@me repo:owner/repo', groupBy: [] }];
}
return defaultValue;
},
} as any;
}
return originalGetConfiguration(section);
};

try {
const sm = new StateManager(undefined as any, {
folderManagers: [mockFolderManager],
credentialStore: { isAnyAuthenticated: () => true, getCurrentUser: async () => ({ login: 'testuser' }) },
} as any, mockContext);

let changeEventCount = 0;
sm.onDidChangeIssueData(() => changeEventCount++);

await (sm as any).setIssueData(mockFolderManager);

// The event should fire at least twice: once from setIssues (per-query) and once from setIssueData (final)
assert.strictEqual(changeEventCount, 2, `onDidChangeIssueData should fire exactly twice for a single query, but fired ${changeEventCount} times`);
} finally {
vscode.workspace.getConfiguration = originalGetConfiguration;
}
});

it('should not reject promises in issueCollection when getIssues throws', async function () {
const mockUri = vscode.Uri.parse('file:///test');
const mockFolderManager = {
repository: { rootUri: mockUri, state: { HEAD: { commit: 'abc123' }, remotes: [] } },
getIssues: async () => {
throw new Error('API error');
},
getMaxIssue: async () => 0,
};

const originalGetConfiguration = vscode.workspace.getConfiguration;
vscode.workspace.getConfiguration = (section?: string) => {
if (section === ISSUES_SETTINGS_NAMESPACE) {
return {
get: (key: string, defaultValue?: any) => {
if (key === 'queries') {
return [{ label: 'Test', query: 'is:open repo:owner/repo', groupBy: [] }];
}
return defaultValue;
},
} as any;
}
return originalGetConfiguration(section);
};

try {
const sm = new StateManager(undefined as any, {
folderManagers: [mockFolderManager],
credentialStore: { isAnyAuthenticated: () => true, getCurrentUser: async () => ({ login: 'testuser' }) },
} as any, mockContext);

await (sm as any).setIssueData(mockFolderManager);

// Verify that the promises in issueCollection resolve (not reject)
const collection = sm.getIssueCollection(mockUri);
for (const [, promise] of collection) {
const result = await promise;
assert.ok(result !== undefined, 'Promise should resolve, not reject');
assert.strictEqual(result.issues, undefined, 'Issues should be undefined on error');
}
} finally {
vscode.workspace.getConfiguration = originalGetConfiguration;
}
});
});
Loading