Loading src/desktop/issuable_controller.ts +28 −5 Original line number Diff line number Diff line Loading @@ -18,6 +18,24 @@ export type IssuableWebviewPanel = vscode.WebviewPanel & { repositoryRoot?: string; }; async function getSourceNamespace( issuable: RestIssuable, gitlabService: GitLabService, ): Promise<string | undefined> { if (!isMr(issuable)) return undefined; if (issuable.source_project_id === issuable.target_project_id) return undefined; try { const sourceProject = await gitlabService.fetchFromApi<RestProject>({ type: 'rest', method: 'GET', path: `/projects/${issuable.source_project_id}`, }); return sourceProject.path_with_namespace; } catch { return undefined; } } async function initPanelIfActive( panel: vscode.WebviewPanel, issuable: RestIssuable, Loading @@ -27,17 +45,22 @@ async function initPanelIfActive( const waitPromise = waitForWebview(panel.webview); const discussionsAndLabels = await gitlabService .getDiscussionsAndLabelEvents(issuable) .catch(e => { const [discussionsAndLabels, sourceNamespace] = await Promise.all([ gitlabService.getDiscussionsAndLabelEvents(issuable).catch(e => { handleError(e); return []; }); }), getSourceNamespace(issuable, gitlabService), ]); const enrichedIssuable = sourceNamespace ? { ...issuable, source_namespace: sourceNamespace } : issuable; await waitPromise; await panel.webview.postMessage({ type: 'issuableFetch', issuable, issuable: enrichedIssuable, discussions: discussionsAndLabels, }); } Loading src/desktop/test_utils/entities.ts +1 −0 Original line number Diff line number Diff line Loading @@ -221,6 +221,7 @@ export const restProject: RestProject = { ssh_url_to_repo: 'sshurl', http_url_to_repo: 'httpurl', web_url: 'weburl', path_with_namespace: '', }; export const user: RestUser = { Loading src/desktop/types.d.ts +1 −0 Original line number Diff line number Diff line Loading @@ -197,6 +197,7 @@ interface RestTag { type ProjectVisibility = 'public' | 'internal' | 'private'; interface RestProject { path_with_namespace: string; ssh_url_to_repo: string; http_url_to_repo: string; web_url: string; Loading webviews/vue3/issuable/src/App.vue +0 −4 Original line number Diff line number Diff line Loading @@ -57,10 +57,6 @@ export default { </template> <style lang="scss"> .issuable-details .state { color: var(--vscode-foreground); } .capitalize { text-transform: capitalize; } Loading webviews/vue3/issuable/src/components/IssuableDetails.test.js 0 → 100644 +144 −0 Original line number Diff line number Diff line import { vi, expect, it, describe, beforeEach } from 'vitest'; import { mount } from '@vue/test-utils'; import IssuableDetails from './IssuableDetails.vue'; const baseIssuable = { id: 1, title: 'Fix the bug', state: 'opened', description: 'Some description', web_url: 'https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/issues/1', created_at: '2024-01-01T00:00:00Z', author: { name: 'Jane Doe', username: 'janedoe', avatar_url: 'https://example.com/avatar.jpg', }, }; const baseMr = { ...baseIssuable, web_url: 'https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/merge_requests/1', state: 'opened', source_branch: 'feature-branch', target_branch: 'main', source_project_id: 1, target_project_id: 1, }; const mountComponent = (issuable = baseIssuable) => mount(IssuableDetails, { props: { issuable }, global: { stubs: { date: true, 'user-avatar': true }, directives: { SafeHtml: {} }, }, }); describe('IssuableDetails', () => { beforeEach(() => { window.vsCodeApi = { postMessage: vi.fn() }; }); describe('title', () => { it('renders the issuable title', () => { const wrapper = mountComponent(); expect(wrapper.find('.title').text()).toBe('Fix the bug'); }); }); describe('Open in GitLab link', () => { it('renders a link to the issuable web url', () => { const wrapper = mountComponent(); const link = wrapper.find('.view-link'); expect(link.attributes('href')).toBe(baseIssuable.web_url); }); }); describe('state badge', () => { it.each` state | expectedText | expectedClass ${'opened'} | ${'Open'} | ${'opened'} ${'closed'} | ${'Closed'} | ${'closed'} ${'merged'} | ${'Merged'} | ${'merged'} `( 'renders "$expectedText" badge for $state state', ({ state, expectedText, expectedClass }) => { const wrapper = mountComponent({ ...baseIssuable, state }); const badge = wrapper.find('.state'); expect(badge.text()).toBe(expectedText); expect(badge.classes()).toContain(expectedClass); }, ); it('renders empty text for unknown state', () => { const wrapper = mountComponent({ ...baseIssuable, state: 'unknown' }); expect(wrapper.find('.state').text()).toBe(''); }); }); describe('branch info', () => { it('does not render branch info for issues', () => { const wrapper = mountComponent(baseIssuable); expect(wrapper.find('.branch-info').exists()).toBe(false); }); it('renders branch info for merge requests', () => { const wrapper = mountComponent(baseMr); expect(wrapper.find('.branch-info').exists()).toBe(true); }); it('renders source and target branch labels', () => { const wrapper = mountComponent(baseMr); const labels = wrapper.findAll('.branch-label'); expect(labels[0].text()).toBe('feature-branch'); expect(labels[1].text()).toBe('main'); }); it('links source branch to the correct GitLab branch page', () => { const wrapper = mountComponent(baseMr); const labels = wrapper.findAll('.branch-label'); expect(labels[0].attributes('href')).toBe( 'https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/tree/feature-branch', ); }); it('links target branch to the correct GitLab branch page', () => { const wrapper = mountComponent(baseMr); const labels = wrapper.findAll('.branch-label'); expect(labels[1].attributes('href')).toBe( 'https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/tree/main', ); }); describe('fork MR', () => { const forkMr = { ...baseMr, source_project_id: 9999, source_namespace: 'my_fork/test_project', }; it('prefixes source branch label with the fork namespace', () => { const wrapper = mountComponent(forkMr); const labels = wrapper.findAll('.branch-label'); expect(labels[0].text()).toBe('my_fork/test_project:feature-branch'); }); it('links source branch to the fork project branch page', () => { const wrapper = mountComponent(forkMr); const labels = wrapper.findAll('.branch-label'); expect(labels[0].attributes('href')).toBe( 'https://gitlab.com/my_fork/test_project/-/tree/feature-branch', ); }); it('links target branch to the target project branch page', () => { const wrapper = mountComponent(forkMr); const labels = wrapper.findAll('.branch-label'); expect(labels[1].attributes('href')).toBe( 'https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/tree/main', ); }); }); }); }); Loading
src/desktop/issuable_controller.ts +28 −5 Original line number Diff line number Diff line Loading @@ -18,6 +18,24 @@ export type IssuableWebviewPanel = vscode.WebviewPanel & { repositoryRoot?: string; }; async function getSourceNamespace( issuable: RestIssuable, gitlabService: GitLabService, ): Promise<string | undefined> { if (!isMr(issuable)) return undefined; if (issuable.source_project_id === issuable.target_project_id) return undefined; try { const sourceProject = await gitlabService.fetchFromApi<RestProject>({ type: 'rest', method: 'GET', path: `/projects/${issuable.source_project_id}`, }); return sourceProject.path_with_namespace; } catch { return undefined; } } async function initPanelIfActive( panel: vscode.WebviewPanel, issuable: RestIssuable, Loading @@ -27,17 +45,22 @@ async function initPanelIfActive( const waitPromise = waitForWebview(panel.webview); const discussionsAndLabels = await gitlabService .getDiscussionsAndLabelEvents(issuable) .catch(e => { const [discussionsAndLabels, sourceNamespace] = await Promise.all([ gitlabService.getDiscussionsAndLabelEvents(issuable).catch(e => { handleError(e); return []; }); }), getSourceNamespace(issuable, gitlabService), ]); const enrichedIssuable = sourceNamespace ? { ...issuable, source_namespace: sourceNamespace } : issuable; await waitPromise; await panel.webview.postMessage({ type: 'issuableFetch', issuable, issuable: enrichedIssuable, discussions: discussionsAndLabels, }); } Loading
src/desktop/test_utils/entities.ts +1 −0 Original line number Diff line number Diff line Loading @@ -221,6 +221,7 @@ export const restProject: RestProject = { ssh_url_to_repo: 'sshurl', http_url_to_repo: 'httpurl', web_url: 'weburl', path_with_namespace: '', }; export const user: RestUser = { Loading
src/desktop/types.d.ts +1 −0 Original line number Diff line number Diff line Loading @@ -197,6 +197,7 @@ interface RestTag { type ProjectVisibility = 'public' | 'internal' | 'private'; interface RestProject { path_with_namespace: string; ssh_url_to_repo: string; http_url_to_repo: string; web_url: string; Loading
webviews/vue3/issuable/src/App.vue +0 −4 Original line number Diff line number Diff line Loading @@ -57,10 +57,6 @@ export default { </template> <style lang="scss"> .issuable-details .state { color: var(--vscode-foreground); } .capitalize { text-transform: capitalize; } Loading
webviews/vue3/issuable/src/components/IssuableDetails.test.js 0 → 100644 +144 −0 Original line number Diff line number Diff line import { vi, expect, it, describe, beforeEach } from 'vitest'; import { mount } from '@vue/test-utils'; import IssuableDetails from './IssuableDetails.vue'; const baseIssuable = { id: 1, title: 'Fix the bug', state: 'opened', description: 'Some description', web_url: 'https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/issues/1', created_at: '2024-01-01T00:00:00Z', author: { name: 'Jane Doe', username: 'janedoe', avatar_url: 'https://example.com/avatar.jpg', }, }; const baseMr = { ...baseIssuable, web_url: 'https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/merge_requests/1', state: 'opened', source_branch: 'feature-branch', target_branch: 'main', source_project_id: 1, target_project_id: 1, }; const mountComponent = (issuable = baseIssuable) => mount(IssuableDetails, { props: { issuable }, global: { stubs: { date: true, 'user-avatar': true }, directives: { SafeHtml: {} }, }, }); describe('IssuableDetails', () => { beforeEach(() => { window.vsCodeApi = { postMessage: vi.fn() }; }); describe('title', () => { it('renders the issuable title', () => { const wrapper = mountComponent(); expect(wrapper.find('.title').text()).toBe('Fix the bug'); }); }); describe('Open in GitLab link', () => { it('renders a link to the issuable web url', () => { const wrapper = mountComponent(); const link = wrapper.find('.view-link'); expect(link.attributes('href')).toBe(baseIssuable.web_url); }); }); describe('state badge', () => { it.each` state | expectedText | expectedClass ${'opened'} | ${'Open'} | ${'opened'} ${'closed'} | ${'Closed'} | ${'closed'} ${'merged'} | ${'Merged'} | ${'merged'} `( 'renders "$expectedText" badge for $state state', ({ state, expectedText, expectedClass }) => { const wrapper = mountComponent({ ...baseIssuable, state }); const badge = wrapper.find('.state'); expect(badge.text()).toBe(expectedText); expect(badge.classes()).toContain(expectedClass); }, ); it('renders empty text for unknown state', () => { const wrapper = mountComponent({ ...baseIssuable, state: 'unknown' }); expect(wrapper.find('.state').text()).toBe(''); }); }); describe('branch info', () => { it('does not render branch info for issues', () => { const wrapper = mountComponent(baseIssuable); expect(wrapper.find('.branch-info').exists()).toBe(false); }); it('renders branch info for merge requests', () => { const wrapper = mountComponent(baseMr); expect(wrapper.find('.branch-info').exists()).toBe(true); }); it('renders source and target branch labels', () => { const wrapper = mountComponent(baseMr); const labels = wrapper.findAll('.branch-label'); expect(labels[0].text()).toBe('feature-branch'); expect(labels[1].text()).toBe('main'); }); it('links source branch to the correct GitLab branch page', () => { const wrapper = mountComponent(baseMr); const labels = wrapper.findAll('.branch-label'); expect(labels[0].attributes('href')).toBe( 'https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/tree/feature-branch', ); }); it('links target branch to the correct GitLab branch page', () => { const wrapper = mountComponent(baseMr); const labels = wrapper.findAll('.branch-label'); expect(labels[1].attributes('href')).toBe( 'https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/tree/main', ); }); describe('fork MR', () => { const forkMr = { ...baseMr, source_project_id: 9999, source_namespace: 'my_fork/test_project', }; it('prefixes source branch label with the fork namespace', () => { const wrapper = mountComponent(forkMr); const labels = wrapper.findAll('.branch-label'); expect(labels[0].text()).toBe('my_fork/test_project:feature-branch'); }); it('links source branch to the fork project branch page', () => { const wrapper = mountComponent(forkMr); const labels = wrapper.findAll('.branch-label'); expect(labels[0].attributes('href')).toBe( 'https://gitlab.com/my_fork/test_project/-/tree/feature-branch', ); }); it('links target branch to the target project branch page', () => { const wrapper = mountComponent(forkMr); const labels = wrapper.findAll('.branch-label'); expect(labels[1].attributes('href')).toBe( 'https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/tree/main', ); }); }); }); });