diff --git a/ee/app/assets/javascripts/ai/model_selection/model_selector.vue b/ee/app/assets/javascripts/ai/model_selection/model_selector.vue index ba88e94d766d2201083ca466efec592ea9d4686e..44983a5e875953985077d22f2bdf74b3459c7318 100644 --- a/ee/app/assets/javascripts/ai/model_selection/model_selector.vue +++ b/ee/app/assets/javascripts/ai/model_selection/model_selector.vue @@ -41,10 +41,12 @@ export default { return { value: GITLAB_DEFAULT_MODEL, text }; }, listItems() { - const modelOptions = this.aiFeatureSetting.selectableModels.map(({ ref, name }) => ({ - value: ref, - text: name, - })); + const modelOptions = this.aiFeatureSetting.selectableModels + .map(({ ref, name }) => ({ + value: ref, + text: name, + })) + .sort((a, b) => a.text.localeCompare(b.text)); return [...modelOptions, this.defaultModelOption]; }, diff --git a/ee/spec/frontend/ai/model_selection/model_selector_spec.js b/ee/spec/frontend/ai/model_selection/model_selector_spec.js index 0e668293277fd1f99031e518ebc3e03f0a1cd327..cea4231a1f1df056113fcb2543b9994b66c41dd6 100644 --- a/ee/spec/frontend/ai/model_selection/model_selector_spec.js +++ b/ee/spec/frontend/ai/model_selection/model_selector_spec.js @@ -95,7 +95,7 @@ describe('ModelSelector', () => { }); describe('loading state', () => { - it('passes corect loading state to `ModelSelectDropdown` while saving', async () => { + it('passes correct loading state to `ModelSelectDropdown` while saving', async () => { createComponent(); await findModelSelectDropdown().vm.$emit('select', 'claude_3_5_sonnet_20240620'); @@ -103,7 +103,7 @@ describe('ModelSelector', () => { expect(findModelSelectDropdown().props('isLoading')).toBe(true); }); - it('passes corect loading state to `ModelSelectDropdown` while batch saving', () => { + it('passes correct loading state to `ModelSelectDropdown` while batch saving', () => { createComponent({ props: { batchUpdateIsSaving: true } }); expect(findModelSelectDropdown().props('isLoading')).toBe(true); @@ -111,19 +111,46 @@ describe('ModelSelector', () => { }); describe('.listItems', () => { - it('contains a list of models, including a default model option', () => { + it('contains a list of models sorted in ascending alphabetical order, including a default model option', () => { createComponent(); expect(findModelSelectDropdown().props('items')).toEqual([ - { value: 'claude_sonnet_3_7_20250219', text: 'Claude Sonnet 3.7 - Anthropic' }, - { value: 'claude_3_5_sonnet_20240620', text: 'Claude Sonnet 3.5 - Anthropic' }, { value: 'claude_3_haiku_20240307', text: 'Claude Haiku 3 - Anthropic' }, + { value: 'claude_3_5_sonnet_20240620', text: 'Claude Sonnet 3.5 - Anthropic' }, + { value: 'claude_sonnet_3_7_20250219', text: 'Claude Sonnet 3.7 - Anthropic' }, { value: GITLAB_DEFAULT_MODEL, text: 'GitLab default model (Claude Sonnet 3.7 - Anthropic)', }, ]); }); + + it('sorts models in ascending alphabetical order', () => { + const testFeatureSetting = { + ...aiFeatureSetting, + selectableModels: [ + { ref: 'claude_3_haiku_20240307', name: 'Claude Haiku 3 - Anthropic' }, + { ref: 'gpt_5', name: 'OpenAI GPT-5' }, + { ref: 'claude_sonnet_4_20250514', name: 'Claude Sonnet 4 - Anthropic' }, + { ref: 'claude_3_5_sonnet_20240620', name: 'Claude Sonnet 3.5 - Anthropic' }, + { ref: 'claude_sonnet_3_7_20250219', name: 'Claude Sonnet 3.7 - Anthropic' }, + ], + }; + + createComponent({ props: { aiFeatureSetting: testFeatureSetting } }); + + const items = findModelSelectDropdown().props('items'); + const modelNames = items.map((item) => item.text); + + expect(modelNames).toEqual([ + 'Claude Haiku 3 - Anthropic', + 'Claude Sonnet 3.5 - Anthropic', + 'Claude Sonnet 3.7 - Anthropic', + 'Claude Sonnet 4 - Anthropic', + 'OpenAI GPT-5', + 'GitLab default model (Claude Sonnet 3.7 - Anthropic)', + ]); + }); }); describe('onSelect', () => {