diff --git a/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu.spec.js b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu.spec.js
index 9e3633e5319347e5835ee63c4481e919e8dc955e..e93972db4261a0cee3e7613392b7cfafb66a7489 100644
--- a/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu.spec.js
+++ b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu.spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import { getMockContextItems, MOCK_CATEGORIES } from '../mock_context_data';
+import { getMockCategory, getMockContextItems, MOCK_CATEGORIES } from '../mock_context_data';
import {
CONTEXT_ITEM_TYPE_ISSUE,
CONTEXT_ITEM_TYPE_MERGE_REQUEST,
@@ -98,7 +98,7 @@ describe('GlDuoChatContextItemMenu', () => {
let category;
let results;
beforeEach(() => {
- category = MOCK_CATEGORIES.find((cat) => cat.value === categoryValue);
+ category = getMockCategory(categoryValue);
results = getMockContextItems()
.filter((item) => item.type === categoryValue)
.map((item, index) => ({
diff --git a/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu_search_item.spec.js b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu_search_item.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..9ddf410b2518e086a7122b689f6569a6986ca3eb
--- /dev/null
+++ b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu_search_item.spec.js
@@ -0,0 +1,64 @@
+import { shallowMount } from '@vue/test-utils';
+import {
+ getMockCategory,
+ MOCK_CONTEXT_ITEM_FILE,
+ MOCK_CONTEXT_ITEM_ISSUE,
+ MOCK_CONTEXT_ITEM_MERGE_REQUEST,
+} from '../mock_context_data';
+import {
+ CONTEXT_ITEM_TYPE_ISSUE,
+ CONTEXT_ITEM_TYPE_MERGE_REQUEST,
+ CONTEXT_ITEM_TYPE_PROJECT_FILE,
+} from '../constants';
+import GlDuoChatContextItemPopover from '../duo_chat_context_item_popover/duo_chat_context_item_popover.vue';
+import GlDuoChatContextItemMenuSearchItem from './duo_chat_context_item_menu_search_item.vue';
+
+describe('GlDuoChatContextItemMenuContextSearchItem', () => {
+ let wrapper;
+
+ const createWrapper = (props) => {
+ wrapper = shallowMount(GlDuoChatContextItemMenuSearchItem, {
+ propsData: {
+ ...props,
+ },
+ });
+ };
+
+ const findByTestId = (testId) => wrapper.find(`[data-testid="${testId}"]`);
+ const findCategoryIcon = () => findByTestId('category-icon');
+ const findContextItemPopover = () => wrapper.findComponent(GlDuoChatContextItemPopover);
+
+ describe.each([
+ {
+ category: getMockCategory(CONTEXT_ITEM_TYPE_PROJECT_FILE),
+ contextItem: MOCK_CONTEXT_ITEM_FILE,
+ },
+ {
+ category: getMockCategory(CONTEXT_ITEM_TYPE_ISSUE),
+ contextItem: MOCK_CONTEXT_ITEM_ISSUE,
+ },
+ {
+ category: getMockCategory(CONTEXT_ITEM_TYPE_MERGE_REQUEST),
+ contextItem: MOCK_CONTEXT_ITEM_MERGE_REQUEST,
+ },
+ ])('for "$category"', ({ category, contextItem }) => {
+ beforeEach(() => createWrapper({ category, contextItem }));
+
+ it('renders the category icon', () => {
+ expect(findCategoryIcon().props('name')).toBe(category.icon);
+ });
+
+ it('renders the context item popover', () => {
+ expect(findContextItemPopover().props()).toEqual(
+ expect.objectContaining({
+ contextItem,
+ target: `info-icon-${contextItem.id}`,
+ })
+ );
+ });
+
+ it('renders the default context item title', () => {
+ expect(wrapper.text()).toContain(contextItem.metadata.name);
+ });
+ });
+});
diff --git a/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu_search_item.vue b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu_search_item.vue
new file mode 100644
index 0000000000000000000000000000000000000000..df4175b15921126db059790e72f7f1588ebd3b55
--- /dev/null
+++ b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu_search_item.vue
@@ -0,0 +1,71 @@
+
+
+
+
+
+ {{ title }}
+
+
+
+
+ {{ secondaryText }}
+
+
+
diff --git a/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu_search_items.spec.js b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu_search_items.spec.js
index 00787560f12cfc56ea9c27704bf8e39e242a9f82..e2c28c98f1c300c8b912a3eb212d8652380f2754 100644
--- a/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu_search_items.spec.js
+++ b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu_search_items.spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import { MOCK_CATEGORIES, getMockContextItems } from '../mock_context_data';
+import { MOCK_CATEGORIES, getMockContextItems, getMockCategory } from '../mock_context_data';
import {
CONTEXT_ITEM_TYPE_ISSUE,
CONTEXT_ITEM_TYPE_MERGE_REQUEST,
@@ -7,6 +7,7 @@ import {
} from '../constants';
import GlDuoChatContextItemMenuSearchItems from './duo_chat_context_item_menu_search_items.vue';
import GlDuoChatContextItemMenuSearchItemsLoading from './duo_chat_context_item_menu_search_items_loading.vue';
+import GlDuoChatContextItemMenuSearchItem from './duo_chat_context_item_menu_search_item.vue';
describe('GlDuoChatContextItemMenuSearchItems', () => {
let wrapper;
@@ -93,12 +94,9 @@ describe('GlDuoChatContextItemMenuSearchItems', () => {
{ numResults: 5, expectedRows: 5 },
])('when there are $numResults results', ({ numResults, expectedRows }) => {
beforeEach(async () => {
- await wrapper.setProps({
- loading: false,
- results: getMockContextItems().slice(0, numResults),
- });
await wrapper.setProps({
loading: true,
+ results: getMockContextItems().slice(0, numResults),
});
});
@@ -165,19 +163,22 @@ describe('GlDuoChatContextItemMenuSearchItems', () => {
});
expect(findResultItems()).toHaveLength(1);
- expect(findByTestId('search-result-item-details').text()).toEqual(
- matchingResult.metadata.name
+ expect(wrapper.findComponent(GlDuoChatContextItemMenuSearchItem).props()).toEqual(
+ expect.objectContaining({
+ contextItem: matchingResult,
+ category,
+ })
);
});
it('marks the correct item as active', async () => {
- expect(findActiveItemDetails().text()).toEqual(results.at(0).metadata.name);
+ expect(findActiveItemDetails().props('contextItem')).toEqual(results.at(0));
await wrapper.setProps({
activeIndex: 1,
});
- expect(findActiveItemDetails().text()).toEqual(results.at(1).metadata.name);
+ expect(findActiveItemDetails().props('contextItem')).toEqual(results.at(1));
});
it('emits "active-index-change" event when hovering over an item', async () => {
@@ -209,7 +210,7 @@ describe('GlDuoChatContextItemMenuSearchItems', () => {
it('disables the item', () => {
expect(disabledItem.attributes('tabindex')).toBe('-1');
- expect(disabledItem.classes()).toContain('disabled');
+ expect(disabledItem.classes()).toContain('gl-cursor-not-allowed');
});
});
});
@@ -217,15 +218,15 @@ describe('GlDuoChatContextItemMenuSearchItems', () => {
describe.each([
{
- testCase: MOCK_CATEGORIES.find((cat) => cat.value === CONTEXT_ITEM_TYPE_PROJECT_FILE),
+ testCase: getMockCategory(CONTEXT_ITEM_TYPE_PROJECT_FILE),
expectedPlaceholder: 'Search files...',
},
{
- testCase: MOCK_CATEGORIES.find((cat) => cat.value === CONTEXT_ITEM_TYPE_ISSUE),
+ testCase: getMockCategory(CONTEXT_ITEM_TYPE_ISSUE),
expectedPlaceholder: 'Search issues...',
},
{
- testCase: MOCK_CATEGORIES.find((cat) => cat.value === CONTEXT_ITEM_TYPE_MERGE_REQUEST),
+ testCase: getMockCategory(CONTEXT_ITEM_TYPE_MERGE_REQUEST),
expectedPlaceholder: 'Search merge requests...',
},
])('when category is "$testCase.label"', ({ testCase, expectedPlaceholder }) => {
diff --git a/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu_search_items.vue b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu_search_items.vue
index 2a5badf650ebde95cea90a08d1e98488b8444aae..84fd89d3bc22b7ac15eaca9315a0cd0023577763 100644
--- a/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu_search_items.vue
+++ b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_menu/duo_chat_context_item_menu_search_items.vue
@@ -5,14 +5,16 @@ import GlAlert from '../../../../../../base/alert/alert.vue';
import { sprintf, translate } from '../../../../../../../utils/i18n';
import { categoryValidator, contextItemsValidator } from '../utils';
import GlDuoChatContextItemMenuSearchItemsLoading from './duo_chat_context_item_menu_search_items_loading.vue';
+import GlDuoChatContextItemMenuSearchItem from './duo_chat_context_item_menu_search_item.vue';
export default {
name: 'GlDuoChatContextItemMenuSearchItems',
components: {
GlAlert,
- GlFormInput,
GlDropdownItem,
+ GlDuoChatContextItemMenuSearchItem,
GlDuoChatContextItemMenuSearchItemsLoading,
+ GlFormInput,
},
model: {
prop: 'searchQuery',
@@ -77,8 +79,8 @@ export default {
},
},
methods: {
- selectItem(item) {
- this.$emit('select', item);
+ selectItem(contextItem) {
+ this.$emit('select', contextItem);
this.userInitiatedSearch = false;
},
setActiveIndex(index) {
@@ -114,20 +116,25 @@ export default {
-
-
- {{ item.metadata.name }} {{ item.isEnabled ? '' : '(disabled)' }}
-
+
diff --git a/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_popover/duo_chat_context_item_popover.spec.js b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_popover/duo_chat_context_item_popover.spec.js
index 68160276e98037ba7a43e2489605fb5b010f7e07..992f8eab458f4026599713ac7fa084a5118b9c46 100644
--- a/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_popover/duo_chat_context_item_popover.spec.js
+++ b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_popover/duo_chat_context_item_popover.spec.js
@@ -15,7 +15,7 @@ describe('GlDuoChatContextItemPopover', () => {
const createComponent = (props = {}, options = {}) => {
wrapper = shallowMount(GlDuoChatContextItemPopover, {
propsData: {
- item: MOCK_CONTEXT_ITEM_FILE,
+ contextItem: MOCK_CONTEXT_ITEM_FILE,
target: 'test-target',
placement: 'top',
...props,
@@ -67,16 +67,16 @@ describe('GlDuoChatContextItemPopover', () => {
['file', MOCK_CONTEXT_ITEM_FILE],
['issue', MOCK_CONTEXT_ITEM_ISSUE],
['merge request', MOCK_CONTEXT_ITEM_MERGE_REQUEST],
- ])('renders correct project and type for %s', (_, item) => {
- createComponent({ item });
+ ])('renders correct project and type for %s', (_, contextItem) => {
+ createComponent({ contextItem });
const content = findPopover().text();
- expect(content).toContain(item.metadata.info.project);
- expect(content).toContain(item.type);
+ expect(content).toContain(contextItem.metadata.info.project);
+ expect(content).toContain(contextItem.type);
});
it('renders file path for file items', () => {
- createComponent({ item: MOCK_CONTEXT_ITEM_FILE });
+ createComponent({ contextItem: MOCK_CONTEXT_ITEM_FILE });
const content = findPopover().text();
expect(content).toContain(MOCK_CONTEXT_ITEM_FILE.metadata.info.relFilePath);
@@ -85,8 +85,8 @@ describe('GlDuoChatContextItemPopover', () => {
it.each([
['issue', MOCK_CONTEXT_ITEM_ISSUE, '#1234'],
['merge request', MOCK_CONTEXT_ITEM_MERGE_REQUEST, '!1122'],
- ])('renders ID for %s items with correct prefix', (_, item, expected) => {
- createComponent({ item });
+ ])('renders ID for %s items with correct prefix', (_, contextItem, expected) => {
+ createComponent({ contextItem });
const content = findPopover().text();
expect(content).toContain(expected);
@@ -95,7 +95,7 @@ describe('GlDuoChatContextItemPopover', () => {
describe('disabled items', () => {
it('renders disabled message', () => {
- createComponent({ item: MOCK_CONTEXT_ITEM_ISSUE_DISABLED });
+ createComponent({ contextItem: MOCK_CONTEXT_ITEM_ISSUE_DISABLED });
expect(findDisabledMessage().text()).toContain(
'This foo is not available to bar, Lorem something something wow?'
@@ -107,7 +107,7 @@ describe('GlDuoChatContextItemPopover', () => {
...MOCK_CONTEXT_ITEM_FILE_DISABLED,
info: { ...MOCK_CONTEXT_ITEM_FILE_DISABLED.metadata.info, disabledReasons: undefined },
};
- createComponent({ item: itemWithoutReasons });
+ createComponent({ contextItem: itemWithoutReasons });
expect(findDisabledMessage().text()).toContain('This item is disabled');
});
diff --git a/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_popover/duo_chat_context_item_popover.vue b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_popover/duo_chat_context_item_popover.vue
index f7d40016de834304d632790c66764dcf83eb5d83..b00498842e324d4fff64ff8664220315bb9ebf2b 100644
--- a/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_popover/duo_chat_context_item_popover.vue
+++ b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_popover/duo_chat_context_item_popover.vue
@@ -6,11 +6,7 @@ import {
CONTEXT_ITEM_TYPE_MERGE_REQUEST,
CONTEXT_ITEM_TYPE_PROJECT_FILE,
} from '../constants';
-
-const ID_PREFIXES = {
- issue: '#',
- merge_request: '!',
-};
+import { formatIssueId, formatMergeRequestId } from '../utils';
export default {
name: 'DuoChatContextItemPopover',
@@ -19,9 +15,9 @@ export default {
},
props: {
/**
- * The context item to display in the popover.
+ * The context contextItem to display in the popover.
*/
- item: {
+ contextItem: {
type: Object,
required: true,
},
@@ -43,28 +39,36 @@ export default {
},
computed: {
itemInfo() {
- return this.item.metadata?.info || {};
+ return this.contextItem.metadata?.info || {};
},
id() {
const isIssuable =
- this.item.type === CONTEXT_ITEM_TYPE_ISSUE ||
- this.item.type === CONTEXT_ITEM_TYPE_MERGE_REQUEST;
+ this.contextItem.type === CONTEXT_ITEM_TYPE_ISSUE ||
+ this.contextItem.type === CONTEXT_ITEM_TYPE_MERGE_REQUEST;
return isIssuable ? this.itemInfo.iid || '' : null;
},
- idPrefix() {
- return ID_PREFIXES[this.item.type] || '';
+ formattedId() {
+ switch (this.contextItem.type) {
+ case CONTEXT_ITEM_TYPE_ISSUE:
+ return formatIssueId(this.id);
+ case CONTEXT_ITEM_TYPE_MERGE_REQUEST:
+ return formatMergeRequestId(this.id);
+ default:
+ return '';
+ }
},
filePath() {
- return this.item.type === CONTEXT_ITEM_TYPE_PROJECT_FILE
+ return this.contextItem.type === CONTEXT_ITEM_TYPE_PROJECT_FILE
? this.itemInfo.relFilePath || ''
: null;
},
isEnabled() {
- return this.item.isEnabled !== false;
+ return this.contextItem.isEnabled !== false;
},
disabledMessage() {
- return Array.isArray(this.item.disabledReasons) && this.item.disabledReasons.length > 0
- ? this.item.disabledReasons.join(', ')
+ return Array.isArray(this.contextItem.disabledReasons) &&
+ this.contextItem.disabledReasons.length > 0
+ ? this.contextItem.disabledReasons.join(', ')
: translate('DuoChatContextItemPopover.DisabledReason', 'This item is disabled');
},
},
@@ -78,12 +82,12 @@ export default {
:target="target"
triggers="hover focus"
:placement="placement"
- :title="item.metadata.name"
+ :title="contextItem.metadata.name"
custom-class="gl-duo-chat-item-popover"
>
{{
- item.metadata.name
+ contextItem.metadata.name
}}
@@ -101,13 +105,13 @@ export default {
{{ translate('DuoChatContextItemPopover.IdLabel', 'ID:') }}
- {{ idPrefix }}{{ id }}
+ {{ formattedId }}
{{
translate('DuoChatContextItemPopover.TypeLabel', 'Type:')
}}
- {{ item.type }}
+ {{ contextItem.type }}
{{
diff --git a/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_selections/duo_chat_context_item_selections.spec.js b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_selections/duo_chat_context_item_selections.spec.js
index 4ad71f15223419a7ffebb7285202736aa05c0a3a..e3227d1d9b0536e765b73303d04d625770511995 100644
--- a/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_selections/duo_chat_context_item_selections.spec.js
+++ b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_selections/duo_chat_context_item_selections.spec.js
@@ -134,10 +134,10 @@ describe('GlDuoChatContextItemSelections', () => {
createComponent();
const index = 0;
- const item = mockSelections.at(index);
+ const contextItem = mockSelections.at(index);
const popover = findPopovers().at(index);
- expect(popover.props('item')).toEqual(item);
+ expect(popover.props('contextItem')).toEqual(contextItem);
expect(popover.props('target')).toMatch(
/^context-item-123e4567-e89b-12d3-a456-426614174000-\d+$/
);
diff --git a/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_selections/duo_chat_context_item_selections.vue b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_selections/duo_chat_context_item_selections.vue
index b2a017404447951806783e3211c00782555b51f2..514996f07a370797060759de2fc32bc6e2b88404 100644
--- a/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_selections/duo_chat_context_item_selections.vue
+++ b/src/components/experimental/duo/chat/components/duo_chat_context/duo_chat_context_item_selections/duo_chat_context_item_selections.vue
@@ -72,12 +72,12 @@ export default {
toggleCollapse() {
this.isCollapsed = !this.isCollapsed;
},
- onRemoveItem(item) {
+ onRemoveItem(contextItem) {
/**
- * Emitted when a context item should be removed.
- * @property {Object} item - The context item to be removed
+ * Emitted when a context contextItem should be removed.
+ * @property {Object} item - The context contextItem to be removed
*/
- this.$emit('remove', item);
+ this.$emit('remove', contextItem);
},
},
};
@@ -99,20 +99,20 @@ export default {
data-testid="chat-context-tokens-wrapper"
>
-
-
- {{ item.metadata.name }}
+
+
+ {{ contextItem.metadata.name }}
diff --git a/src/components/experimental/duo/chat/components/duo_chat_context/mock_context_data.js b/src/components/experimental/duo/chat/components/duo_chat_context/mock_context_data.js
index 95d6cb2cec1c11788f930a949669577e3509fcd3..2c940ad81509de30a889fdbbb9982c85d4bf719e 100644
--- a/src/components/experimental/duo/chat/components/duo_chat_context/mock_context_data.js
+++ b/src/components/experimental/duo/chat/components/duo_chat_context/mock_context_data.js
@@ -10,6 +10,10 @@ export const MOCK_CATEGORIES = [
{ label: 'Merge Requests', value: CONTEXT_ITEM_TYPE_MERGE_REQUEST, icon: 'merge-request' },
];
+export function getMockCategory(categoryValue) {
+ return MOCK_CATEGORIES.find((cat) => cat.value === categoryValue);
+}
+
export const MOCK_CONTEXT_ITEM_FILE = {
id: '123e4567-e89b-12d3-a456-426614174000',
isEnabled: true,
diff --git a/src/components/experimental/duo/chat/components/duo_chat_context/utils.js b/src/components/experimental/duo/chat/components/duo_chat_context/utils.js
index d8baa557125040a5294792296bc85fe1a5698a3f..93cce8a00097e21dab7419c39d0b6ec1e35f1550 100644
--- a/src/components/experimental/duo/chat/components/duo_chat_context/utils.js
+++ b/src/components/experimental/duo/chat/components/duo_chat_context/utils.js
@@ -27,3 +27,15 @@ export function contextItemValidator(item) {
export function contextItemsValidator(items) {
return Array.isArray(items) && items.every((item) => contextItemValidator(item));
}
+
+export function formatIssueId(iid) {
+ if (!iid) return '';
+
+ return `#${iid}`;
+}
+
+export function formatMergeRequestId(iid) {
+ if (!iid) return '';
+
+ return `!${iid}`;
+}
diff --git a/src/components/experimental/duo/chat/components/duo_chat_context/utils.spec.js b/src/components/experimental/duo/chat/components/duo_chat_context/utils.spec.js
index 330cfe1cf17ad3e797b98c40019b773d98eb7213..b68f013ce6442f38bb3f82396479f0b608e8507a 100644
--- a/src/components/experimental/duo/chat/components/duo_chat_context/utils.spec.js
+++ b/src/components/experimental/duo/chat/components/duo_chat_context/utils.spec.js
@@ -3,6 +3,8 @@ import {
categoryValidator,
contextItemsValidator,
contextItemValidator,
+ formatIssueId,
+ formatMergeRequestId,
} from './utils';
import {
MOCK_CATEGORIES,
@@ -170,4 +172,34 @@ describe('duo_chat_context utils', () => {
});
});
});
+
+ describe('formatIssueId', () => {
+ it.each([
+ { id: '123', expected: '#123' },
+ { id: 123, expected: '#123' },
+ ])('formats "$id" as "$expected"', ({ id, expected }) => {
+ expect(formatIssueId(id)).toBe(expected);
+ });
+
+ it('returns empty string for falsy values', () => {
+ expect(formatIssueId()).toBe('');
+ expect(formatIssueId(null)).toBe('');
+ expect(formatIssueId('')).toBe('');
+ });
+ });
+
+ describe('formatMergeRequestId', () => {
+ it.each([
+ { id: '123', expected: '!123' },
+ { id: 123, expected: '!123' },
+ ])('formats "$id" as "$expected"', ({ id, expected }) => {
+ expect(formatMergeRequestId(id)).toBe(expected);
+ });
+
+ it('returns empty string for falsy values', () => {
+ expect(formatMergeRequestId()).toBe('');
+ expect(formatMergeRequestId(null)).toBe('');
+ expect(formatMergeRequestId('')).toBe('');
+ });
+ });
});